Skip to main content

turbopack_ecmascript/references/esm/
base.rs

1use anyhow::{Result, bail};
2use async_trait::async_trait;
3use either::Either;
4use strsim::jaro;
5use swc_core::{
6    common::{BytePos, DUMMY_SP, Span, SyntaxContext, source_map::PURE_SP},
7    ecma::ast::{
8        ComputedPropName, Decl, Expr, ExprStmt, Ident, Lit, MemberExpr, MemberProp, Number,
9        SeqExpr, Stmt, Str,
10    },
11    quote,
12};
13use turbo_rcstr::{RcStr, rcstr};
14use turbo_tasks::{ResolvedVc, ValueToString, Vc, turbobail};
15use turbo_tasks_fs::FileSystemPath;
16use turbopack_core::{
17    chunk::{ChunkingContext, ChunkingType, ModuleChunkItemIdExt},
18    issue::{Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, StyledString},
19    loader::ResolvedWebpackLoaderItem,
20    module::{Module, ModuleSideEffects},
21    module_graph::binding_usage_info::ModuleExportUsageInfo,
22    reference::ModuleReference,
23    reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
24    resolve::{
25        BindingUsage, ExportUsage, ExternalType, ImportUsage, ModulePart, ModuleResolveResult,
26        ModuleResolveResultItem, RequestKey, ResolveErrorMode,
27        origin::{ResolveOrigin, ResolveOriginExt},
28        parse::Request,
29        resolve,
30    },
31    source::Source,
32};
33use turbopack_resolve::ecmascript::esm_resolve;
34
35use crate::{
36    EcmascriptModuleAsset, ScopeHoistingContext, TreeShakingMode,
37    analyzer::imports::ImportAnnotations,
38    chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
39    code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
40    export::Liveness,
41    magic_identifier,
42    references::{
43        esm::{
44            EsmExport,
45            export::{all_known_export_names, is_export_missing},
46        },
47        util::{SpecifiedChunkingType, throw_module_not_found_expr},
48    },
49    runtime_functions::{TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_IMPORT},
50    tree_shake::{TURBOPACK_PART_IMPORT_SOURCE, part::module::EcmascriptModulePartAsset},
51    utils::module_id_to_lit,
52};
53
54#[turbo_tasks::value]
55pub enum ReferencedAsset {
56    Some(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>),
57    External(RcStr, ExternalType),
58    None,
59    Unresolvable,
60}
61
62#[derive(Debug)]
63pub enum ReferencedAssetIdent {
64    /// The given export (or namespace) is a local binding in the current scope hoisting group.
65    LocalBinding {
66        ident: RcStr,
67        ctxt: SyntaxContext,
68        liveness: Liveness,
69    },
70    /// The given export (or namespace) should be imported and will be assigned to a new variable.
71    Module {
72        namespace_ident: String,
73        ctxt: Option<SyntaxContext>,
74        export: Option<RcStr>,
75    },
76}
77
78impl ReferencedAssetIdent {
79    pub fn into_module_namespace_ident(self) -> Option<(String, Option<SyntaxContext>)> {
80        match self {
81            ReferencedAssetIdent::Module {
82                namespace_ident,
83                ctxt,
84                ..
85            } => Some((namespace_ident, ctxt)),
86            ReferencedAssetIdent::LocalBinding { .. } => None,
87        }
88    }
89
90    pub fn as_expr_individual(&self, span: Span) -> Either<Ident, MemberExpr> {
91        match self {
92            ReferencedAssetIdent::LocalBinding {
93                ident,
94                ctxt,
95                liveness: _,
96            } => Either::Left(Ident::new(ident.as_str().into(), span, *ctxt)),
97            ReferencedAssetIdent::Module {
98                namespace_ident,
99                ctxt,
100                export,
101            } => {
102                if let Some(export) = export {
103                    Either::Right(MemberExpr {
104                        span,
105                        obj: Box::new(Expr::Ident(Ident::new(
106                            namespace_ident.as_str().into(),
107                            DUMMY_SP,
108                            ctxt.unwrap_or_default(),
109                        ))),
110                        prop: MemberProp::Computed(ComputedPropName {
111                            span: DUMMY_SP,
112                            expr: Box::new(Expr::Lit(Lit::Str(Str {
113                                span: DUMMY_SP,
114                                value: export.as_str().into(),
115                                raw: None,
116                            }))),
117                        }),
118                    })
119                } else {
120                    Either::Left(Ident::new(
121                        namespace_ident.as_str().into(),
122                        span,
123                        ctxt.unwrap_or_default(),
124                    ))
125                }
126            }
127        }
128    }
129    pub fn as_expr(&self, span: Span, is_callee: bool) -> Expr {
130        match self.as_expr_individual(span) {
131            Either::Left(ident) => ident.into(),
132            Either::Right(member) => {
133                if is_callee {
134                    Expr::Seq(SeqExpr {
135                        exprs: vec![
136                            Box::new(Expr::Lit(Lit::Num(Number {
137                                span: DUMMY_SP,
138                                value: 0.0,
139                                raw: None,
140                            }))),
141                            Box::new(member.into()),
142                        ],
143                        span: DUMMY_SP,
144                    })
145                } else {
146                    member.into()
147                }
148            }
149        }
150    }
151}
152
153impl ReferencedAsset {
154    pub async fn get_ident(
155        &self,
156        chunking_context: Vc<Box<dyn ChunkingContext>>,
157        export: Option<RcStr>,
158        scope_hoisting_context: ScopeHoistingContext<'_>,
159    ) -> Result<Option<ReferencedAssetIdent>> {
160        self.get_ident_inner(chunking_context, export, scope_hoisting_context, None)
161            .await
162    }
163
164    async fn get_ident_inner(
165        &self,
166        chunking_context: Vc<Box<dyn ChunkingContext>>,
167        export: Option<RcStr>,
168        scope_hoisting_context: ScopeHoistingContext<'_>,
169        initial: Option<&ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
170    ) -> Result<Option<ReferencedAssetIdent>> {
171        Ok(match self {
172            ReferencedAsset::Some(asset) => {
173                if let Some(ctxt) = scope_hoisting_context.get_module_syntax_context(*asset)
174                    && let Some(export) = &export
175                    && let EcmascriptExports::EsmExports(exports) = *asset.get_exports().await?
176                {
177                    let exports = exports.expand_exports(ModuleExportUsageInfo::all()).await?;
178                    let esm_export = exports.exports.get(export);
179                    match esm_export {
180                        Some(EsmExport::LocalBinding(_name, liveness)) => {
181                            // A local binding in a module that is merged in the same group. Use the
182                            // export name as identifier, it will be replaced with the actual
183                            // variable name during AST merging.
184                            return Ok(Some(ReferencedAssetIdent::LocalBinding {
185                                ident: export.clone(),
186                                ctxt,
187                                liveness: *liveness,
188                            }));
189                        }
190                        Some(b @ EsmExport::ImportedBinding(esm_ref, _, _))
191                        | Some(b @ EsmExport::ImportedNamespace(esm_ref)) => {
192                            let imported = if let EsmExport::ImportedBinding(_, export, _) = b {
193                                Some(export.clone())
194                            } else {
195                                None
196                            };
197
198                            let referenced_asset =
199                                ReferencedAsset::from_resolve_result(esm_ref.resolve_reference())
200                                    .await?;
201
202                            if let Some(&initial) = initial
203                                && *referenced_asset == ReferencedAsset::Some(initial)
204                            {
205                                // `initial` reexports from `asset` reexports from
206                                // `referenced_asset` (which is `initial`)
207                                CircularReExport {
208                                    export: export.clone(),
209                                    import: imported.clone(),
210                                    module: *asset,
211                                    module_cycle: initial,
212                                }
213                                .resolved_cell()
214                                .emit();
215                                return Ok(None);
216                            }
217
218                            // If the target module is still in the same group, we can
219                            // refer it locally, otherwise it will be imported
220                            return Ok(
221                                match Box::pin(referenced_asset.get_ident_inner(
222                                    chunking_context,
223                                    imported,
224                                    scope_hoisting_context,
225                                    Some(asset),
226                                ))
227                                .await?
228                                {
229                                    Some(ReferencedAssetIdent::Module {
230                                        namespace_ident,
231                                        // Overwrite the context. This import isn't
232                                        // inserted in the module that uses the import,
233                                        // but in the module containing the reexport
234                                        ctxt: None,
235                                        export,
236                                    }) => Some(ReferencedAssetIdent::Module {
237                                        namespace_ident,
238                                        ctxt: Some(ctxt),
239                                        export,
240                                    }),
241                                    ident => ident,
242                                },
243                            );
244                        }
245                        Some(EsmExport::Error) | None => {
246                            // Export not found, either there was already an error, or
247                            // this is some dynamic (CJS) (re)export situation.
248                        }
249                    }
250                }
251
252                Some(ReferencedAssetIdent::Module {
253                    namespace_ident: Self::get_ident_from_placeable(asset, chunking_context)
254                        .await?,
255                    ctxt: None,
256                    export,
257                })
258            }
259            ReferencedAsset::External(request, ty) => Some(ReferencedAssetIdent::Module {
260                namespace_ident: magic_identifier::mangle(&format!("{ty} external {request}")),
261                ctxt: None,
262                export,
263            }),
264            ReferencedAsset::None | ReferencedAsset::Unresolvable => None,
265        })
266    }
267
268    pub(crate) async fn get_ident_from_placeable(
269        asset: &Vc<Box<dyn EcmascriptChunkPlaceable>>,
270        chunking_context: Vc<Box<dyn ChunkingContext>>,
271    ) -> Result<String> {
272        let id = asset.chunk_item_id(chunking_context).await?;
273        // There are a number of places in `next` that match on this prefix.
274        // See `packages/next/src/shared/lib/magic-identifier.ts`
275        Ok(magic_identifier::mangle(&format!("imported module {id}")))
276    }
277}
278
279#[turbo_tasks::value_impl]
280impl ReferencedAsset {
281    #[turbo_tasks::function]
282    pub async fn from_resolve_result(resolve_result: Vc<ModuleResolveResult>) -> Result<Vc<Self>> {
283        // TODO handle multiple keyed results
284        let result = resolve_result.await?;
285        if result.is_unresolvable_ref() {
286            return Ok(ReferencedAsset::Unresolvable.cell());
287        }
288        for (_, result) in result.primary.iter() {
289            match result {
290                ModuleResolveResultItem::External {
291                    name: request, ty, ..
292                } => {
293                    return Ok(ReferencedAsset::External(request.clone(), *ty).cell());
294                }
295                &ModuleResolveResultItem::Module(module) => {
296                    if let Some(placeable) =
297                        ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(module)
298                    {
299                        return Ok(ReferencedAsset::Some(placeable).cell());
300                    }
301                }
302                // TODO ignore should probably be handled differently
303                _ => {}
304            }
305        }
306        Ok(ReferencedAsset::None.cell())
307    }
308}
309
310#[turbo_tasks::value(transparent)]
311pub struct EsmAssetReferences(Vec<ResolvedVc<EsmAssetReference>>);
312
313#[turbo_tasks::value_impl]
314impl EsmAssetReferences {
315    #[turbo_tasks::function]
316    pub fn empty() -> Vc<Self> {
317        Vc::cell(Vec::new())
318    }
319}
320
321#[turbo_tasks::value(shared)]
322#[derive(Hash, Debug, ValueToString)]
323#[value_to_string("import {request}")]
324pub struct EsmAssetReference {
325    pub module: ResolvedVc<EcmascriptModuleAsset>,
326    pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
327    // Request is a string to avoid eagerly parsing into a `Request` VC
328    pub request: RcStr,
329    pub annotations: Option<ImportAnnotations>,
330    pub issue_source: IssueSource,
331    pub export_name: Option<ModulePart>,
332    pub import_usage: ImportUsage,
333    pub import_externals: bool,
334    pub tree_shaking_mode: Option<TreeShakingMode>,
335    pub is_pure_import: bool,
336}
337
338impl EsmAssetReference {
339    fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
340        if let Some(transition) = self.annotations.as_ref().and_then(|a| a.transition()) {
341            self.origin.with_transition(transition.into())
342        } else {
343            *self.origin
344        }
345    }
346}
347
348impl EsmAssetReference {
349    pub fn new(
350        module: ResolvedVc<EcmascriptModuleAsset>,
351        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
352        request: RcStr,
353        issue_source: IssueSource,
354        annotations: Option<ImportAnnotations>,
355        export_name: Option<ModulePart>,
356        import_usage: ImportUsage,
357        import_externals: bool,
358        tree_shaking_mode: Option<TreeShakingMode>,
359    ) -> Self {
360        EsmAssetReference {
361            module,
362            origin,
363            request,
364            issue_source,
365            annotations,
366            export_name,
367            import_usage,
368            import_externals,
369            tree_shaking_mode,
370            is_pure_import: false,
371        }
372    }
373
374    pub fn new_pure(
375        module: ResolvedVc<EcmascriptModuleAsset>,
376        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
377        request: RcStr,
378        issue_source: IssueSource,
379        annotations: Option<ImportAnnotations>,
380        export_name: Option<ModulePart>,
381        import_usage: ImportUsage,
382        import_externals: bool,
383        tree_shaking_mode: Option<TreeShakingMode>,
384    ) -> Self {
385        EsmAssetReference {
386            module,
387            origin,
388            request,
389            issue_source,
390            annotations,
391            export_name,
392            import_usage,
393            import_externals,
394            tree_shaking_mode,
395            is_pure_import: true,
396        }
397    }
398    pub(crate) fn get_referenced_asset(self: Vc<Self>) -> Vc<ReferencedAsset> {
399        ReferencedAsset::from_resolve_result(self.resolve_reference())
400    }
401}
402
403#[turbo_tasks::value_impl]
404impl ModuleReference for EsmAssetReference {
405    #[turbo_tasks::function]
406    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
407        let ty = if let Some(loader) = self.annotations.as_ref().and_then(|a| a.turbopack_loader())
408        {
409            // Resolve the loader path relative to the importing file
410            let origin = self.get_origin();
411            let origin_path = origin.origin_path().await?;
412            let loader_request = Request::parse(loader.loader.clone().into());
413            let resolved = resolve(
414                origin_path.parent(),
415                ReferenceType::Loader,
416                loader_request,
417                origin.resolve_options(),
418            );
419            let loader_fs_path = if let Some(source) = *resolved.first_source().await? {
420                (*source.ident().path().await?).clone()
421            } else {
422                bail!("Unable to resolve turbopackLoader '{}'", loader.loader);
423            };
424
425            EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
426                loader: ResolvedWebpackLoaderItem {
427                    loader: loader_fs_path,
428                    options: loader.options.clone(),
429                },
430                rename_as: self
431                    .annotations
432                    .as_ref()
433                    .and_then(|a| a.turbopack_rename_as())
434                    .cloned(),
435                module_type: self
436                    .annotations
437                    .as_ref()
438                    .and_then(|a| a.turbopack_module_type())
439                    .cloned(),
440            }
441        } else if let Some(module_type) = self.annotations.as_ref().and_then(|a| a.module_type()) {
442            EcmaScriptModulesReferenceSubType::ImportWithType(RcStr::from(
443                &*module_type.to_string_lossy(),
444            ))
445        } else if let Some(part) = &self.export_name {
446            EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
447        } else {
448            EcmaScriptModulesReferenceSubType::Import
449        };
450
451        let request = Request::parse(self.request.clone().into());
452
453        if let Some(TreeShakingMode::ModuleFragments) = self.tree_shaking_mode {
454            if let Some(ModulePart::Evaluation) = &self.export_name
455                && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
456            {
457                return Ok(ModuleResolveResult {
458                    primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
459                    affecting_sources: Default::default(),
460                }
461                .cell());
462            }
463
464            if let Request::Module { module, .. } = &*request.await?
465                && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
466            {
467                if let Some(part) = &self.export_name {
468                    return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
469                        EcmascriptModulePartAsset::select_part(*self.module, part.clone())
470                            .to_resolved()
471                            .await?,
472                    )));
473                }
474                bail!("export_name is required for part import")
475            }
476        }
477
478        let result = esm_resolve(
479            self.get_origin(),
480            request,
481            ty,
482            ResolveErrorMode::Error,
483            Some(self.issue_source),
484        )
485        .await?;
486
487        if let Some(ModulePart::Export(export_name)) = &self.export_name {
488            for &module in result.primary_modules().await? {
489                if let Some(module) = ResolvedVc::try_downcast(module)
490                    && *is_export_missing(*module, export_name.clone()).await?
491                {
492                    InvalidExport {
493                        export: export_name.clone(),
494                        module,
495                        source: self.issue_source,
496                    }
497                    .resolved_cell()
498                    .emit();
499                }
500            }
501        }
502
503        Ok(result)
504    }
505
506    fn chunking_type(&self) -> Option<ChunkingType> {
507        self.annotations
508            .as_ref()
509            .and_then(|a| a.chunking_type())
510            .map_or_else(
511                || {
512                    Some(ChunkingType::Parallel {
513                        inherit_async: true,
514                        hoisted: true,
515                    })
516                },
517                |c| c.as_chunking_type(true, true),
518            )
519    }
520
521    fn binding_usage(&self) -> BindingUsage {
522        BindingUsage {
523            import: self.import_usage.clone(),
524            export: match &self.export_name {
525                Some(ModulePart::Export(export_name)) => ExportUsage::Named(export_name.clone()),
526                Some(ModulePart::Evaluation) => ExportUsage::Evaluation,
527                _ => ExportUsage::All,
528            },
529        }
530    }
531}
532
533impl EsmAssetReference {
534    pub async fn code_generation(
535        self: ResolvedVc<Self>,
536        chunking_context: Vc<Box<dyn ChunkingContext>>,
537        scope_hoisting_context: ScopeHoistingContext<'_>,
538    ) -> Result<CodeGeneration> {
539        let this = &*self.await?;
540
541        if chunking_context
542            .unused_references()
543            .contains_key(&ResolvedVc::upcast(self))
544            .await?
545        {
546            return Ok(CodeGeneration::empty());
547        }
548
549        // only chunked references can be imported
550        if this
551            .annotations
552            .as_ref()
553            .and_then(|a| a.chunking_type())
554            .is_none_or(|v| v != SpecifiedChunkingType::None)
555        {
556            let import_externals = this.import_externals;
557            let referenced_asset = self.get_referenced_asset().await?;
558
559            match &*referenced_asset {
560                ReferencedAsset::Unresolvable => {
561                    // Insert code that throws immediately at time of import if a request is
562                    // unresolvable
563                    let request = &this.request;
564                    let stmt = Stmt::Expr(ExprStmt {
565                        expr: Box::new(throw_module_not_found_expr(request)),
566                        span: DUMMY_SP,
567                    });
568                    return Ok(CodeGeneration::hoisted_stmt(
569                        format!("throw {request}").into(),
570                        stmt,
571                    ));
572                }
573                ReferencedAsset::None => {}
574                _ => {
575                    let mut result = vec![];
576
577                    let merged_index = if let ReferencedAsset::Some(asset) = &*referenced_asset {
578                        scope_hoisting_context.get_module_index(*asset)
579                    } else {
580                        None
581                    };
582
583                    if let Some(merged_index) = merged_index {
584                        // Insert a placeholder to inline the merged module at the right place
585                        // relative to the other references (so to keep reference order).
586                        result.push(CodeGenerationHoistedStmt::new(
587                            format!("hoisted {merged_index}").into(),
588                            quote!(
589                                "__turbopack_merged_esm__($id);" as Stmt,
590                                id: Expr = Lit::Num(merged_index.into()).into(),
591                            ),
592                        ));
593                    }
594
595                    if merged_index.is_some()
596                        && matches!(this.export_name, Some(ModulePart::Evaluation))
597                    {
598                        // No need to import, the module was already executed and is available in
599                        // the same scope hoisting group (unless it's a
600                        // namespace import)
601                    } else {
602                        let ident = referenced_asset
603                            .get_ident(
604                                chunking_context,
605                                this.export_name.as_ref().and_then(|e| match e {
606                                    ModulePart::Export(export_name) => Some(export_name.clone()),
607                                    _ => None,
608                                }),
609                                scope_hoisting_context,
610                            )
611                            .await?;
612                        match ident {
613                            Some(ReferencedAssetIdent::LocalBinding { .. }) => {
614                                // no need to import
615                            }
616                            Some(ident @ ReferencedAssetIdent::Module { .. }) => {
617                                let span = this
618                                    .issue_source
619                                    .to_swc_offsets()
620                                    .await?
621                                    .map_or(DUMMY_SP, |(start, end)| {
622                                        Span::new(BytePos(start), BytePos(end))
623                                    });
624                                match &*referenced_asset {
625                                    ReferencedAsset::Unresolvable => {
626                                        unreachable!();
627                                    }
628                                    ReferencedAsset::Some(asset) => {
629                                        let id = asset.chunk_item_id(chunking_context).await?;
630                                        let (sym, ctxt) =
631                                            ident.into_module_namespace_ident().unwrap();
632                                        let name = Ident::new(
633                                            sym.into(),
634                                            DUMMY_SP,
635                                            ctxt.unwrap_or_default(),
636                                        );
637                                        let mut call_expr = quote!(
638                                            "$turbopack_import($id)" as Expr,
639                                            turbopack_import: Expr = TURBOPACK_IMPORT.into(),
640                                            id: Expr = module_id_to_lit(&id),
641                                        );
642                                        if this.is_pure_import {
643                                            call_expr.set_span(PURE_SP);
644                                        }
645                                        result.push(CodeGenerationHoistedStmt::new(
646                                            id.to_string().into(),
647                                            var_decl_with_span(
648                                                quote!(
649                                                    "var $name = $call;" as Stmt,
650                                                    name = name,
651                                                    call: Expr = call_expr
652                                                ),
653                                                span,
654                                            ),
655                                        ));
656                                    }
657                                    ReferencedAsset::External(
658                                        request,
659                                        ExternalType::EcmaScriptModule,
660                                    ) => {
661                                        if !*chunking_context
662                                            .environment()
663                                            .supports_esm_externals()
664                                            .await?
665                                        {
666                                            turbobail!(
667                                                "the chunking context ({}) does not support \
668                                                 external modules (esm request: {request})",
669                                                chunking_context.name()
670                                            );
671                                        }
672                                        let (sym, ctxt) =
673                                            ident.into_module_namespace_ident().unwrap();
674                                        let name = Ident::new(
675                                            sym.into(),
676                                            DUMMY_SP,
677                                            ctxt.unwrap_or_default(),
678                                        );
679                                        let mut call_expr = if import_externals {
680                                            quote!(
681                                                "$turbopack_external_import($id)" as Expr,
682                                                turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
683                                                id: Expr = Expr::Lit(request.clone().to_string().into())
684                                            )
685                                        } else {
686                                            quote!(
687                                                "$turbopack_external_require($id, () => require($id), true)" as Expr,
688                                                turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
689                                                id: Expr = Expr::Lit(request.clone().to_string().into())
690                                            )
691                                        };
692                                        if this.is_pure_import {
693                                            call_expr.set_span(PURE_SP);
694                                        }
695                                        result.push(CodeGenerationHoistedStmt::new(
696                                            name.sym.as_str().into(),
697                                            var_decl_with_span(
698                                                quote!(
699                                                    "var $name = $call;" as Stmt,
700                                                    name = name,
701                                                    call: Expr = call_expr,
702                                                ),
703                                                span,
704                                            ),
705                                        ));
706                                    }
707                                    ReferencedAsset::External(
708                                        request,
709                                        ExternalType::CommonJs | ExternalType::Url,
710                                    ) => {
711                                        if !*chunking_context
712                                            .environment()
713                                            .supports_commonjs_externals()
714                                            .await?
715                                        {
716                                            turbobail!(
717                                                "the chunking context ({}) does not support \
718                                                 external modules (request: {request})",
719                                                chunking_context.name()
720                                            );
721                                        }
722                                        let (sym, ctxt) =
723                                            ident.into_module_namespace_ident().unwrap();
724                                        let name = Ident::new(
725                                            sym.into(),
726                                            DUMMY_SP,
727                                            ctxt.unwrap_or_default(),
728                                        );
729                                        let mut call_expr = quote!(
730                                            "$turbopack_external_require($id, () => require($id), true)" as Expr,
731                                            turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
732                                            id: Expr = Expr::Lit(request.clone().to_string().into())
733                                        );
734                                        if this.is_pure_import {
735                                            call_expr.set_span(PURE_SP);
736                                        }
737                                        result.push(CodeGenerationHoistedStmt::new(
738                                            name.sym.as_str().into(),
739                                            var_decl_with_span(
740                                                quote!(
741                                                    "var $name = $call;" as Stmt,
742                                                    name = name,
743                                                    call: Expr = call_expr,
744                                                ),
745                                                span,
746                                            ),
747                                        ));
748                                    }
749                                    // fallback in case we introduce a new `ExternalType`
750                                    #[allow(unreachable_patterns)]
751                                    ReferencedAsset::External(request, ty) => {
752                                        bail!(
753                                            "Unsupported external type {:?} for ESM reference \
754                                             with request: {:?}",
755                                            ty,
756                                            request
757                                        )
758                                    }
759                                    ReferencedAsset::None => {}
760                                };
761                            }
762                            None => {
763                                // Nothing to import.
764                            }
765                        }
766                    }
767                    return Ok(CodeGeneration::hoisted_stmts(result));
768                }
769            }
770        };
771
772        Ok(CodeGeneration::empty())
773    }
774}
775
776fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
777    match &mut decl {
778        Stmt::Decl(Decl::Var(decl)) => decl.span = span,
779        _ => panic!("Expected Stmt::Decl::Var"),
780    };
781    decl
782}
783
784#[turbo_tasks::value(shared)]
785pub struct InvalidExport {
786    export: RcStr,
787    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
788    source: IssueSource,
789}
790
791#[async_trait]
792#[turbo_tasks::value_impl]
793impl Issue for InvalidExport {
794    fn severity(&self) -> IssueSeverity {
795        IssueSeverity::Error
796    }
797
798    async fn title(&self) -> Result<StyledString> {
799        Ok(StyledString::Line(vec![
800            StyledString::Text(rcstr!("Export ")),
801            StyledString::Code(self.export.clone()),
802            StyledString::Text(rcstr!(" doesn't exist in target module")),
803        ]))
804    }
805
806    fn stage(&self) -> IssueStage {
807        IssueStage::Bindings
808    }
809
810    async fn file_path(&self) -> Result<FileSystemPath> {
811        self.source.file_path().owned().await
812    }
813
814    async fn description(&self) -> Result<Option<StyledString>> {
815        let export_names = all_known_export_names(*self.module).await?;
816        let did_you_mean = export_names
817            .iter()
818            .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
819            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
820            .map(|(s, _)| s);
821        Ok(Some(StyledString::Stack(vec![
822            StyledString::Line(vec![
823                StyledString::Text(rcstr!("The export ")),
824                StyledString::Code(self.export.clone()),
825                StyledString::Text(rcstr!(" was not found in module ")),
826                StyledString::Strong(self.module.ident().to_string().owned().await?),
827                StyledString::Text(rcstr!(".")),
828            ]),
829            if let Some(did_you_mean) = did_you_mean {
830                StyledString::Line(vec![
831                    StyledString::Text(rcstr!("Did you mean to import ")),
832                    StyledString::Code(did_you_mean.clone()),
833                    StyledString::Text(rcstr!("?")),
834                ])
835            } else {
836                StyledString::Strong(rcstr!("The module has no exports at all."))
837            },
838            StyledString::Text(
839                "All exports of the module are statically known (It doesn't have dynamic \
840                 exports). So it's known statically that the requested export doesn't exist."
841                    .into(),
842            ),
843        ])))
844    }
845
846    async fn detail(&self) -> Result<Option<StyledString>> {
847        let export_names = all_known_export_names(*self.module).await?;
848        Ok(Some(StyledString::Line(vec![
849            StyledString::Text(rcstr!("These are the exports of the module:\n")),
850            StyledString::Code(
851                export_names
852                    .iter()
853                    .map(|s| s.as_str())
854                    .intersperse(", ")
855                    .collect::<String>()
856                    .into(),
857            ),
858        ])))
859    }
860
861    fn source(&self) -> Option<IssueSource> {
862        Some(self.source)
863    }
864}
865
866#[turbo_tasks::value(shared)]
867pub struct CircularReExport {
868    export: RcStr,
869    import: Option<RcStr>,
870    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
871    module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
872}
873
874#[async_trait]
875#[turbo_tasks::value_impl]
876impl Issue for CircularReExport {
877    fn severity(&self) -> IssueSeverity {
878        IssueSeverity::Error
879    }
880
881    async fn title(&self) -> Result<StyledString> {
882        Ok(StyledString::Line(vec![
883            StyledString::Text(rcstr!("Export ")),
884            StyledString::Code(self.export.clone()),
885            StyledString::Text(rcstr!(" is a circular re-export")),
886        ]))
887    }
888
889    fn stage(&self) -> IssueStage {
890        IssueStage::Bindings
891    }
892
893    async fn file_path(&self) -> Result<FileSystemPath> {
894        self.module.ident().path().owned().await
895    }
896
897    async fn description(&self) -> Result<Option<StyledString>> {
898        Ok(Some(StyledString::Stack(vec![
899            StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
900            StyledString::Line(vec![
901                StyledString::Code(self.export.clone()),
902                StyledString::Text(rcstr!(" of module ")),
903                StyledString::Strong(self.module.ident().to_string().owned().await?),
904            ]),
905            StyledString::Line(vec![StyledString::Text(rcstr!(
906                "is a re-export of the export"
907            ))]),
908            StyledString::Line(vec![
909                StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
910                StyledString::Text(rcstr!(" of module ")),
911                StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
912                StyledString::Text(rcstr!(".")),
913            ]),
914        ])))
915    }
916
917    fn source(&self) -> Option<IssueSource> {
918        // TODO(PACK-4879): This should point at the buggy export by querying for the source
919        // location
920        None
921    }
922}