turbopack_ecmascript/references/esm/
meta.rs1use std::borrow::Cow;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use swc_core::{
6 common::DUMMY_SP,
7 ecma::ast::{Expr, Ident},
8 quote,
9};
10use turbo_rcstr::{RcStr, rcstr};
11use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat};
12use turbo_tasks_fs::FileSystemPath;
13use turbopack_core::chunk::ChunkingContext;
14
15use crate::{
16 ast_path_trie::{AstPathId, AstPathTrie},
17 code_gen::{CodeGen, CodeGeneration},
18 create_visitor, magic_identifier,
19 runtime_functions::{TURBOPACK_MODULE, TURBOPACK_RESOLVE_FILE_URL},
20};
21
22#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode)]
30pub struct ImportMetaBinding {
31 path: FileSystemPath,
32 hmr_enabled: bool,
33 mode: RcStr,
34 base_url: RcStr,
35 is_ssr: bool,
36}
37
38impl ImportMetaBinding {
39 pub fn new(
40 path: FileSystemPath,
41 hmr_enabled: bool,
42 mode: RcStr,
43 base_url: RcStr,
44 is_ssr: bool,
45 ) -> Self {
46 ImportMetaBinding {
47 path,
48 hmr_enabled,
49 mode,
50 base_url,
51 is_ssr,
52 }
53 }
54
55 pub async fn code_generation(
56 &self,
57 _trie: &AstPathTrie,
58 chunking_context: Vc<Box<dyn ChunkingContext>>,
59 ) -> Result<CodeGeneration> {
60 let rel_path = chunking_context
61 .root_path()
62 .await?
63 .get_relative_path_to(&self.path);
64 let path = rel_path.map_or_else(
65 || {
66 quote!(
67 "(() => { throw new Error('could not convert import.meta.url to filepath') })()"
68 as Expr
69 )
70 },
71 |path| {
72 let formatted = encode_path(path.trim_start_matches("./")).to_string();
76 quote!(
77 "$turbopack_resolve_file_url($formatted)" as Expr,
78 turbopack_resolve_file_url: Expr = TURBOPACK_RESOLVE_FILE_URL.into(),
79 formatted: Expr = formatted.into()
80 )
81 },
82 );
83
84 let hmr_enabled = self.hmr_enabled;
85 let mode: Expr = self.mode.as_str().into();
86 let is_prod: Expr = (self.mode == "production").into();
87 let is_dev: Expr = (self.mode != "production").into();
88 let is_ssr: Expr = self.is_ssr.into();
89 let base_url: Expr = self.base_url.as_str().into();
90
91 let stmt = if hmr_enabled {
94 let turbopack_module: Expr = TURBOPACK_MODULE.into();
96 quote!(
97 "var $name = { get url() { return $path }, env: { DEV: $is_dev, PROD: \
98 $is_prod, MODE: $mode, BASE_URL: $base_url, SSR: $is_ssr }, get turbopackHot() { \
99 return $m.hot } };" as Stmt,
100 name = meta_ident(),
101 path: Expr = path,
102 is_dev: Expr = is_dev,
103 is_prod: Expr = is_prod,
104 mode: Expr = mode,
105 base_url: Expr = base_url,
106 is_ssr: Expr = is_ssr,
107 m: Expr = turbopack_module,
108 )
109 } else {
110 quote!(
111 "var $name = { get url() { return $path }, env: { DEV: $is_dev, PROD: \
112 $is_prod, MODE: $mode, BASE_URL: $base_url, SSR: $is_ssr } };" as Stmt,
113 name = meta_ident(),
114 path: Expr = path,
115 is_dev: Expr = is_dev,
116 is_prod: Expr = is_prod,
117 mode: Expr = mode,
118 base_url: Expr = base_url,
119 is_ssr: Expr = is_ssr,
120 )
121 };
122
123 Ok(CodeGeneration::hoisted_stmt(rcstr!("import.meta"), stmt))
124 }
125}
126
127impl From<ImportMetaBinding> for CodeGen {
128 fn from(val: ImportMetaBinding) -> Self {
129 CodeGen::ImportMetaBinding(val)
130 }
131}
132
133#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
139pub struct ImportMetaRef {
140 ast_path: AstPathId,
141}
142
143impl ImportMetaRef {
144 pub fn new(ast_path: AstPathId) -> Self {
145 ImportMetaRef { ast_path }
146 }
147
148 pub async fn code_generation(
149 &self,
150 trie: &AstPathTrie,
151 _chunking_context: Vc<Box<dyn ChunkingContext>>,
152 ) -> Result<CodeGeneration> {
153 let visitor = create_visitor!(trie, self.ast_path, visit_mut_expr, |expr: &mut Expr| {
154 *expr = Expr::Ident(meta_ident());
155 });
156
157 Ok(CodeGeneration::visitors(vec![visitor]))
158 }
159}
160
161impl From<ImportMetaRef> for CodeGen {
162 fn from(val: ImportMetaRef) -> Self {
163 CodeGen::ImportMetaRef(val)
164 }
165}
166
167fn encode_path(path: &'_ str) -> Cow<'_, str> {
170 let mut encoded = String::new();
171 let mut start = 0;
172 for (i, c) in path.char_indices() {
173 let mapping = match c {
174 '%' => "%25",
175 '\\' => "%5C",
176 '\n' => "%0A",
177 '\r' => "%0D",
178 '\t' => "%09",
179 _ => continue,
180 };
181
182 if encoded.is_empty() {
183 encoded.reserve(path.len());
184 }
185
186 encoded += &path[start..i];
187 encoded += mapping;
188 start = i + 1;
189 }
190
191 if encoded.is_empty() {
192 return Cow::Borrowed(path);
193 }
194 encoded += &path[start..];
195 Cow::Owned(encoded)
196}
197
198fn meta_ident() -> Ident {
199 Ident::new(
200 magic_identifier::mangle("import.meta").into(),
201 DUMMY_SP,
202 Default::default(),
203 )
204}
205
206#[cfg(test)]
207mod test {
208 use super::encode_path;
209
210 #[test]
211 fn test_encode_path_regular() {
212 let input = "abc";
213 assert_eq!(encode_path(input), "abc");
214 }
215
216 #[test]
217 fn test_encode_path_special_chars() {
218 let input = "abc%def\\ghi\njkl\rmno\tpqr";
219 assert_eq!(encode_path(input), "abc%25def%5Cghi%0Ajkl%0Dmno%09pqr");
220 }
221
222 #[test]
223 fn test_encode_path_special_char_start() {
224 let input = "%abc";
225 assert_eq!(encode_path(input), "%25abc");
226 }
227
228 #[test]
229 fn test_encode_path_special_char_end() {
230 let input = "abc%";
231 assert_eq!(encode_path(input), "abc%25");
232 }
233
234 #[test]
235 fn test_encode_path_special_char_contiguous() {
236 let input = "%%%";
237 assert_eq!(encode_path(input), "%25%25%25");
238 }
239}