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    pub resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
337}
338
339impl EsmAssetReference {
340    fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
341        if let Some(transition) = self.annotations.as_ref().and_then(|a| a.transition()) {
342            self.origin.with_transition(transition.into())
343        } else {
344            *self.origin
345        }
346    }
347}
348
349impl EsmAssetReference {
350    pub fn new(
351        module: ResolvedVc<EcmascriptModuleAsset>,
352        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
353        request: RcStr,
354        issue_source: IssueSource,
355        annotations: Option<ImportAnnotations>,
356        export_name: Option<ModulePart>,
357        import_usage: ImportUsage,
358        import_externals: bool,
359        tree_shaking_mode: Option<TreeShakingMode>,
360        resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
361    ) -> Self {
362        EsmAssetReference {
363            module,
364            origin,
365            request,
366            issue_source,
367            annotations,
368            export_name,
369            import_usage,
370            import_externals,
371            tree_shaking_mode,
372            is_pure_import: false,
373            resolve_override,
374        }
375    }
376
377    pub fn new_pure(
378        module: ResolvedVc<EcmascriptModuleAsset>,
379        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
380        request: RcStr,
381        issue_source: IssueSource,
382        annotations: Option<ImportAnnotations>,
383        export_name: Option<ModulePart>,
384        import_usage: ImportUsage,
385        import_externals: bool,
386        tree_shaking_mode: Option<TreeShakingMode>,
387        resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
388    ) -> Self {
389        EsmAssetReference {
390            module,
391            origin,
392            request,
393            issue_source,
394            annotations,
395            export_name,
396            import_usage,
397            import_externals,
398            tree_shaking_mode,
399            is_pure_import: true,
400            resolve_override,
401        }
402    }
403    pub(crate) fn get_referenced_asset(self: Vc<Self>) -> Vc<ReferencedAsset> {
404        ReferencedAsset::from_resolve_result(self.resolve_reference())
405    }
406}
407
408#[turbo_tasks::value_impl]
409impl ModuleReference for EsmAssetReference {
410    #[turbo_tasks::function]
411    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
412        if let Some(resolved) = &self.resolve_override {
413            return Ok(*ModuleResolveResult::module(*resolved));
414        }
415        let ty = if let Some(loader) = self.annotations.as_ref().and_then(|a| a.turbopack_loader())
416        {
417            // Resolve the loader path relative to the importing file
418            let origin = self.get_origin();
419            let origin_path = origin.origin_path().await?;
420            let loader_request = Request::parse(loader.loader.clone().into());
421            let resolved = resolve(
422                origin_path.parent(),
423                ReferenceType::Loader,
424                loader_request,
425                origin.resolve_options(),
426            );
427            let loader_fs_path = if let Some(source) = *resolved.first_source().await? {
428                (*source.ident().path().await?).clone()
429            } else {
430                bail!("Unable to resolve turbopackLoader '{}'", loader.loader);
431            };
432
433            EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
434                loader: ResolvedWebpackLoaderItem {
435                    loader: loader_fs_path,
436                    options: loader.options.clone(),
437                },
438                rename_as: self
439                    .annotations
440                    .as_ref()
441                    .and_then(|a| a.turbopack_rename_as())
442                    .cloned(),
443                module_type: self
444                    .annotations
445                    .as_ref()
446                    .and_then(|a| a.turbopack_module_type())
447                    .cloned(),
448            }
449        } else if let Some(module_type) = self.annotations.as_ref().and_then(|a| a.module_type()) {
450            EcmaScriptModulesReferenceSubType::ImportWithType(RcStr::from(
451                &*module_type.to_string_lossy(),
452            ))
453        } else if let Some(part) = &self.export_name {
454            EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
455        } else {
456            EcmaScriptModulesReferenceSubType::Import
457        };
458
459        let request = Request::parse(self.request.clone().into());
460
461        if let Some(TreeShakingMode::ModuleFragments) = self.tree_shaking_mode {
462            if let Some(ModulePart::Evaluation) = &self.export_name
463                && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
464            {
465                return Ok(ModuleResolveResult {
466                    primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
467                    affecting_sources: Default::default(),
468                }
469                .cell());
470            }
471
472            if let Request::Module { module, .. } = &*request.await?
473                && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
474            {
475                if let Some(part) = &self.export_name {
476                    return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
477                        EcmascriptModulePartAsset::select_part(*self.module, part.clone())
478                            .to_resolved()
479                            .await?,
480                    )));
481                }
482                bail!("export_name is required for part import")
483            }
484        }
485
486        let result = esm_resolve(
487            self.get_origin(),
488            request,
489            ty,
490            ResolveErrorMode::Error,
491            Some(self.issue_source),
492        )
493        .await?;
494
495        if let Some(ModulePart::Export(export_name)) = &self.export_name {
496            for &module in result.primary_modules().await? {
497                if let Some(module) = ResolvedVc::try_downcast(module)
498                    && *is_export_missing(*module, export_name.clone()).await?
499                {
500                    InvalidExport {
501                        export: export_name.clone(),
502                        module,
503                        source: self.issue_source,
504                    }
505                    .resolved_cell()
506                    .emit();
507                }
508            }
509        }
510
511        Ok(result)
512    }
513
514    fn chunking_type(&self) -> Option<ChunkingType> {
515        self.annotations
516            .as_ref()
517            .and_then(|a| a.chunking_type())
518            .map_or_else(
519                || {
520                    Some(ChunkingType::Parallel {
521                        inherit_async: true,
522                        hoisted: true,
523                    })
524                },
525                |c| c.as_chunking_type(true, true),
526            )
527    }
528
529    fn binding_usage(&self) -> BindingUsage {
530        BindingUsage {
531            import: self.import_usage.clone(),
532            export: match &self.export_name {
533                Some(ModulePart::Export(export_name)) => ExportUsage::Named(export_name.clone()),
534                Some(ModulePart::Evaluation) => ExportUsage::Evaluation,
535                _ => ExportUsage::All,
536            },
537        }
538    }
539}
540
541impl EsmAssetReference {
542    pub async fn code_generation(
543        self: ResolvedVc<Self>,
544        chunking_context: Vc<Box<dyn ChunkingContext>>,
545        scope_hoisting_context: ScopeHoistingContext<'_>,
546    ) -> Result<CodeGeneration> {
547        let this = &*self.await?;
548
549        if chunking_context
550            .unused_references()
551            .contains_key(&ResolvedVc::upcast(self))
552            .await?
553        {
554            return Ok(CodeGeneration::empty());
555        }
556
557        // only chunked references can be imported
558        if this
559            .annotations
560            .as_ref()
561            .and_then(|a| a.chunking_type())
562            .is_none_or(|v| v != SpecifiedChunkingType::None)
563        {
564            let import_externals = this.import_externals;
565            let referenced_asset = self.get_referenced_asset().await?;
566
567            match &*referenced_asset {
568                ReferencedAsset::Unresolvable => {
569                    // Insert code that throws immediately at time of import if a request is
570                    // unresolvable
571                    let request = &this.request;
572                    let stmt = Stmt::Expr(ExprStmt {
573                        expr: Box::new(throw_module_not_found_expr(request)),
574                        span: DUMMY_SP,
575                    });
576                    return Ok(CodeGeneration::hoisted_stmt(
577                        format!("throw {request}").into(),
578                        stmt,
579                    ));
580                }
581                ReferencedAsset::None => {}
582                _ => {
583                    let mut result = vec![];
584
585                    let merged_index = if let ReferencedAsset::Some(asset) = &*referenced_asset {
586                        scope_hoisting_context.get_module_index(*asset)
587                    } else {
588                        None
589                    };
590
591                    if let Some(merged_index) = merged_index {
592                        // Insert a placeholder to inline the merged module at the right place
593                        // relative to the other references (so to keep reference order).
594                        result.push(CodeGenerationHoistedStmt::new(
595                            format!("hoisted {merged_index}").into(),
596                            quote!(
597                                "__turbopack_merged_esm__($id);" as Stmt,
598                                id: Expr = Lit::Num(merged_index.into()).into(),
599                            ),
600                        ));
601                    }
602
603                    if merged_index.is_some()
604                        && matches!(this.export_name, Some(ModulePart::Evaluation))
605                    {
606                        // No need to import, the module was already executed and is available in
607                        // the same scope hoisting group (unless it's a
608                        // namespace import)
609                    } else {
610                        let ident = referenced_asset
611                            .get_ident(
612                                chunking_context,
613                                this.export_name.as_ref().and_then(|e| match e {
614                                    ModulePart::Export(export_name) => Some(export_name.clone()),
615                                    _ => None,
616                                }),
617                                scope_hoisting_context,
618                            )
619                            .await?;
620                        match ident {
621                            Some(ReferencedAssetIdent::LocalBinding { .. }) => {
622                                // no need to import
623                            }
624                            Some(ident @ ReferencedAssetIdent::Module { .. }) => {
625                                let span = this
626                                    .issue_source
627                                    .to_swc_offsets()
628                                    .await?
629                                    .map_or(DUMMY_SP, |(start, end)| {
630                                        Span::new(BytePos(start), BytePos(end))
631                                    });
632                                match &*referenced_asset {
633                                    ReferencedAsset::Unresolvable => {
634                                        unreachable!();
635                                    }
636                                    ReferencedAsset::Some(asset) => {
637                                        let id = asset.chunk_item_id(chunking_context).await?;
638                                        let (sym, ctxt) =
639                                            ident.into_module_namespace_ident().unwrap();
640                                        let name = Ident::new(
641                                            sym.into(),
642                                            DUMMY_SP,
643                                            ctxt.unwrap_or_default(),
644                                        );
645                                        let mut call_expr = quote!(
646                                            "$turbopack_import($id)" as Expr,
647                                            turbopack_import: Expr = TURBOPACK_IMPORT.into(),
648                                            id: Expr = module_id_to_lit(&id),
649                                        );
650                                        if this.is_pure_import {
651                                            call_expr.set_span(PURE_SP);
652                                        }
653                                        result.push(CodeGenerationHoistedStmt::new(
654                                            id.to_string().into(),
655                                            var_decl_with_span(
656                                                quote!(
657                                                    "var $name = $call;" as Stmt,
658                                                    name = name,
659                                                    call: Expr = call_expr
660                                                ),
661                                                span,
662                                            ),
663                                        ));
664                                    }
665                                    ReferencedAsset::External(
666                                        request,
667                                        ExternalType::EcmaScriptModule,
668                                    ) => {
669                                        if !*chunking_context
670                                            .environment()
671                                            .supports_esm_externals()
672                                            .await?
673                                        {
674                                            turbobail!(
675                                                "the chunking context ({}) does not support \
676                                                 external modules (esm request: {request})",
677                                                chunking_context.name()
678                                            );
679                                        }
680                                        let (sym, ctxt) =
681                                            ident.into_module_namespace_ident().unwrap();
682                                        let name = Ident::new(
683                                            sym.into(),
684                                            DUMMY_SP,
685                                            ctxt.unwrap_or_default(),
686                                        );
687                                        let mut call_expr = if import_externals {
688                                            quote!(
689                                                "$turbopack_external_import($id)" as Expr,
690                                                turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
691                                                id: Expr = Expr::Lit(request.clone().to_string().into())
692                                            )
693                                        } else {
694                                            quote!(
695                                                "$turbopack_external_require($id, () => require($id), true)" as Expr,
696                                                turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
697                                                id: Expr = Expr::Lit(request.clone().to_string().into())
698                                            )
699                                        };
700                                        if this.is_pure_import {
701                                            call_expr.set_span(PURE_SP);
702                                        }
703                                        result.push(CodeGenerationHoistedStmt::new(
704                                            name.sym.as_str().into(),
705                                            var_decl_with_span(
706                                                quote!(
707                                                    "var $name = $call;" as Stmt,
708                                                    name = name,
709                                                    call: Expr = call_expr,
710                                                ),
711                                                span,
712                                            ),
713                                        ));
714                                    }
715                                    ReferencedAsset::External(
716                                        request,
717                                        ExternalType::CommonJs | ExternalType::Url,
718                                    ) => {
719                                        if !*chunking_context
720                                            .environment()
721                                            .supports_commonjs_externals()
722                                            .await?
723                                        {
724                                            turbobail!(
725                                                "the chunking context ({}) does not support \
726                                                 external modules (request: {request})",
727                                                chunking_context.name()
728                                            );
729                                        }
730                                        let (sym, ctxt) =
731                                            ident.into_module_namespace_ident().unwrap();
732                                        let name = Ident::new(
733                                            sym.into(),
734                                            DUMMY_SP,
735                                            ctxt.unwrap_or_default(),
736                                        );
737                                        let mut call_expr = quote!(
738                                            "$turbopack_external_require($id, () => require($id), true)" as Expr,
739                                            turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
740                                            id: Expr = Expr::Lit(request.clone().to_string().into())
741                                        );
742                                        if this.is_pure_import {
743                                            call_expr.set_span(PURE_SP);
744                                        }
745                                        result.push(CodeGenerationHoistedStmt::new(
746                                            name.sym.as_str().into(),
747                                            var_decl_with_span(
748                                                quote!(
749                                                    "var $name = $call;" as Stmt,
750                                                    name = name,
751                                                    call: Expr = call_expr,
752                                                ),
753                                                span,
754                                            ),
755                                        ));
756                                    }
757                                    // fallback in case we introduce a new `ExternalType`
758                                    #[allow(unreachable_patterns)]
759                                    ReferencedAsset::External(request, ty) => {
760                                        bail!(
761                                            "Unsupported external type {:?} for ESM reference \
762                                             with request: {:?}",
763                                            ty,
764                                            request
765                                        )
766                                    }
767                                    ReferencedAsset::None => {}
768                                };
769                            }
770                            None => {
771                                // Nothing to import.
772                            }
773                        }
774                    }
775                    return Ok(CodeGeneration::hoisted_stmts(result));
776                }
777            }
778        };
779
780        Ok(CodeGeneration::empty())
781    }
782}
783
784fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
785    match &mut decl {
786        Stmt::Decl(Decl::Var(decl)) => decl.span = span,
787        _ => panic!("Expected Stmt::Decl::Var"),
788    };
789    decl
790}
791
792#[turbo_tasks::value(shared)]
793pub struct InvalidExport {
794    export: RcStr,
795    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
796    source: IssueSource,
797}
798
799#[async_trait]
800#[turbo_tasks::value_impl]
801impl Issue for InvalidExport {
802    fn severity(&self) -> IssueSeverity {
803        IssueSeverity::Error
804    }
805
806    async fn title(&self) -> Result<StyledString> {
807        Ok(StyledString::Line(vec![
808            StyledString::Text(rcstr!("Export ")),
809            StyledString::Code(self.export.clone()),
810            StyledString::Text(rcstr!(" doesn't exist in target module")),
811        ]))
812    }
813
814    fn stage(&self) -> IssueStage {
815        IssueStage::Bindings
816    }
817
818    async fn file_path(&self) -> Result<FileSystemPath> {
819        self.source.file_path().owned().await
820    }
821
822    async fn description(&self) -> Result<Option<StyledString>> {
823        let export_names = all_known_export_names(*self.module).await?;
824        let did_you_mean = export_names
825            .iter()
826            .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
827            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
828            .map(|(s, _)| s);
829        Ok(Some(StyledString::Stack(vec![
830            StyledString::Line(vec![
831                StyledString::Text(rcstr!("The export ")),
832                StyledString::Code(self.export.clone()),
833                StyledString::Text(rcstr!(" was not found in module ")),
834                StyledString::Strong(self.module.ident().to_string().owned().await?),
835                StyledString::Text(rcstr!(".")),
836            ]),
837            if let Some(did_you_mean) = did_you_mean {
838                StyledString::Line(vec![
839                    StyledString::Text(rcstr!("Did you mean to import ")),
840                    StyledString::Code(did_you_mean.clone()),
841                    StyledString::Text(rcstr!("?")),
842                ])
843            } else {
844                StyledString::Strong(rcstr!("The module has no exports at all."))
845            },
846            StyledString::Text(
847                "All exports of the module are statically known (It doesn't have dynamic \
848                 exports). So it's known statically that the requested export doesn't exist."
849                    .into(),
850            ),
851        ])))
852    }
853
854    async fn detail(&self) -> Result<Option<StyledString>> {
855        let export_names = all_known_export_names(*self.module).await?;
856        Ok(Some(StyledString::Line(vec![
857            StyledString::Text(rcstr!("These are the exports of the module:\n")),
858            StyledString::Code(
859                export_names
860                    .iter()
861                    .map(|s| s.as_str())
862                    .intersperse(", ")
863                    .collect::<String>()
864                    .into(),
865            ),
866        ])))
867    }
868
869    fn source(&self) -> Option<IssueSource> {
870        Some(self.source)
871    }
872}
873
874#[turbo_tasks::value(shared)]
875pub struct CircularReExport {
876    export: RcStr,
877    import: Option<RcStr>,
878    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
879    module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
880}
881
882#[async_trait]
883#[turbo_tasks::value_impl]
884impl Issue for CircularReExport {
885    fn severity(&self) -> IssueSeverity {
886        IssueSeverity::Error
887    }
888
889    async fn title(&self) -> Result<StyledString> {
890        Ok(StyledString::Line(vec![
891            StyledString::Text(rcstr!("Export ")),
892            StyledString::Code(self.export.clone()),
893            StyledString::Text(rcstr!(" is a circular re-export")),
894        ]))
895    }
896
897    fn stage(&self) -> IssueStage {
898        IssueStage::Bindings
899    }
900
901    async fn file_path(&self) -> Result<FileSystemPath> {
902        self.module.ident().path().owned().await
903    }
904
905    async fn description(&self) -> Result<Option<StyledString>> {
906        Ok(Some(StyledString::Stack(vec![
907            StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
908            StyledString::Line(vec![
909                StyledString::Code(self.export.clone()),
910                StyledString::Text(rcstr!(" of module ")),
911                StyledString::Strong(self.module.ident().to_string().owned().await?),
912            ]),
913            StyledString::Line(vec![StyledString::Text(rcstr!(
914                "is a re-export of the export"
915            ))]),
916            StyledString::Line(vec![
917                StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
918                StyledString::Text(rcstr!(" of module ")),
919                StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
920                StyledString::Text(rcstr!(".")),
921            ]),
922        ])))
923    }
924
925    fn source(&self) -> Option<IssueSource> {
926        // TODO(PACK-4879): This should point at the buggy export by querying for the source
927        // location
928        None
929    }
930}