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;
11use turbo_tasks::{NonLocalValue, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
12use turbo_tasks_fs::FileSystemPath;
13use turbopack_core::chunk::ChunkingContext;
14
15use crate::{
16 code_gen::{CodeGen, CodeGeneration},
17 create_visitor, magic_identifier,
18 references::AstPath,
19 runtime_functions::{TURBOPACK_MODULE, TURBOPACK_RESOLVE_ABSOLUTE_PATH},
20};
21
22#[derive(
30 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
31)]
32pub struct ImportMetaBinding {
33 path: FileSystemPath,
34 hmr_enabled: bool,
35}
36
37impl ImportMetaBinding {
38 pub fn new(path: FileSystemPath, hmr_enabled: bool) -> Self {
39 ImportMetaBinding { path, hmr_enabled }
40 }
41
42 pub async fn code_generation(
43 &self,
44 chunking_context: Vc<Box<dyn ChunkingContext>>,
45 ) -> Result<CodeGeneration> {
46 let rel_path = chunking_context
47 .root_path()
48 .await?
49 .get_relative_path_to(&self.path);
50 let path = rel_path.map_or_else(
51 || {
52 quote!(
53 "(() => { throw new Error('could not convert import.meta.url to filepath') })()"
54 as Expr
55 )
56 },
57 |path| {
58 let formatted = encode_path(path.trim_start_matches("./")).to_string();
59 quote!(
60 "`file://${$turbopack_resolve_absolute_path($formatted)}`" as Expr,
61 turbopack_resolve_absolute_path: Expr = TURBOPACK_RESOLVE_ABSOLUTE_PATH.into(),
62 formatted: Expr = formatted.into()
63 )
64 },
65 );
66
67 let hmr_enabled = self.hmr_enabled;
68
69 let stmt = if hmr_enabled {
72 let turbopack_module: Expr = TURBOPACK_MODULE.into();
74 quote!(
75 "const $name = { get url() { return $path }, get turbopackHot() { return $m.hot } };" as Stmt,
76 name = meta_ident(),
77 path: Expr = path,
78 m: Expr = turbopack_module,
79 )
80 } else {
81 quote!(
82 "const $name = { get url() { return $path } };" as Stmt,
83 name = meta_ident(),
84 path: Expr = path,
85 )
86 };
87
88 Ok(CodeGeneration::hoisted_stmt(rcstr!("import.meta"), stmt))
89 }
90}
91
92impl From<ImportMetaBinding> for CodeGen {
93 fn from(val: ImportMetaBinding) -> Self {
94 CodeGen::ImportMetaBinding(val)
95 }
96}
97
98#[derive(
104 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
105)]
106pub struct ImportMetaRef {
107 ast_path: AstPath,
108}
109
110impl ImportMetaRef {
111 pub fn new(ast_path: AstPath) -> Self {
112 ImportMetaRef { ast_path }
113 }
114
115 pub async fn code_generation(
116 &self,
117 _chunking_context: Vc<Box<dyn ChunkingContext>>,
118 ) -> Result<CodeGeneration> {
119 let visitor = create_visitor!(self.ast_path, visit_mut_expr, |expr: &mut Expr| {
120 *expr = Expr::Ident(meta_ident());
121 });
122
123 Ok(CodeGeneration::visitors(vec![visitor]))
124 }
125}
126
127impl From<ImportMetaRef> for CodeGen {
128 fn from(val: ImportMetaRef) -> Self {
129 CodeGen::ImportMetaRef(val)
130 }
131}
132
133fn encode_path(path: &'_ str) -> Cow<'_, str> {
136 let mut encoded = String::new();
137 let mut start = 0;
138 for (i, c) in path.char_indices() {
139 let mapping = match c {
140 '%' => "%25",
141 '\\' => "%5C",
142 '\n' => "%0A",
143 '\r' => "%0D",
144 '\t' => "%09",
145 _ => continue,
146 };
147
148 if encoded.is_empty() {
149 encoded.reserve(path.len());
150 }
151
152 encoded += &path[start..i];
153 encoded += mapping;
154 start = i + 1;
155 }
156
157 if encoded.is_empty() {
158 return Cow::Borrowed(path);
159 }
160 encoded += &path[start..];
161 Cow::Owned(encoded)
162}
163
164fn meta_ident() -> Ident {
165 Ident::new(
166 magic_identifier::mangle("import.meta").into(),
167 DUMMY_SP,
168 Default::default(),
169 )
170}
171
172#[cfg(test)]
173mod test {
174 use super::encode_path;
175
176 #[test]
177 fn test_encode_path_regular() {
178 let input = "abc";
179 assert_eq!(encode_path(input), "abc");
180 }
181
182 #[test]
183 fn test_encode_path_special_chars() {
184 let input = "abc%def\\ghi\njkl\rmno\tpqr";
185 assert_eq!(encode_path(input), "abc%25def%5Cghi%0Ajkl%0Dmno%09pqr");
186 }
187
188 #[test]
189 fn test_encode_path_special_char_start() {
190 let input = "%abc";
191 assert_eq!(encode_path(input), "%25abc");
192 }
193
194 #[test]
195 fn test_encode_path_special_char_end() {
196 let input = "abc%";
197 assert_eq!(encode_path(input), "abc%25");
198 }
199
200 #[test]
201 fn test_encode_path_special_char_contiguous() {
202 let input = "%%%";
203 assert_eq!(encode_path(input), "%25%25%25");
204 }
205}