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