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