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