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