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/// Optional extra state for an [`EsmAssetReference`] that is rarely present: the few values
447/// extracted from `ImportAnnotations` (the full `ImportAnnotations` — a `BTreeMap` plus several
448/// `Option`s — is not retained) plus a `resolve_override` from matched inner assets.
449#[derive(
450    Clone,
451    Default,
452    PartialEq,
453    Eq,
454    Hash,
455    Debug,
456    TraceRawVcs,
457    ValueDebugFormat,
458    NonLocalValue,
459    Encode,
460    Decode,
461)]
462struct EsmReferenceExtras {
463    /// `turbopackLoader` configuration (drives `ImportWithTurbopackUse`).
464    turbopack_loader: Option<WebpackLoaderItem>,
465    /// `turbopackAs` rename configuration.
466    turbopack_rename_as: Option<RcStr>,
467    /// `turbopackModuleType` override (distinct from the `with { type: ... }` attribute).
468    turbopack_module_type: Option<RcStr>,
469    /// The `with { type: ... }` attribute (drives `ImportWithType`).
470    module_type: Option<RcStr>,
471    /// The chunking-type annotation (drives `chunking_type`).
472    chunking_type: Option<SpecifiedChunkingType>,
473    /// A module to resolve to directly, bypassing resolution (from a matched inner asset).
474    resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
475}
476
477impl EsmReferenceExtras {
478    /// Builds the extras from import annotations and a resolve override, returning `None` (rather
479    /// than an all-empty box) when nothing relevant is present — the common case.
480    fn new(
481        annotations: Option<&ImportAnnotations>,
482        resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
483    ) -> Option<Box<Self>> {
484        let extras = EsmReferenceExtras {
485            turbopack_loader: annotations.and_then(|a| a.turbopack_loader().cloned()),
486            turbopack_rename_as: annotations.and_then(|a| a.turbopack_rename_as().cloned()),
487            turbopack_module_type: annotations.and_then(|a| a.turbopack_module_type().cloned()),
488            module_type: annotations
489                .and_then(|a| a.module_type())
490                .map(|m| RcStr::from(&*m.to_string_lossy())),
491            chunking_type: annotations.and_then(|a| a.chunking_type()),
492            resolve_override,
493        };
494        (extras != EsmReferenceExtras::default()).then(|| Box::new(extras))
495    }
496}
497
498impl EsmAssetReference {
499    #[allow(clippy::too_many_arguments)]
500    async fn new_inner(
501        module: ResolvedVc<EcmascriptModuleAsset>,
502        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
503        request: RcStr,
504        issue_source: IssueSource,
505        annotations: Option<ImportAnnotations>,
506        export_name: Option<ModulePart>,
507        import_usage: ImportUsage,
508        import_externals: bool,
509        module_fragments_enabled: bool,
510        resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
511        is_pure_import: bool,
512    ) -> Result<Self> {
513        // Apply any annotation-driven transition eagerly so the stored origin is final and the
514        // `annotations` don't need to be retained on the reference.
515        let origin = if let Some(transition) = annotations.as_ref().and_then(|a| a.transition()) {
516            origin
517                .with_transition(transition.into())
518                .await?
519                .to_resolved()
520                .await?
521        } else {
522            origin
523        };
524        Ok(EsmAssetReference {
525            module,
526            origin,
527            request,
528            issue_source,
529            export_name,
530            import_usage,
531            import_externals,
532            module_fragments_enabled,
533            is_pure_import,
534            extras: EsmReferenceExtras::new(annotations.as_ref(), resolve_override),
535        })
536    }
537
538    #[allow(clippy::too_many_arguments)]
539    pub async fn new(
540        module: ResolvedVc<EcmascriptModuleAsset>,
541        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
542        request: RcStr,
543        issue_source: IssueSource,
544        annotations: Option<ImportAnnotations>,
545        export_name: Option<ModulePart>,
546        import_usage: ImportUsage,
547        import_externals: bool,
548        module_fragments_enabled: bool,
549        resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
550    ) -> Result<Self> {
551        Self::new_inner(
552            module,
553            origin,
554            request,
555            issue_source,
556            annotations,
557            export_name,
558            import_usage,
559            import_externals,
560            module_fragments_enabled,
561            resolve_override,
562            /* is_pure_import */ false,
563        )
564        .await
565    }
566
567    #[allow(clippy::too_many_arguments)]
568    pub async fn new_pure(
569        module: ResolvedVc<EcmascriptModuleAsset>,
570        origin: ResolvedVc<Box<dyn ResolveOrigin>>,
571        request: RcStr,
572        issue_source: IssueSource,
573        annotations: Option<ImportAnnotations>,
574        export_name: Option<ModulePart>,
575        import_usage: ImportUsage,
576        import_externals: bool,
577        module_fragments_enabled: bool,
578        resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
579    ) -> Result<Self> {
580        Self::new_inner(
581            module,
582            origin,
583            request,
584            issue_source,
585            annotations,
586            export_name,
587            import_usage,
588            import_externals,
589            module_fragments_enabled,
590            resolve_override,
591            /* is_pure_import */ true,
592        )
593        .await
594    }
595
596    /// Builds a copy of this reference for a single resolved namespace export
597    /// (`import * as ns from 'foo'; ns.bar` → behave like `import { bar } from 'foo'`).
598    ///
599    /// Reuses the already-transitioned `origin` and the `extras` (annotation values + resolve
600    /// override), overriding only the `export_name`. Stays synchronous (no transition / annotation
601    /// re-extraction) so it can be called from the synchronous namespace-rewrite path.
602    pub fn rewrite_for_export(&self, export_name: ModulePart) -> Self {
603        EsmAssetReference {
604            module: self.module,
605            origin: self.origin,
606            request: self.request.clone(),
607            issue_source: self.issue_source,
608            export_name: Some(export_name),
609            // TODO this is correct, but an overapproximation. We should have individual
610            // import_usage data for each export. This would be fixed by moving this
611            // logic earlier.
612            import_usage: self.import_usage.clone(),
613            import_externals: self.import_externals,
614            module_fragments_enabled: self.module_fragments_enabled,
615            is_pure_import: self.is_pure_import,
616            extras: self.extras.clone(),
617        }
618    }
619
620    pub(crate) fn get_referenced_asset(
621        self: Vc<Self>,
622    ) -> impl Future<Output = Result<ReferencedAsset>> {
623        ReferencedAsset::from_resolve_result(self.resolve_reference())
624    }
625}
626
627#[turbo_tasks::value_impl]
628impl ModuleReference for EsmAssetReference {
629    #[turbo_tasks::function]
630    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
631        let extras = self.extras.as_deref();
632        if let Some(resolved) = extras.and_then(|e| e.resolve_override) {
633            return Ok(*ModuleResolveResult::module(resolved));
634        }
635        let ty = if let Some(loader) = extras.and_then(|e| e.turbopack_loader.as_ref()) {
636            // Resolve the loader path relative to the importing file
637            let origin_ref = self.origin.into_trait_ref().await?;
638            let origin_path = origin_ref.origin_path();
639            let loader_request = Request::parse(loader.loader.clone().into());
640            let resolved = resolve(
641                origin_path.parent(),
642                ReferenceType::Loader,
643                loader_request,
644                origin_ref.resolve_options(),
645            );
646            let loader_fs_path = if let Some(source) = resolved.await?.first_source() {
647                source.ident().await?.path.clone()
648            } else {
649                bail!("Unable to resolve turbopackLoader '{}'", loader.loader);
650            };
651
652            EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
653                loader: ResolvedWebpackLoaderItem {
654                    loader: loader_fs_path,
655                    options: loader.options.clone(),
656                },
657                rename_as: extras.and_then(|e| e.turbopack_rename_as.clone()),
658                module_type: extras.and_then(|e| e.turbopack_module_type.clone()),
659            }
660        } else if let Some(module_type) = extras.and_then(|e| e.module_type.as_ref()) {
661            EcmaScriptModulesReferenceSubType::ImportWithType(module_type.clone())
662        } else if let Some(part) = &self.export_name {
663            EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
664        } else {
665            EcmaScriptModulesReferenceSubType::Import
666        };
667
668        let request = Request::parse(self.request.clone().into());
669
670        if self.module_fragments_enabled {
671            if let Some(ModulePart::Evaluation) = &self.export_name
672                && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
673            {
674                return Ok(ModuleResolveResult {
675                    primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
676                    affecting_sources: Default::default(),
677                }
678                .cell());
679            }
680
681            if let Request::Module { module, .. } = &*request.await?
682                && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
683            {
684                if let Some(part) = &self.export_name {
685                    return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
686                        EcmascriptModulePartAsset::select_part(*self.module, part.clone())
687                            .to_resolved()
688                            .await?,
689                    )));
690                }
691                bail!("export_name is required for part import")
692            }
693        }
694
695        let result = esm_resolve(
696            *self.origin,
697            request,
698            ty,
699            ResolveErrorMode::Error,
700            Some(self.issue_source),
701        )
702        .await?;
703
704        if let Some(ModulePart::Export(export_name)) = &self.export_name {
705            for &module in result.await?.primary_modules().await?.iter() {
706                if let Some(module) = ResolvedVc::try_downcast(module)
707                    && *is_export_missing(*module, export_name.clone()).await?
708                {
709                    InvalidExport {
710                        export: export_name.clone(),
711                        module,
712                        source: self.issue_source,
713                    }
714                    .resolved_cell()
715                    .emit();
716                }
717            }
718        }
719
720        Ok(result)
721    }
722
723    fn chunking_type(&self) -> Option<ChunkingType> {
724        self.extras
725            .as_deref()
726            .and_then(|e| e.chunking_type)
727            .map_or_else(
728                || {
729                    Some(ChunkingType::Parallel {
730                        inherit_async: true,
731                        hoisted: true,
732                    })
733                },
734                |c| c.as_chunking_type(true, true),
735            )
736    }
737
738    fn binding_usage(&self) -> BindingUsage {
739        BindingUsage {
740            import: self.import_usage.clone(),
741            export: match &self.export_name {
742                Some(ModulePart::Export(export_name)) => ExportUsage::Named(export_name.clone()),
743                Some(ModulePart::Evaluation) => ExportUsage::Evaluation,
744                _ => ExportUsage::All,
745            },
746        }
747    }
748
749    fn source(&self) -> Option<IssueSource> {
750        Some(self.issue_source)
751    }
752}
753
754impl EsmAssetReference {
755    pub async fn code_generation(
756        self: ResolvedVc<Self>,
757        chunking_context: Vc<Box<dyn ChunkingContext>>,
758        scope_hoisting_context: ScopeHoistingContext<'_>,
759    ) -> Result<CodeGeneration> {
760        let this = &*self.await?;
761
762        if chunking_context
763            .unused_references()
764            .contains_key(&ResolvedVc::upcast(self))
765            .await?
766        {
767            return Ok(CodeGeneration::empty());
768        }
769
770        // only chunked references can be imported
771        if this
772            .extras
773            .as_deref()
774            .and_then(|e| e.chunking_type)
775            .is_none_or(|v| v != SpecifiedChunkingType::None)
776        {
777            let import_externals = this.import_externals;
778            let referenced_asset = self.get_referenced_asset().await?;
779
780            match &referenced_asset {
781                ReferencedAsset::Unresolvable => {
782                    // Insert code that throws immediately at time of import if a request is
783                    // unresolvable
784                    let request = &this.request;
785                    let stmt = Stmt::Expr(ExprStmt {
786                        expr: Box::new(throw_module_not_found_expr(request)),
787                        span: DUMMY_SP,
788                    });
789                    return Ok(CodeGeneration::hoisted_stmt(
790                        format!("throw {request}").into(),
791                        stmt,
792                    ));
793                }
794                // A module without ECMAScript bindings (e.g. a stylesheet) may still
795                // be imported for its side effects, which needs no code generation.
796                ReferencedAsset::None | ReferencedAsset::NonPlaceable(_) => {}
797                _ => {
798                    let mut result = vec![];
799
800                    let merged_index = if let ReferencedAsset::Some(asset) = &referenced_asset {
801                        scope_hoisting_context.get_module_index(*asset)
802                    } else {
803                        None
804                    };
805
806                    if let Some(merged_index) = merged_index {
807                        // Insert a placeholder to inline the merged module at the right place
808                        // relative to the other references (so to keep reference order).
809                        result.push(CodeGenerationHoistedStmt::new(
810                            format!("hoisted {merged_index}").into(),
811                            quote!(
812                                "__turbopack_merged_esm__($id);" as Stmt,
813                                id: Expr = Lit::Num(merged_index.into()).into(),
814                            ),
815                        ));
816                    }
817
818                    if merged_index.is_some()
819                        && matches!(this.export_name, Some(ModulePart::Evaluation))
820                    {
821                        // No need to import, the module was already executed and is available in
822                        // the same scope hoisting group (unless it's a
823                        // namespace import)
824                    } else {
825                        let ident = referenced_asset
826                            .get_ident(
827                                chunking_context,
828                                this.export_name.as_ref().and_then(|e| match e {
829                                    ModulePart::Export(export_name) => Some(export_name.clone()),
830                                    _ => None,
831                                }),
832                                scope_hoisting_context,
833                            )
834                            .await?;
835                        // `referenced_asset` must not be used past this point: the ident carries
836                        // everything about the import target (see `ImportSource`) — notably,
837                        // when the ident was resolved through a re-export chain, the
838                        // directly-referenced asset is the outer (rename) module, not the one
839                        // the emitted variable actually holds.
840                        drop(referenced_asset);
841                        match ident {
842                            Some(ReferencedAssetIdent::LocalBinding { .. }) => {
843                                // no need to import
844                            }
845                            Some(ReferencedAssetIdent::Module {
846                                namespace_ident,
847                                ctxt,
848                                export: _,
849                                import_source,
850                            }) => {
851                                let span = this
852                                    .issue_source
853                                    .to_swc_offsets()
854                                    .await?
855                                    .map_or(DUMMY_SP, |(start, end)| {
856                                        Span::new(BytePos(start), BytePos(end))
857                                    });
858                                let name = Ident::new(
859                                    namespace_ident.into(),
860                                    DUMMY_SP,
861                                    ctxt.unwrap_or_default(),
862                                );
863                                let (key, mut call_expr) = match import_source {
864                                    ImportSource::Module { asset } => {
865                                        let id = asset.chunk_item_id(chunking_context).await?;
866                                        // Include ctxt in the key to prevent incorrect
867                                        // deduplication when multiple merged modules import the
868                                        // same target but have different syntax contexts (which
869                                        // would cause hygiene to rename one of them).
870                                        (
871                                            format!("{} {:?}", id, ctxt).into(),
872                                            quote!(
873                                                "$turbopack_import($id)" as Expr,
874                                                turbopack_import: Expr = TURBOPACK_IMPORT.into(),
875                                                id: Expr = module_id_to_lit(&id),
876                                            ),
877                                        )
878                                    }
879                                    ImportSource::External {
880                                        request,
881                                        ty: ExternalType::EcmaScriptModule,
882                                    } => {
883                                        if !*chunking_context
884                                            .environment()
885                                            .supports_esm_externals()
886                                            .await?
887                                        {
888                                            turbobail!(
889                                                "the chunking context ({}) does not support \
890                                                 external modules (esm request: {request})",
891                                                chunking_context.name()
892                                            );
893                                        }
894                                        let call = if import_externals {
895                                            quote!(
896                                                "$turbopack_external_import($id)" as Expr,
897                                                turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
898                                                id: Expr = Expr::Lit(request.to_string().into())
899                                            )
900                                        } else {
901                                            quote!(
902                                                "$turbopack_external_require($id, () => require($id), true)" as Expr,
903                                                turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
904                                                id: Expr = Expr::Lit(request.to_string().into())
905                                            )
906                                        };
907                                        (name.sym.as_str().into(), call)
908                                    }
909                                    ImportSource::External {
910                                        request,
911                                        ty: ExternalType::CommonJs | ExternalType::Url,
912                                    } => {
913                                        if !*chunking_context
914                                            .environment()
915                                            .supports_commonjs_externals()
916                                            .await?
917                                        {
918                                            turbobail!(
919                                                "the chunking context ({}) does not support \
920                                                 external modules (request: {request})",
921                                                chunking_context.name()
922                                            );
923                                        }
924                                        let call = quote!(
925                                            "$turbopack_external_require($id, () => require($id), true)" as Expr,
926                                            turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
927                                            id: Expr = Expr::Lit(request.to_string().into())
928                                        );
929                                        (name.sym.as_str().into(), call)
930                                    }
931                                    // fallback in case we introduce a new `ExternalType`
932                                    #[allow(unreachable_patterns)]
933                                    ImportSource::External { request, ty, .. } => {
934                                        bail!(
935                                            "Unsupported external type {:?} for ESM reference \
936                                             with request: {:?}",
937                                            ty,
938                                            request
939                                        )
940                                    }
941                                };
942                                if this.is_pure_import {
943                                    call_expr.set_span(PURE_SP);
944                                }
945                                result.push(CodeGenerationHoistedStmt::new(
946                                    key,
947                                    var_decl_with_span(
948                                        quote!(
949                                            "var $name = $call;" as Stmt,
950                                            name = name,
951                                            call: Expr = call_expr
952                                        ),
953                                        span,
954                                    ),
955                                ));
956                            }
957                            None => {
958                                // Nothing to import.
959                            }
960                        }
961                    }
962                    return Ok(CodeGeneration::hoisted_stmts(result));
963                }
964            }
965        };
966
967        Ok(CodeGeneration::empty())
968    }
969}
970
971fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
972    match &mut decl {
973        Stmt::Decl(Decl::Var(decl)) => decl.span = span,
974        _ => panic!("Expected Stmt::Decl::Var"),
975    };
976    decl
977}
978
979#[turbo_tasks::value(shared)]
980pub struct InvalidExport {
981    export: RcStr,
982    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
983    source: IssueSource,
984}
985
986#[async_trait]
987#[turbo_tasks::value_impl]
988impl Issue for InvalidExport {
989    fn severity(&self) -> IssueSeverity {
990        IssueSeverity::Error
991    }
992
993    async fn title(&self) -> Result<StyledString> {
994        Ok(StyledString::Line(vec![
995            StyledString::Text(rcstr!("Export ")),
996            StyledString::Code(self.export.clone()),
997            StyledString::Text(rcstr!(" doesn't exist in target module")),
998        ]))
999    }
1000
1001    fn stage(&self) -> IssueStage {
1002        IssueStage::Bindings
1003    }
1004
1005    async fn file_path(&self) -> Result<FileSystemPath> {
1006        self.source.file_path().await
1007    }
1008
1009    async fn description(&self) -> Result<Option<StyledString>> {
1010        let export_names = all_known_export_names(*self.module).await?;
1011        let did_you_mean = export_names
1012            .iter()
1013            .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
1014            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
1015            .map(|(s, _)| s);
1016        Ok(Some(StyledString::Stack(vec![
1017            StyledString::Line(vec![
1018                StyledString::Text(rcstr!("The export ")),
1019                StyledString::Code(self.export.clone()),
1020                StyledString::Text(rcstr!(" was not found in module ")),
1021                StyledString::Strong(self.module.ident().to_string().owned().await?),
1022                StyledString::Text(rcstr!(".")),
1023            ]),
1024            if let Some(did_you_mean) = did_you_mean {
1025                StyledString::Line(vec![
1026                    StyledString::Text(rcstr!("Did you mean to import ")),
1027                    StyledString::Code(did_you_mean.clone()),
1028                    StyledString::Text(rcstr!("?")),
1029                ])
1030            } else {
1031                StyledString::Strong(rcstr!("The module has no exports at all."))
1032            },
1033            StyledString::Text(
1034                "All exports of the module are statically known (It doesn't have dynamic \
1035                 exports). So it's known statically that the requested export doesn't exist."
1036                    .into(),
1037            ),
1038        ])))
1039    }
1040
1041    async fn detail(&self) -> Result<Option<StyledString>> {
1042        let export_names = all_known_export_names(*self.module).await?;
1043        Ok(Some(StyledString::Line(vec![
1044            StyledString::Text(rcstr!("These are the exports of the module:\n")),
1045            StyledString::Code(
1046                export_names
1047                    .iter()
1048                    .map(|s| s.as_str())
1049                    .intersperse(", ")
1050                    .collect::<String>()
1051                    .into(),
1052            ),
1053        ])))
1054    }
1055
1056    fn source(&self) -> Option<IssueSource> {
1057        Some(self.source)
1058    }
1059}
1060
1061#[turbo_tasks::value(shared)]
1062pub struct CircularReExport {
1063    export: RcStr,
1064    import: Option<RcStr>,
1065    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1066    module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1067}
1068
1069#[async_trait]
1070#[turbo_tasks::value_impl]
1071impl Issue for CircularReExport {
1072    fn severity(&self) -> IssueSeverity {
1073        IssueSeverity::Error
1074    }
1075
1076    async fn title(&self) -> Result<StyledString> {
1077        Ok(StyledString::Line(vec![
1078            StyledString::Text(rcstr!("Export ")),
1079            StyledString::Code(self.export.clone()),
1080            StyledString::Text(rcstr!(" is a circular re-export")),
1081        ]))
1082    }
1083
1084    fn stage(&self) -> IssueStage {
1085        IssueStage::Bindings
1086    }
1087
1088    async fn file_path(&self) -> Result<FileSystemPath> {
1089        Ok(self.module.ident().await?.path.clone())
1090    }
1091
1092    async fn description(&self) -> Result<Option<StyledString>> {
1093        Ok(Some(StyledString::Stack(vec![
1094            StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
1095            StyledString::Line(vec![
1096                StyledString::Code(self.export.clone()),
1097                StyledString::Text(rcstr!(" of module ")),
1098                StyledString::Strong(self.module.ident().to_string().owned().await?),
1099            ]),
1100            StyledString::Line(vec![StyledString::Text(rcstr!(
1101                "is a re-export of the export"
1102            ))]),
1103            StyledString::Line(vec![
1104                StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
1105                StyledString::Text(rcstr!(" of module ")),
1106                StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
1107                StyledString::Text(rcstr!(".")),
1108            ]),
1109        ])))
1110    }
1111
1112    fn source(&self) -> Option<IssueSource> {
1113        // TODO(PACK-4879): This should point at the buggy export by querying for the source
1114        // location
1115        None
1116    }
1117}