Skip to main content

turbopack_ecmascript/references/esm/
url.rs

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