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