turbopack_ecmascript/references/esm/url.rs
1use anyhow::{Result, bail};
2use serde::{Deserialize, Serialize};
3use swc_core::{
4 ecma::ast::{Expr, ExprOrSpread, NewExpr},
5 quote,
6};
7use turbo_rcstr::RcStr;
8use turbo_tasks::{
9 NonLocalValue, ResolvedVc, TaskInput, ValueToString, Vc, debug::ValueDebugFormat,
10 trace::TraceRawVcs,
11};
12use turbopack_core::{
13 chunk::{
14 ChunkableModuleReference, ChunkingContext, ChunkingType, ChunkingTypeOption,
15 ModuleChunkItemIdExt,
16 },
17 environment::Rendering,
18 issue::IssueSource,
19 reference::ModuleReference,
20 reference_type::{ReferenceType, UrlReferenceSubType},
21 resolve::{
22 ExternalType, ModuleResolveResult, origin::ResolveOrigin, parse::Request, url_resolve,
23 },
24};
25
26use super::base::ReferencedAsset;
27use crate::{
28 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
29 create_visitor,
30 references::AstPath,
31 runtime_functions::{
32 TURBOPACK_RELATIVE_URL, TURBOPACK_REQUIRE, TURBOPACK_RESOLVE_MODULE_ID_PATH,
33 },
34 utils::module_id_to_lit,
35};
36
37/// Determines how to treat `new URL(...)` rewrites.
38/// This allows to construct url depends on the different building context,
39/// e.g. SSR, CSR, or Node.js.
40#[derive(
41 Copy,
42 Clone,
43 Debug,
44 Eq,
45 PartialEq,
46 Hash,
47 Serialize,
48 Deserialize,
49 TraceRawVcs,
50 TaskInput,
51 NonLocalValue,
52)]
53pub enum UrlRewriteBehavior {
54 /// Omits base, resulting in a relative URL.
55 Relative,
56 /// Uses the full URL, including the base.
57 Full,
58 /// Do not attempt to rewrite the URL.
59 None,
60}
61
62/// URL Asset References are injected during code analysis when we find a
63/// (statically analyzable) `new URL("path", import.meta.url)`.
64///
65/// It's responsible rewriting the `URL` constructor's arguments to allow the
66/// referenced file to be imported/fetched/etc.
67#[turbo_tasks::value]
68pub struct UrlAssetReference {
69 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
70 request: ResolvedVc<Request>,
71 rendering: Rendering,
72 issue_source: IssueSource,
73 in_try: bool,
74 url_rewrite_behavior: UrlRewriteBehavior,
75}
76
77impl UrlAssetReference {
78 pub fn new(
79 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
80 request: ResolvedVc<Request>,
81 rendering: Rendering,
82 issue_source: IssueSource,
83 in_try: bool,
84 url_rewrite_behavior: UrlRewriteBehavior,
85 ) -> Self {
86 UrlAssetReference {
87 origin,
88 request,
89 rendering,
90 issue_source,
91 in_try,
92 url_rewrite_behavior,
93 }
94 }
95
96 pub(crate) fn get_referenced_asset(self: Vc<Self>) -> Vc<ReferencedAsset> {
97 ReferencedAsset::from_resolve_result(self.resolve_reference())
98 }
99}
100
101#[turbo_tasks::value_impl]
102impl ModuleReference for UrlAssetReference {
103 #[turbo_tasks::function]
104 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
105 url_resolve(
106 *self.origin,
107 *self.request,
108 ReferenceType::Url(UrlReferenceSubType::EcmaScriptNewUrl),
109 Some(self.issue_source),
110 self.in_try,
111 )
112 }
113}
114
115#[turbo_tasks::value_impl]
116impl ValueToString for UrlAssetReference {
117 #[turbo_tasks::function]
118 async fn to_string(&self) -> Result<Vc<RcStr>> {
119 Ok(Vc::cell(
120 format!("new URL({})", self.request.to_string().await?,).into(),
121 ))
122 }
123}
124
125#[turbo_tasks::value_impl]
126impl ChunkableModuleReference for UrlAssetReference {
127 #[turbo_tasks::function]
128 fn chunking_type(&self) -> Vc<ChunkingTypeOption> {
129 Vc::cell(Some(ChunkingType::Parallel {
130 inherit_async: false,
131 hoisted: false,
132 }))
133 }
134}
135
136impl IntoCodeGenReference for UrlAssetReference {
137 fn into_code_gen_reference(
138 self,
139 path: AstPath,
140 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
141 let reference = self.resolved_cell();
142 (
143 ResolvedVc::upcast(reference),
144 CodeGen::UrlAssetReferenceCodeGen(UrlAssetReferenceCodeGen { reference, path }),
145 )
146 }
147}
148
149#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
150pub struct UrlAssetReferenceCodeGen {
151 reference: ResolvedVc<UrlAssetReference>,
152 path: AstPath,
153}
154
155impl UrlAssetReferenceCodeGen {
156 /// Rewrites call to the `new URL()` ctor depends on the current
157 /// conditions. Generated code will point to the output path of the asset,
158 /// as similar to the webpack's behavior. This is based on the
159 /// configuration (UrlRewriteBehavior), and the current context
160 /// (rendering), lastly the asset's condition (if it's referenced /
161 /// external). The following table shows the behavior:
162 /*
163 * original call: `new URL(url, base);`
164 ┌───────────────────────────────┬─────────────────────────────────────────────────────────────────────────┬────────────────────────────────────────────────┬───────────────────────┐
165 │ UrlRewriteBehavior\RefAsset │ ReferencedAsset::Some() │ ReferencedAsset::External │ ReferencedAsset::None │
166 ├───────────────────────────────┼─────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────┼───────────────────────┤
167 │ Relative │ __turbopack_relative_url__(__turbopack_require__(urlId)) │ __turbopack_relative_url__(url) │ new URL(url, base) │
168 │ Full(RenderingClient::Client) │ new URL(__turbopack_require__(urlId), location.origin) │ new URL(url, location.origin) │ new URL(url, base) │
169 │ Full(RenderingClient::..) │ new URL(__turbopack_resolve_module_id_path__(urlId)) │ new URL(url, base) │ new URL(url, base) │
170 │ None │ new URL(url, base) │ new URL(url, base) │ new URL(url, base) │
171 └───────────────────────────────┴─────────────────────────────────────────────────────────────────────────┴────────────────────────────────────────────────┴───────────────────────┘
172 */
173 pub async fn code_generation(
174 &self,
175 chunking_context: Vc<Box<dyn ChunkingContext>>,
176 ) -> Result<CodeGeneration> {
177 let mut visitors = vec![];
178
179 let reference = self.reference.await?;
180
181 match reference.url_rewrite_behavior {
182 UrlRewriteBehavior::Relative => {
183 let referenced_asset = self.reference.get_referenced_asset().await?;
184
185 // if the referenced url is in the module graph of turbopack, replace it into
186 // the chunk item will be emitted into output path to point the
187 // static asset path. for the `new URL()` call, replace it into
188 // pseudo url object `__turbopack_relative_url__`
189 // which is injected by turbopack's runtime to resolve into the relative path
190 // omitting the base.
191 match &*referenced_asset {
192 ReferencedAsset::Some(asset) => {
193 // We rewrite the first `new URL()` arguments to be a require() of the chunk
194 // item, which exports the static asset path to the linked file.
195 let id = asset.chunk_item_id(Vc::upcast(chunking_context)).await?;
196
197 visitors.push(create_visitor!(self.path, visit_mut_expr, |new_expr: &mut Expr| {
198 let should_rewrite_to_relative = if let Expr::New(NewExpr { args: Some(args), .. }) = new_expr {
199 matches!(args.first(), Some(ExprOrSpread { .. }))
200 } else {
201 false
202 };
203
204 if should_rewrite_to_relative {
205 *new_expr = quote!(
206 "new $turbopack_relative_url($turbopack_require($id))" as Expr,
207 turbopack_relative_url: Expr = TURBOPACK_RELATIVE_URL.into(),
208 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
209 id: Expr = module_id_to_lit(&id),
210 );
211 }
212 }));
213 }
214 ReferencedAsset::External(request, ExternalType::Url) => {
215 let request = request.to_string();
216 visitors.push(create_visitor!(self.path, visit_mut_expr, |new_expr: &mut Expr| {
217 let should_rewrite_to_relative = if let Expr::New(NewExpr { args: Some(args), .. }) = new_expr {
218 matches!(args.first(), Some(ExprOrSpread { .. }))
219 } else {
220 false
221 };
222
223 if should_rewrite_to_relative {
224 *new_expr = quote!(
225 "new $turbopack_relative_url($id)" as Expr,
226 turbopack_relative_url: Expr = TURBOPACK_RELATIVE_URL.into(),
227 id: Expr = request.as_str().into(),
228 );
229 }
230 }));
231 }
232 ReferencedAsset::External(request, ty) => {
233 bail!(
234 "Unsupported external type {:?} for URL reference with request: {:?}",
235 ty,
236 request
237 )
238 }
239 ReferencedAsset::None | ReferencedAsset::Unresolvable => {}
240 }
241 }
242 UrlRewriteBehavior::Full => {
243 let referenced_asset = self.reference.get_referenced_asset().await?;
244
245 // For rendering environments (CSR), we rewrite the `import.meta.url` to
246 // be a location.origin because it allows us to access files from the root of
247 // the dev server.
248 //
249 // By default for the remaining environments, turbopack's runtime have overridden
250 // `import.meta.url`.
251 let rewrite_url_base = match reference.rendering {
252 Rendering::Client => Some(quote!("location.origin" as Expr)),
253 Rendering::None | Rendering::Server => None,
254 };
255
256 match &*referenced_asset {
257 ReferencedAsset::Some(asset) => {
258 // We rewrite the first `new URL()` arguments to be a require() of the
259 // chunk item, which returns the asset path as its exports.
260 let id = asset.chunk_item_id(Vc::upcast(chunking_context)).await?;
261
262 // If there's a rewrite to the base url, then the current rendering
263 // environment should able to resolve the asset path
264 // (asset_url) from the base. Wrap the module id
265 // with __turbopack_require__ which returns the asset_url.
266 //
267 // Otherwise, the environment should provide an absolute path to the actual
268 // output asset; delegate those calculation to the
269 // runtime fn __turbopack_resolve_module_id_path__.
270 let url_segment_resolver = if rewrite_url_base.is_some() {
271 quote!(
272 "$turbopack_require($id)" as Expr,
273 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
274 id: Expr = module_id_to_lit(&id),
275 )
276 } else {
277 quote!(
278 "$turbopack_resolve_module_id_path($id)" as Expr,
279 turbopack_resolve_module_id_path: Expr = TURBOPACK_RESOLVE_MODULE_ID_PATH.into(),
280 id: Expr = module_id_to_lit(&id),
281 )
282 };
283
284 visitors.push(create_visitor!(
285 self.path,
286 visit_mut_expr,
287 |new_expr: &mut Expr| {
288 if let Expr::New(NewExpr {
289 args: Some(args), ..
290 }) = new_expr
291 {
292 if let Some(ExprOrSpread {
293 box expr,
294 spread: None,
295 }) = args.get_mut(0)
296 {
297 *expr = url_segment_resolver.clone();
298 }
299
300 if let Some(ExprOrSpread {
301 box expr,
302 spread: None,
303 }) = args.get_mut(1)
304 {
305 if let Some(rewrite) = &rewrite_url_base {
306 *expr = rewrite.clone();
307 } else {
308 // If rewrite for the base doesn't exists, means
309 // __turbopack_resolve_module_id_path__
310 // should resolve the full path correctly and there
311 // shouldn't be a base.
312 args.remove(1);
313 }
314 }
315 }
316 }
317 ));
318 }
319 ReferencedAsset::External(request, ExternalType::Url) => {
320 let request = request.to_string();
321 visitors.push(create_visitor!(
322 self.path,
323 visit_mut_expr,
324 |new_expr: &mut Expr| {
325 if let Expr::New(NewExpr {
326 args: Some(args), ..
327 }) = new_expr
328 {
329 if let Some(ExprOrSpread {
330 box expr,
331 spread: None,
332 }) = args.get_mut(0)
333 {
334 *expr = request.as_str().into()
335 }
336
337 if let Some(rewrite) = &rewrite_url_base
338 && let Some(ExprOrSpread {
339 box expr,
340 spread: None,
341 }) = args.get_mut(1)
342 {
343 *expr = rewrite.clone();
344 }
345 }
346 }
347 ));
348 }
349 ReferencedAsset::External(request, ty) => {
350 bail!(
351 "Unsupported external type {:?} for URL reference with request: {:?}",
352 ty,
353 request
354 )
355 }
356 ReferencedAsset::None | ReferencedAsset::Unresolvable => {}
357 }
358 }
359 UrlRewriteBehavior::None => {
360 // Asked to not rewrite the URL, so we don't do anything.
361 }
362 };
363
364 Ok(CodeGeneration::visitors(visitors))
365 }
366}