Skip to main content

turbopack_ecmascript/references/esm/
base.rs

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