Skip to main content

turbopack_ecmascript/references/esm/
base.rs

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