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