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}