Skip to main content

turbopack_ecmascript/references/esm/
base.rs

1use anyhow::{Result, 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, turbobail};
14use turbo_tasks_fs::FileSystemPath;
15use turbopack_core::{
16    chunk::{ChunkingContext, ChunkingType, 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::{SpecifiedChunkingType, throw_module_not_found_expr},
50    },
51    runtime_functions::{TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_IMPORT},
52    tree_shake::{TURBOPACK_PART_IMPORT_SOURCE, part::module::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}")]
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: Option<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.as_ref().and_then(|a| a.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: Option<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: Option<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    pub(crate) fn get_referenced_asset(self: Vc<Self>) -> Vc<ReferencedAsset> {
401        ReferencedAsset::from_resolve_result(self.resolve_reference())
402    }
403}
404
405#[turbo_tasks::value_impl]
406impl ModuleReference for EsmAssetReference {
407    #[turbo_tasks::function]
408    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
409        let ty = if let Some(loader) = self.annotations.as_ref().and_then(|a| a.turbopack_loader())
410        {
411            // Resolve the loader path relative to the importing file
412            let origin = self.get_origin();
413            let origin_path = origin.origin_path().await?;
414            let loader_request = Request::parse(loader.loader.clone().into());
415            let resolved = resolve(
416                origin_path.parent(),
417                ReferenceType::Loader,
418                loader_request,
419                origin.resolve_options(),
420            );
421            let loader_fs_path = if let Some(source) = *resolved.first_source().await? {
422                (*source.ident().path().await?).clone()
423            } else {
424                bail!("Unable to resolve turbopackLoader '{}'", loader.loader);
425            };
426
427            EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
428                loader: ResolvedWebpackLoaderItem {
429                    loader: loader_fs_path,
430                    options: loader.options.clone(),
431                },
432                rename_as: self
433                    .annotations
434                    .as_ref()
435                    .and_then(|a| a.turbopack_rename_as())
436                    .cloned(),
437                module_type: self
438                    .annotations
439                    .as_ref()
440                    .and_then(|a| a.turbopack_module_type())
441                    .cloned(),
442            }
443        } else if let Some(module_type) = self.annotations.as_ref().and_then(|a| a.module_type()) {
444            EcmaScriptModulesReferenceSubType::ImportWithType(RcStr::from(
445                &*module_type.to_string_lossy(),
446            ))
447        } else if let Some(part) = &self.export_name {
448            EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
449        } else {
450            EcmaScriptModulesReferenceSubType::Import
451        };
452
453        let request = Request::parse(self.request.clone().into());
454
455        if let Some(TreeShakingMode::ModuleFragments) = self.tree_shaking_mode {
456            if let Some(ModulePart::Evaluation) = &self.export_name
457                && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
458            {
459                return Ok(ModuleResolveResult {
460                    primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
461                    affecting_sources: Default::default(),
462                }
463                .cell());
464            }
465
466            if let Request::Module { module, .. } = &*request.await?
467                && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
468            {
469                if let Some(part) = &self.export_name {
470                    return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
471                        EcmascriptModulePartAsset::select_part(*self.module, part.clone())
472                            .to_resolved()
473                            .await?,
474                    )));
475                }
476                bail!("export_name is required for part import")
477            }
478        }
479
480        let result = esm_resolve(
481            self.get_origin(),
482            request,
483            ty,
484            ResolveErrorMode::Error,
485            Some(self.issue_source),
486        )
487        .await?;
488
489        if let Some(ModulePart::Export(export_name)) = &self.export_name {
490            for &module in result.primary_modules().await? {
491                if let Some(module) = ResolvedVc::try_downcast(module)
492                    && *is_export_missing(*module, export_name.clone()).await?
493                {
494                    InvalidExport {
495                        export: export_name.clone(),
496                        module,
497                        source: self.issue_source,
498                    }
499                    .resolved_cell()
500                    .emit();
501                }
502            }
503        }
504
505        Ok(result)
506    }
507
508    fn chunking_type(&self) -> Option<ChunkingType> {
509        self.annotations
510            .as_ref()
511            .and_then(|a| a.chunking_type())
512            .map_or_else(
513                || {
514                    Some(ChunkingType::Parallel {
515                        inherit_async: true,
516                        hoisted: true,
517                    })
518                },
519                |c| c.as_chunking_type(true, true),
520            )
521    }
522
523    fn binding_usage(&self) -> BindingUsage {
524        BindingUsage {
525            import: self.import_usage.clone(),
526            export: match &self.export_name {
527                Some(ModulePart::Export(export_name)) => ExportUsage::Named(export_name.clone()),
528                Some(ModulePart::Evaluation) => ExportUsage::Evaluation,
529                _ => ExportUsage::All,
530            },
531        }
532    }
533}
534
535impl EsmAssetReference {
536    pub async fn code_generation(
537        self: ResolvedVc<Self>,
538        chunking_context: Vc<Box<dyn ChunkingContext>>,
539        scope_hoisting_context: ScopeHoistingContext<'_>,
540    ) -> Result<CodeGeneration> {
541        let this = &*self.await?;
542
543        if chunking_context
544            .unused_references()
545            .contains_key(&ResolvedVc::upcast(self))
546            .await?
547        {
548            return Ok(CodeGeneration::empty());
549        }
550
551        // only chunked references can be imported
552        if this
553            .annotations
554            .as_ref()
555            .and_then(|a| a.chunking_type())
556            .is_none_or(|v| v != SpecifiedChunkingType::None)
557        {
558            let import_externals = this.import_externals;
559            let referenced_asset = self.get_referenced_asset().await?;
560
561            match &*referenced_asset {
562                ReferencedAsset::Unresolvable => {
563                    // Insert code that throws immediately at time of import if a request is
564                    // unresolvable
565                    let request = &this.request;
566                    let stmt = Stmt::Expr(ExprStmt {
567                        expr: Box::new(throw_module_not_found_expr(request)),
568                        span: DUMMY_SP,
569                    });
570                    return Ok(CodeGeneration::hoisted_stmt(
571                        format!("throw {request}").into(),
572                        stmt,
573                    ));
574                }
575                ReferencedAsset::None => {}
576                _ => {
577                    let mut result = vec![];
578
579                    let merged_index = if let ReferencedAsset::Some(asset) = &*referenced_asset {
580                        scope_hoisting_context.get_module_index(*asset)
581                    } else {
582                        None
583                    };
584
585                    if let Some(merged_index) = merged_index {
586                        // Insert a placeholder to inline the merged module at the right place
587                        // relative to the other references (so to keep reference order).
588                        result.push(CodeGenerationHoistedStmt::new(
589                            format!("hoisted {merged_index}").into(),
590                            quote!(
591                                "__turbopack_merged_esm__($id);" as Stmt,
592                                id: Expr = Lit::Num(merged_index.into()).into(),
593                            ),
594                        ));
595                    }
596
597                    if merged_index.is_some()
598                        && matches!(this.export_name, Some(ModulePart::Evaluation))
599                    {
600                        // No need to import, the module was already executed and is available in
601                        // the same scope hoisting group (unless it's a
602                        // namespace import)
603                    } else {
604                        let ident = referenced_asset
605                            .get_ident(
606                                chunking_context,
607                                this.export_name.as_ref().and_then(|e| match e {
608                                    ModulePart::Export(export_name) => Some(export_name.clone()),
609                                    _ => None,
610                                }),
611                                scope_hoisting_context,
612                            )
613                            .await?;
614                        match ident {
615                            Some(ReferencedAssetIdent::LocalBinding { .. }) => {
616                                // no need to import
617                            }
618                            Some(ident @ ReferencedAssetIdent::Module { .. }) => {
619                                let span = this
620                                    .issue_source
621                                    .to_swc_offsets()
622                                    .await?
623                                    .map_or(DUMMY_SP, |(start, end)| {
624                                        Span::new(BytePos(start), BytePos(end))
625                                    });
626                                match &*referenced_asset {
627                                    ReferencedAsset::Unresolvable => {
628                                        unreachable!();
629                                    }
630                                    ReferencedAsset::Some(asset) => {
631                                        let id = asset.chunk_item_id(chunking_context).await?;
632                                        let (sym, ctxt) =
633                                            ident.into_module_namespace_ident().unwrap();
634                                        let name = Ident::new(
635                                            sym.into(),
636                                            DUMMY_SP,
637                                            ctxt.unwrap_or_default(),
638                                        );
639                                        let mut call_expr = quote!(
640                                            "$turbopack_import($id)" as Expr,
641                                            turbopack_import: Expr = TURBOPACK_IMPORT.into(),
642                                            id: Expr = module_id_to_lit(&id),
643                                        );
644                                        if this.is_pure_import {
645                                            call_expr.set_span(PURE_SP);
646                                        }
647                                        result.push(CodeGenerationHoistedStmt::new(
648                                            id.to_string().into(),
649                                            var_decl_with_span(
650                                                quote!(
651                                                    "var $name = $call;" as Stmt,
652                                                    name = name,
653                                                    call: Expr = call_expr
654                                                ),
655                                                span,
656                                            ),
657                                        ));
658                                    }
659                                    ReferencedAsset::External(
660                                        request,
661                                        ExternalType::EcmaScriptModule,
662                                    ) => {
663                                        if !*chunking_context
664                                            .environment()
665                                            .supports_esm_externals()
666                                            .await?
667                                        {
668                                            turbobail!(
669                                                "the chunking context ({}) does not support \
670                                                 external modules (esm request: {request})",
671                                                chunking_context.name()
672                                            );
673                                        }
674                                        let (sym, ctxt) =
675                                            ident.into_module_namespace_ident().unwrap();
676                                        let name = Ident::new(
677                                            sym.into(),
678                                            DUMMY_SP,
679                                            ctxt.unwrap_or_default(),
680                                        );
681                                        let mut call_expr = if import_externals {
682                                            quote!(
683                                                "$turbopack_external_import($id)" as Expr,
684                                                turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
685                                                id: Expr = Expr::Lit(request.clone().to_string().into())
686                                            )
687                                        } else {
688                                            quote!(
689                                                "$turbopack_external_require($id, () => require($id), true)" as Expr,
690                                                turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
691                                                id: Expr = Expr::Lit(request.clone().to_string().into())
692                                            )
693                                        };
694                                        if this.is_pure_import {
695                                            call_expr.set_span(PURE_SP);
696                                        }
697                                        result.push(CodeGenerationHoistedStmt::new(
698                                            name.sym.as_str().into(),
699                                            var_decl_with_span(
700                                                quote!(
701                                                    "var $name = $call;" as Stmt,
702                                                    name = name,
703                                                    call: Expr = call_expr,
704                                                ),
705                                                span,
706                                            ),
707                                        ));
708                                    }
709                                    ReferencedAsset::External(
710                                        request,
711                                        ExternalType::CommonJs | ExternalType::Url,
712                                    ) => {
713                                        if !*chunking_context
714                                            .environment()
715                                            .supports_commonjs_externals()
716                                            .await?
717                                        {
718                                            turbobail!(
719                                                "the chunking context ({}) does not support \
720                                                 external modules (request: {request})",
721                                                chunking_context.name()
722                                            );
723                                        }
724                                        let (sym, ctxt) =
725                                            ident.into_module_namespace_ident().unwrap();
726                                        let name = Ident::new(
727                                            sym.into(),
728                                            DUMMY_SP,
729                                            ctxt.unwrap_or_default(),
730                                        );
731                                        let mut call_expr = quote!(
732                                            "$turbopack_external_require($id, () => require($id), true)" as Expr,
733                                            turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
734                                            id: Expr = Expr::Lit(request.clone().to_string().into())
735                                        );
736                                        if this.is_pure_import {
737                                            call_expr.set_span(PURE_SP);
738                                        }
739                                        result.push(CodeGenerationHoistedStmt::new(
740                                            name.sym.as_str().into(),
741                                            var_decl_with_span(
742                                                quote!(
743                                                    "var $name = $call;" as Stmt,
744                                                    name = name,
745                                                    call: Expr = call_expr,
746                                                ),
747                                                span,
748                                            ),
749                                        ));
750                                    }
751                                    // fallback in case we introduce a new `ExternalType`
752                                    #[allow(unreachable_patterns)]
753                                    ReferencedAsset::External(request, ty) => {
754                                        bail!(
755                                            "Unsupported external type {:?} for ESM reference \
756                                             with request: {:?}",
757                                            ty,
758                                            request
759                                        )
760                                    }
761                                    ReferencedAsset::None => {}
762                                };
763                            }
764                            None => {
765                                // Nothing to import.
766                            }
767                        }
768                    }
769                    return Ok(CodeGeneration::hoisted_stmts(result));
770                }
771            }
772        };
773
774        Ok(CodeGeneration::empty())
775    }
776}
777
778fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
779    match &mut decl {
780        Stmt::Decl(Decl::Var(decl)) => decl.span = span,
781        _ => panic!("Expected Stmt::Decl::Var"),
782    };
783    decl
784}
785
786#[turbo_tasks::value(shared)]
787pub struct InvalidExport {
788    export: RcStr,
789    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
790    source: IssueSource,
791}
792
793#[turbo_tasks::value_impl]
794impl Issue for InvalidExport {
795    fn severity(&self) -> IssueSeverity {
796        IssueSeverity::Error
797    }
798
799    #[turbo_tasks::function]
800    fn title(&self) -> Result<Vc<StyledString>> {
801        Ok(StyledString::Line(vec![
802            StyledString::Text(rcstr!("Export ")),
803            StyledString::Code(self.export.clone()),
804            StyledString::Text(rcstr!(" doesn't exist in target module")),
805        ])
806        .cell())
807    }
808
809    #[turbo_tasks::function]
810    fn stage(&self) -> Vc<IssueStage> {
811        IssueStage::Bindings.cell()
812    }
813
814    #[turbo_tasks::function]
815    fn file_path(&self) -> Vc<FileSystemPath> {
816        self.source.file_path()
817    }
818
819    #[turbo_tasks::function]
820    async fn description(&self) -> Result<Vc<OptionStyledString>> {
821        let export_names = all_known_export_names(*self.module).await?;
822        let did_you_mean = export_names
823            .iter()
824            .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
825            .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
826            .map(|(s, _)| s);
827        Ok(Vc::cell(Some(
828            StyledString::Stack(vec![
829                StyledString::Line(vec![
830                    StyledString::Text(rcstr!("The export ")),
831                    StyledString::Code(self.export.clone()),
832                    StyledString::Text(rcstr!(" was not found in module ")),
833                    StyledString::Strong(self.module.ident().to_string().owned().await?),
834                    StyledString::Text(rcstr!(".")),
835                ]),
836                if let Some(did_you_mean) = did_you_mean {
837                    StyledString::Line(vec![
838                        StyledString::Text(rcstr!("Did you mean to import ")),
839                        StyledString::Code(did_you_mean.clone()),
840                        StyledString::Text(rcstr!("?")),
841                    ])
842                } else {
843                    StyledString::Strong(rcstr!("The module has no exports at all."))
844                },
845                StyledString::Text(
846                    "All exports of the module are statically known (It doesn't have dynamic \
847                     exports). So it's known statically that the requested export doesn't exist."
848                        .into(),
849                ),
850            ])
851            .resolved_cell(),
852        )))
853    }
854
855    #[turbo_tasks::function]
856    async fn detail(&self) -> Result<Vc<OptionStyledString>> {
857        let export_names = all_known_export_names(*self.module).await?;
858        Ok(Vc::cell(Some(
859            StyledString::Line(vec![
860                StyledString::Text(rcstr!("These are the exports of the module:\n")),
861                StyledString::Code(
862                    export_names
863                        .iter()
864                        .map(|s| s.as_str())
865                        .intersperse(", ")
866                        .collect::<String>()
867                        .into(),
868                ),
869            ])
870            .resolved_cell(),
871        )))
872    }
873
874    #[turbo_tasks::function]
875    fn source(&self) -> Vc<OptionIssueSource> {
876        Vc::cell(Some(self.source))
877    }
878}
879
880#[turbo_tasks::value(shared)]
881pub struct CircularReExport {
882    export: RcStr,
883    import: Option<RcStr>,
884    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
885    module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
886}
887
888#[turbo_tasks::value_impl]
889impl Issue for CircularReExport {
890    fn severity(&self) -> IssueSeverity {
891        IssueSeverity::Error
892    }
893
894    #[turbo_tasks::function]
895    async fn title(&self) -> Result<Vc<StyledString>> {
896        Ok(StyledString::Line(vec![
897            StyledString::Text(rcstr!("Export ")),
898            StyledString::Code(self.export.clone()),
899            StyledString::Text(rcstr!(" is a circular re-export")),
900        ])
901        .cell())
902    }
903
904    #[turbo_tasks::function]
905    fn stage(&self) -> Vc<IssueStage> {
906        IssueStage::Bindings.cell()
907    }
908
909    #[turbo_tasks::function]
910    fn file_path(&self) -> Vc<FileSystemPath> {
911        self.module.ident().path()
912    }
913
914    #[turbo_tasks::function]
915    async fn description(&self) -> Result<Vc<OptionStyledString>> {
916        Ok(Vc::cell(Some(
917            StyledString::Stack(vec![
918                StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
919                StyledString::Line(vec![
920                    StyledString::Code(self.export.clone()),
921                    StyledString::Text(rcstr!(" of module ")),
922                    StyledString::Strong(self.module.ident().to_string().owned().await?),
923                ]),
924                StyledString::Line(vec![StyledString::Text(rcstr!(
925                    "is a re-export of the export"
926                ))]),
927                StyledString::Line(vec![
928                    StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
929                    StyledString::Text(rcstr!(" of module ")),
930                    StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
931                    StyledString::Text(rcstr!(".")),
932                ]),
933            ])
934            .resolved_cell(),
935        )))
936    }
937
938    #[turbo_tasks::function]
939    fn source(&self) -> Vc<OptionIssueSource> {
940        // TODO(PACK-4879): This should point at the buggy export by querying for the source
941        // location
942        Vc::cell(None)
943    }
944}