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