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}