1use std::{borrow::Cow, collections::HashSet};
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use swc_core::{
6 common::DUMMY_SP,
7 ecma::ast::{
8 CallExpr, Callee, Expr, ExprOrSpread, KeyValueProp, Lit, ObjectLit, Prop, PropName,
9 PropOrSpread,
10 },
11 quote, quote_expr,
12};
13use turbo_rcstr::{RcStr, rcstr};
14use turbo_tasks::{
15 FxIndexMap, NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, Vc, debug::ValueDebugFormat,
16 trace::TraceRawVcs,
17};
18use turbopack_core::{
19 chunk::{ChunkableModule, ChunkingContext, ModuleChunkItemIdExt, ModuleId},
20 issue::{
21 IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue,
22 module::emit_unknown_module_type_error,
23 },
24 resolve::{
25 ExternalType, ModuleResolveResult, ModuleResolveResultItem, origin::ResolveOrigin,
26 parse::Request,
27 },
28};
29
30use crate::{
31 references::util::{
32 request_to_string, throw_module_not_found_error_expr, throw_module_not_found_expr,
33 throw_module_not_found_expr_async,
34 },
35 runtime_functions::{
36 TURBOPACK_ASYNC_LOADER, TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE,
37 TURBOPACK_IMPORT, TURBOPACK_MODULE_CONTEXT, TURBOPACK_REQUIRE,
38 },
39 utils::module_id_to_lit,
40};
41
42#[derive(PartialEq, Eq, ValueDebugFormat, TraceRawVcs, NonLocalValue, Encode, Decode)]
43pub(crate) enum SinglePatternMapping {
44 Invalid,
46 Unresolvable(String),
48 Ignored,
50 Module(ModuleId),
57 ModuleLoader(ModuleId),
66 External(RcStr, ExternalType),
68}
69
70#[turbo_tasks::value]
74pub(crate) enum PatternMapping {
75 Single(SinglePatternMapping),
82 Map(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, SinglePatternMapping>),
89}
90
91#[derive(
92 Copy, Clone, Debug, Eq, PartialEq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
93)]
94pub(crate) enum ResolveType {
95 AsyncChunkLoader,
96 ChunkItem,
97}
98
99impl SinglePatternMapping {
100 pub fn create_id(&self, key_expr: Cow<'_, Expr>) -> Expr {
101 match self {
102 Self::Invalid => {
103 quote!(
104 "(() => {throw new Error('could not resolve \"' + $arg + '\" into a module');})()" as Expr,
105 arg: Expr = key_expr.into_owned()
106 )
107 }
108 Self::Unresolvable(request) => throw_module_not_found_expr(request),
109 Self::Ignored => {
110 quote!("undefined" as Expr)
111 }
112 Self::Module(module_id) | Self::ModuleLoader(module_id) => module_id_to_lit(module_id),
113 Self::External(s, _) => Expr::Lit(Lit::Str(s.as_str().into())),
114 }
115 }
116
117 pub fn create_require(&self, key_expr: Cow<'_, Expr>) -> Expr {
118 match self {
119 Self::Invalid => self.create_id(key_expr),
120 Self::Unresolvable(request) => throw_module_not_found_expr(request),
121 Self::Ignored => quote!("{}" as Expr),
122 Self::Module(_) | Self::ModuleLoader(_) => quote!(
123 "$turbopack_require($arg)" as Expr,
124 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
125 arg: Expr = self.create_id(key_expr)
126 ),
127 Self::External(request, ExternalType::CommonJs) => quote!(
128 "$turbopack_external_require($arg, () => require($arg))" as Expr,
129 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
130 arg: Expr = request.as_str().into()
131 ),
132 Self::External(request, ty) => throw_module_not_found_error_expr(
133 request,
134 &format!("Unsupported external type {ty:?} for commonjs reference"),
135 ),
136 }
137 }
138
139 pub fn create_import(&self, key_expr: Cow<'_, Expr>, import_externals: bool) -> Expr {
140 match self {
141 Self::Invalid => {
142 let error = quote_expr!(
143 "() => {throw new Error('could not resolve \"' + $arg + '\" into a module');}",
144 arg: Expr = key_expr.into_owned()
145 );
146 Expr::Call(CallExpr {
147 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
148 args: vec![ExprOrSpread {
149 spread: None,
150 expr: error,
151 }],
152 span: DUMMY_SP,
153 ..Default::default()
154 })
155 }
156 Self::Unresolvable(request) => throw_module_not_found_expr_async(request),
157 Self::External(_, ExternalType::EcmaScriptModule) => {
158 if import_externals {
159 Expr::Call(CallExpr {
160 callee: Callee::Expr(Box::new(TURBOPACK_EXTERNAL_IMPORT.into())),
161 args: vec![ExprOrSpread {
162 spread: None,
163 expr: Box::new(key_expr.into_owned()),
164 }],
165 span: DUMMY_SP,
166 ..Default::default()
167 })
168 } else {
169 Expr::Call(CallExpr {
170 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
171 args: vec![ExprOrSpread {
172 spread: None,
173 expr: quote_expr!(
174 "() => $turbopack_external_require($arg, () => require($arg), true)",
175 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
176 arg: Expr = key_expr.into_owned()
177 ),
178 }],
179 span: DUMMY_SP,
180 ..Default::default()
181 })
182 }
183 }
184 Self::External(_, ExternalType::CommonJs | ExternalType::Url) => Expr::Call(CallExpr {
185 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
186 args: vec![ExprOrSpread {
187 spread: None,
188 expr: quote_expr!(
189 "() => $turbopack_external_require($arg, () => require($arg), true)",
190 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
191 arg: Expr = key_expr.into_owned()
192 ),
193 }],
194 span: DUMMY_SP,
195 ..Default::default()
196 }),
197 #[allow(unreachable_patterns)]
198 Self::External(request, ty) => throw_module_not_found_error_expr(
199 request,
200 &format!("Unsupported external type {ty:?} for dynamic import reference"),
201 ),
202 Self::ModuleLoader(module_id) => {
203 quote!("$turbopack_async_loader($id)" as Expr,
204 turbopack_async_loader: Expr = TURBOPACK_ASYNC_LOADER.into(),
205 id: Expr = module_id_to_lit(module_id)
206 )
207 }
208 Self::Ignored => {
209 quote!("Promise.resolve({})" as Expr)
210 }
211 Self::Module(_) => Expr::Call(CallExpr {
212 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
213 args: vec![ExprOrSpread {
214 spread: None,
215 expr: quote_expr!(
216 "() => $turbopack_import($arg)",
217 turbopack_import: Expr = TURBOPACK_IMPORT.into(),
218 arg: Expr = self.create_id(key_expr)
219 ),
220 }],
221 span: DUMMY_SP,
222 ..Default::default()
223 }),
224 }
225 }
226}
227
228enum ImportMode {
229 Require,
230 Import { import_externals: bool },
231}
232
233fn create_context_map(
234 map: &FxIndexMap<RcStr, SinglePatternMapping>,
235 key_expr: &Expr,
236 import_mode: ImportMode,
237) -> Expr {
238 let props = map
239 .iter()
240 .map(|(k, v)| {PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {key: PropName::Str(k.as_str().into()),
241 value: quote_expr!(
242 "{id: () => $id, module: () => $module}",
243 id: Expr = v.create_id(Cow::Borrowed(key_expr)),
244 module: Expr = match import_mode {ImportMode::Require => v.create_require(Cow::Borrowed(key_expr)),
245 ImportMode::Import {import_externals} => v.create_import(Cow::Borrowed(key_expr), import_externals),},
246 ),})))})
247 .collect();
248
249 Expr::Object(ObjectLit {
250 span: DUMMY_SP,
251 props,
252 })
253}
254
255impl PatternMapping {
256 pub fn create_id(&self, key_expr: Expr) -> Expr {
257 match self {
258 PatternMapping::Single(pm) => pm.create_id(Cow::Owned(key_expr)),
259 PatternMapping::Map(map) => {
260 let map = create_context_map(map, &key_expr, ImportMode::Require);
261
262 quote!("$turbopack_module_context($map).resolve($key)" as Expr,
263 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
264 map: Expr = map,
265 key: Expr = key_expr
266 )
267 }
268 }
269 }
270
271 pub fn create_require(&self, key_expr: Expr) -> Expr {
272 match self {
273 PatternMapping::Single(pm) => pm.create_require(Cow::Owned(key_expr)),
274 PatternMapping::Map(map) => {
275 let map = create_context_map(map, &key_expr, ImportMode::Require);
276
277 quote!("$turbopack_module_context($map)($key)" as Expr,
278 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
279 map: Expr = map,
280 key: Expr = key_expr
281 )
282 }
283 }
284 }
285
286 pub fn create_import(&self, key_expr: Expr, import_externals: bool) -> Expr {
287 match self {
288 PatternMapping::Single(pm) => pm.create_import(Cow::Owned(key_expr), import_externals),
289 PatternMapping::Map(map) => {
290 let map =
291 create_context_map(map, &key_expr, ImportMode::Import { import_externals });
292
293 quote!("$turbopack_module_context($map).import($key)" as Expr,
294 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
295 map: Expr = map,
296 key: Expr = key_expr
297 )
298 }
299 }
300 }
301}
302
303async fn to_single_pattern_mapping(
304 origin: Vc<Box<dyn ResolveOrigin>>,
305 chunking_context: Vc<Box<dyn ChunkingContext>>,
306 resolve_item: &ModuleResolveResultItem,
307 primary: &[(turbopack_core::resolve::RequestKey, ModuleResolveResultItem)],
308 resolve_type: ResolveType,
309) -> Result<SinglePatternMapping> {
310 let module = match resolve_item {
311 ModuleResolveResultItem::Module(module) => *module,
312 ModuleResolveResultItem::External { name: s, ty, .. } => {
313 return Ok(SinglePatternMapping::External(s.clone(), *ty));
314 }
315 ModuleResolveResultItem::Ignore => return Ok(SinglePatternMapping::Ignored),
316 ModuleResolveResultItem::Unknown(source) => {
317 emit_unknown_module_type_error(**source).await?;
318 return Ok(SinglePatternMapping::Unresolvable(
319 "unknown module type".to_string(),
320 ));
321 }
322 ModuleResolveResultItem::Error(issue) => {
323 return Ok(SinglePatternMapping::Unresolvable(
324 issue
325 .into_trait_ref()
326 .await?
327 .title()
328 .await?
329 .to_unstyled_string(),
330 ));
331 }
332 ModuleResolveResultItem::Duplicate(first) => {
333 return Box::pin(to_single_pattern_mapping(
334 origin,
335 chunking_context,
336 &primary[*first].1,
337 primary,
338 resolve_type,
339 ))
340 .await;
341 }
342 ModuleResolveResultItem::Empty | ModuleResolveResultItem::Custom(_) => {
343 CodeGenerationIssue {
345 severity: IssueSeverity::Bug,
346 title: StyledString::Text(rcstr!(
347 "pattern mapping is not implemented for this result"
348 ))
349 .resolved_cell(),
350 message: StyledString::Text(
351 format!(
352 "the reference resolves to a non-trivial result, which is not supported \
353 yet: {resolve_item:?}"
354 )
355 .into(),
356 )
357 .resolved_cell(),
358 path: origin.origin_path().owned().await?,
359 source: None,
360 }
361 .resolved_cell()
362 .emit();
363 return Ok(SinglePatternMapping::Invalid);
364 }
365 };
366 if let Some(chunkable) = ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(module) {
367 match resolve_type {
368 ResolveType::AsyncChunkLoader => {
369 let ident = chunking_context.async_loader_chunk_item_ident(*chunkable);
370 let loader_id = chunking_context
371 .chunk_item_id_strategy()
372 .await?
373 .get_id_from_ident(ident)
374 .await?;
375 return Ok(SinglePatternMapping::ModuleLoader(loader_id));
376 }
377 ResolveType::ChunkItem => {
378 let item_id = chunkable.chunk_item_id(chunking_context).await?;
379 return Ok(SinglePatternMapping::Module(item_id));
380 }
381 }
382 }
383 CodeGenerationIssue {
384 severity: IssueSeverity::Bug,
385 title: StyledString::Text(rcstr!("non-ecmascript placeable asset")).resolved_cell(),
386 message: StyledString::Text(rcstr!(
387 "asset is not placeable in ESM chunks, so it doesn't have a module id"
388 ))
389 .resolved_cell(),
390 path: origin.origin_path().owned().await?,
391 source: None,
392 }
393 .resolved_cell()
394 .emit();
395 Ok(SinglePatternMapping::Invalid)
396}
397
398#[turbo_tasks::value_impl]
399impl PatternMapping {
400 #[turbo_tasks::function]
404 pub async fn resolve_request(
405 request: Vc<Request>,
406 origin: Vc<Box<dyn ResolveOrigin>>,
407 chunking_context: Vc<Box<dyn ChunkingContext>>,
408 resolve_result: Vc<ModuleResolveResult>,
409 resolve_type: ResolveType,
410 ) -> Result<Vc<PatternMapping>> {
411 let result = resolve_result.await?;
412 match result.primary.len() {
413 0 => Ok(PatternMapping::Single(SinglePatternMapping::Unresolvable(
414 request_to_string(request).await?.to_string(),
415 ))
416 .cell()),
417 1 if !request.request_pattern().await?.has_dynamic_parts() => {
418 let resolve_item = &result.primary.first().unwrap().1;
419 let single_pattern_mapping = to_single_pattern_mapping(
420 origin,
421 chunking_context,
422 resolve_item,
423 &result.primary,
424 resolve_type,
425 )
426 .await?;
427 Ok(PatternMapping::Single(single_pattern_mapping).cell())
428 }
429 _ => {
430 let primary = &result.primary;
431 let mut set = HashSet::new();
432 let items: Vec<(RcStr, &ModuleResolveResultItem)> = primary
433 .iter()
434 .filter_map(|(k, v)| {
435 let request = k.request.as_ref()?;
436 set.insert(request).then(|| (request.clone(), v))
437 })
438 .collect();
439 let map = items
440 .into_iter()
441 .map(|(k, v)| async move {
442 let single_pattern_mapping = to_single_pattern_mapping(
443 origin,
444 chunking_context,
445 v,
446 primary,
447 resolve_type,
448 )
449 .await?;
450 Ok((k, single_pattern_mapping))
451 })
452 .try_join()
453 .await?
454 .into_iter()
455 .collect();
456 Ok(PatternMapping::Map(map).cell())
457 }
458 }
459 }
460}