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