1use std::{borrow::Cow, collections::HashSet};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
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;
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 super::util::{request_to_string, throw_module_not_found_expr};
31use crate::{
32 references::util::throw_module_not_found_error_expr,
33 runtime_functions::{
34 TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_IMPORT,
35 TURBOPACK_MODULE_CONTEXT, TURBOPACK_REQUIRE,
36 },
37 utils::module_id_to_lit,
38};
39
40#[derive(PartialEq, Eq, ValueDebugFormat, TraceRawVcs, Serialize, Deserialize, NonLocalValue)]
41pub(crate) enum SinglePatternMapping {
42 Invalid,
44 Unresolvable(String),
46 Ignored,
48 Module(ModuleId),
55 ModuleLoader(ModuleId),
64 External(RcStr, ExternalType),
66}
67
68#[turbo_tasks::value]
72pub(crate) enum PatternMapping {
73 Single(SinglePatternMapping),
80 Map(FxIndexMap<String, SinglePatternMapping>),
87}
88
89#[derive(
90 Copy,
91 Clone,
92 Debug,
93 Eq,
94 PartialEq,
95 Hash,
96 Serialize,
97 Deserialize,
98 TraceRawVcs,
99 TaskInput,
100 NonLocalValue,
101)]
102pub(crate) enum ResolveType {
103 AsyncChunkLoader,
104 ChunkItem,
105}
106
107impl SinglePatternMapping {
108 pub fn create_id(&self, key_expr: Cow<'_, Expr>) -> Expr {
109 match self {
110 Self::Invalid => {
111 quote!(
112 "(() => { throw new Error('could not resolve \"' + $arg + '\" into a module'); })()" as Expr,
113 arg: Expr = key_expr.into_owned()
114 )
115 }
116 Self::Unresolvable(request) => throw_module_not_found_expr(request),
117 Self::Ignored => {
118 quote!("undefined" as Expr)
119 }
120 Self::Module(module_id) | Self::ModuleLoader(module_id) => module_id_to_lit(module_id),
121 Self::External(s, _) => Expr::Lit(Lit::Str(s.as_str().into())),
122 }
123 }
124
125 pub fn create_require(&self, key_expr: Cow<'_, Expr>) -> Expr {
126 match self {
127 Self::Invalid => self.create_id(key_expr),
128 Self::Unresolvable(request) => throw_module_not_found_expr(request),
129 Self::Ignored => quote!("{}" as Expr),
130 Self::Module(_) | Self::ModuleLoader(_) => quote!(
131 "$turbopack_require($arg)" as Expr,
132 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
133 arg: Expr = self.create_id(key_expr)
134 ),
135 Self::External(request, ExternalType::CommonJs) => quote!(
136 "$turbopack_external_require($arg, () => require($arg))" as Expr,
137 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
138 arg: Expr = request.as_str().into()
139 ),
140 Self::External(request, ty) => throw_module_not_found_error_expr(
141 request,
142 &format!("Unsupported external type {ty:?} for commonjs reference"),
143 ),
144 }
145 }
146
147 pub fn create_import(&self, key_expr: Cow<'_, Expr>, import_externals: bool) -> Expr {
148 match self {
149 Self::Invalid => {
150 let error = quote_expr!(
151 "() => { throw new Error('could not resolve \"' + $arg + '\" into a module'); }",
152 arg: Expr = key_expr.into_owned()
153 );
154 Expr::Call(CallExpr {
155 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
156 args: vec![ExprOrSpread {
157 spread: None,
158 expr: error,
159 }],
160 span: DUMMY_SP,
161 ..Default::default()
162 })
163 }
164 Self::Unresolvable(_) => self.create_id(key_expr),
165 Self::External(_, ExternalType::EcmaScriptModule) => {
166 if import_externals {
167 Expr::Call(CallExpr {
168 callee: Callee::Expr(Box::new(TURBOPACK_EXTERNAL_IMPORT.into())),
169 args: vec![ExprOrSpread {
170 spread: None,
171 expr: Box::new(key_expr.into_owned()),
172 }],
173 span: DUMMY_SP,
174 ..Default::default()
175 })
176 } else {
177 Expr::Call(CallExpr {
178 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
179 args: vec![ExprOrSpread {
180 spread: None,
181 expr: quote_expr!(
182 "() => $turbopack_external_require($arg, () => require($arg), true)",
183 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
184 arg: Expr = key_expr.into_owned()
185 ),
186 }],
187 span: DUMMY_SP,
188 ..Default::default()
189 })
190 }
191 }
192 Self::External(_, ExternalType::CommonJs | ExternalType::Url) => Expr::Call(CallExpr {
193 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
194 args: vec![ExprOrSpread {
195 spread: None,
196 expr: quote_expr!(
197 "() => $turbopack_external_require($arg, () => require($arg), true)",
198 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
199 arg: Expr = key_expr.into_owned()
200 ),
201 }],
202 span: DUMMY_SP,
203 ..Default::default()
204 }),
205 #[allow(unreachable_patterns)]
206 Self::External(request, ty) => throw_module_not_found_error_expr(
207 request,
208 &format!("Unsupported external type {ty:?} for dynamic import reference"),
209 ),
210 Self::ModuleLoader(module_id) => {
211 quote!("($turbopack_require($id))($turbopack_import)" as Expr,
212 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
213 turbopack_import: Expr = TURBOPACK_IMPORT.into(),
214 id: Expr = module_id_to_lit(module_id)
215 )
216 }
217 Self::Ignored => {
218 quote!("Promise.resolve({})" as Expr)
219 }
220 Self::Module(_) => Expr::Call(CallExpr {
221 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
222 args: vec![ExprOrSpread {
223 spread: None,
224 expr: quote_expr!(
225 "() => $turbopack_import($arg)",
226 turbopack_import: Expr = TURBOPACK_IMPORT.into(),
227 arg: Expr = self.create_id(key_expr)
228 ),
229 }],
230 span: DUMMY_SP,
231 ..Default::default()
232 }),
233 }
234 }
235}
236
237enum ImportMode {
238 Require,
239 Import { import_externals: bool },
240}
241
242fn create_context_map(
243 map: &FxIndexMap<String, SinglePatternMapping>,
244 key_expr: &Expr,
245 import_mode: ImportMode,
246) -> Expr {
247 let props = map
248 .iter()
249 .map(|(k, v)| {
250 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
251 key: PropName::Str(k.as_str().into()),
252 value: quote_expr!(
253 "{ id: () => $id, module: () => $module }",
254 id: Expr = v.create_id(Cow::Borrowed(key_expr)),
255 module: Expr = match import_mode {
256 ImportMode::Require => v.create_require(Cow::Borrowed(key_expr)),
257 ImportMode::Import { import_externals } => v.create_import(Cow::Borrowed(key_expr), import_externals),
258 },
259 ),
260 })))
261 })
262 .collect();
263
264 Expr::Object(ObjectLit {
265 span: DUMMY_SP,
266 props,
267 })
268}
269
270impl PatternMapping {
271 pub fn create_id(&self, key_expr: Expr) -> Expr {
272 match self {
273 PatternMapping::Single(pm) => pm.create_id(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).resolve($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_require(&self, key_expr: Expr) -> Expr {
287 match self {
288 PatternMapping::Single(pm) => pm.create_require(Cow::Owned(key_expr)),
289 PatternMapping::Map(map) => {
290 let map = create_context_map(map, &key_expr, ImportMode::Require);
291
292 quote!("$turbopack_module_context($map)($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 pub fn create_import(&self, key_expr: Expr, import_externals: bool) -> Expr {
302 match self {
303 PatternMapping::Single(pm) => pm.create_import(Cow::Owned(key_expr), import_externals),
304 PatternMapping::Map(map) => {
305 let map =
306 create_context_map(map, &key_expr, ImportMode::Import { import_externals });
307
308 quote!("$turbopack_module_context($map).import($key)" as Expr,
309 turbopack_module_context: Expr = TURBOPACK_MODULE_CONTEXT.into(),
310 map: Expr = map,
311 key: Expr = key_expr
312 )
313 }
314 }
315 }
316}
317
318async fn to_single_pattern_mapping(
319 origin: Vc<Box<dyn ResolveOrigin>>,
320 chunking_context: Vc<Box<dyn ChunkingContext>>,
321 resolve_item: &ModuleResolveResultItem,
322 resolve_type: ResolveType,
323) -> Result<SinglePatternMapping> {
324 let module = match resolve_item {
325 ModuleResolveResultItem::Module(module) => *module,
326 ModuleResolveResultItem::External { name: s, ty, .. } => {
327 return Ok(SinglePatternMapping::External(s.clone(), *ty));
328 }
329 ModuleResolveResultItem::Ignore => return Ok(SinglePatternMapping::Ignored),
330 ModuleResolveResultItem::Unknown(source) => {
331 emit_unknown_module_type_error(**source).await?;
332 return Ok(SinglePatternMapping::Unresolvable(
333 "unknown module type".to_string(),
334 ));
335 }
336 ModuleResolveResultItem::Error(str) => {
337 return Ok(SinglePatternMapping::Unresolvable(str.await?.to_string()));
338 }
339 ModuleResolveResultItem::OutputAsset(_)
340 | ModuleResolveResultItem::Empty
341 | ModuleResolveResultItem::Custom(_) => {
342 CodeGenerationIssue {
344 severity: IssueSeverity::Bug.resolved_cell(),
345 title: StyledString::Text(
346 "pattern mapping is not implemented for this result".into(),
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.origin_path().to_resolved().await?,
358 }
359 .resolved_cell()
360 .emit();
361 return Ok(SinglePatternMapping::Invalid);
362 }
363 };
364 if let Some(chunkable) = ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(module) {
365 match resolve_type {
366 ResolveType::AsyncChunkLoader => {
367 let loader_id = chunking_context.async_loader_chunk_item_id(*chunkable);
368 return Ok(SinglePatternMapping::ModuleLoader(loader_id.owned().await?));
369 }
370 ResolveType::ChunkItem => {
371 let item_id = chunkable.chunk_item_id(chunking_context);
372 return Ok(SinglePatternMapping::Module(item_id.owned().await?));
373 }
374 }
375 }
376 CodeGenerationIssue {
377 severity: IssueSeverity::Bug.resolved_cell(),
378 title: StyledString::Text("non-ecmascript placeable asset".into()).resolved_cell(),
379 message: StyledString::Text(
380 "asset is not placeable in ESM chunks, so it doesn't have a module id".into(),
381 )
382 .resolved_cell(),
383 path: origin.origin_path().to_resolved().await?,
384 }
385 .resolved_cell()
386 .emit();
387 Ok(SinglePatternMapping::Invalid)
388}
389
390#[turbo_tasks::value_impl]
391impl PatternMapping {
392 #[turbo_tasks::function]
396 pub async fn resolve_request(
397 request: Vc<Request>,
398 origin: Vc<Box<dyn ResolveOrigin>>,
399 chunking_context: Vc<Box<dyn ChunkingContext>>,
400 resolve_result: Vc<ModuleResolveResult>,
401 resolve_type: ResolveType,
402 ) -> Result<Vc<PatternMapping>> {
403 let result = resolve_result.await?;
404 match result.primary.len() {
405 0 => Ok(PatternMapping::Single(SinglePatternMapping::Unresolvable(
406 request_to_string(request).await?.to_string(),
407 ))
408 .cell()),
409 1 if !request.request_pattern().await?.has_dynamic_parts() => {
410 let resolve_item = &result.primary.first().unwrap().1;
411 let single_pattern_mapping =
412 to_single_pattern_mapping(origin, chunking_context, resolve_item, resolve_type)
413 .await?;
414 Ok(PatternMapping::Single(single_pattern_mapping).cell())
415 }
416 _ => {
417 let mut set = HashSet::new();
418 let map = result
419 .primary
420 .iter()
421 .filter_map(|(k, v)| {
422 let request = k.request.as_ref()?;
423 set.insert(request).then(|| (request.to_string(), v))
424 })
425 .map(|(k, v)| async move {
426 let single_pattern_mapping =
427 to_single_pattern_mapping(origin, chunking_context, v, resolve_type)
428 .await?;
429 Ok((k, single_pattern_mapping))
430 })
431 .try_join()
432 .await?
433 .into_iter()
434 .collect();
435 Ok(PatternMapping::Map(map).cell())
436 }
437 }
438 }
439}