1use anyhow::{Result, bail};
2use async_trait::async_trait;
3use bincode::{Decode, Encode};
4use either::Either;
5use strsim::jaro;
6use swc_core::{
7 common::{BytePos, DUMMY_SP, Span, SyntaxContext, source_map::PURE_SP},
8 ecma::ast::{
9 ComputedPropName, Decl, Expr, ExprStmt, Ident, Lit, MemberExpr, MemberProp, Number,
10 SeqExpr, Stmt, Str,
11 },
12 quote,
13};
14use turbo_rcstr::{RcStr, rcstr};
15use turbo_tasks::{
16 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
17 turbobail,
18};
19use turbo_tasks_fs::FileSystemPath;
20use turbopack_core::{
21 chunk::{ChunkingContext, ChunkingType, ModuleChunkItemIdExt},
22 issue::{
23 Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, StyledString,
24 code_gen::CodeGenerationIssue,
25 },
26 loader::{ResolvedWebpackLoaderItem, WebpackLoaderItem},
27 module::{Module, ModuleSideEffects},
28 module_graph::binding_usage_info::ModuleExportUsageInfo,
29 reference::ModuleReference,
30 reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
31 resolve::{
32 BindingUsage, ExportUsage, ExternalType, ImportUsage, ModulePart, ModuleResolveResult,
33 ModuleResolveResultItem, RequestKey, ResolveErrorMode,
34 origin::{ResolveOrigin, ResolveOriginExt},
35 parse::Request,
36 resolve,
37 },
38 source::Source,
39};
40use turbopack_resolve::ecmascript::esm_resolve;
41
42use crate::{
43 EcmascriptModuleAsset, ScopeHoistingContext,
44 analyzer::imports::ImportAnnotations,
45 chunk::{EcmascriptChunkPlaceable, EcmascriptExports},
46 code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
47 export::Liveness,
48 magic_identifier,
49 module_fragments::{TURBOPACK_PART_IMPORT_SOURCE, part::module::EcmascriptModulePartAsset},
50 references::{
51 esm::{
52 EsmExport,
53 export::{all_known_export_names, is_export_missing},
54 mangle::generated_export_key,
55 },
56 util::{SpecifiedChunkingType, throw_module_not_found_expr},
57 },
58 runtime_functions::{TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_IMPORT},
59 utils::module_id_to_lit,
60};
61
62#[derive(PartialEq, Eq)]
63pub enum ReferencedAsset {
64 Some(ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>),
65 External(RcStr, ExternalType),
66 NonPlaceable(ResolvedVc<Box<dyn Module>>),
71 None,
72 Unresolvable,
73}
74
75#[derive(Debug)]
76pub enum ReferencedAssetIdent {
77 LocalBinding {
79 ident: RcStr,
80 ctxt: SyntaxContext,
81 liveness: Liveness,
82 },
83 Module {
85 namespace_ident: String,
89 ctxt: Option<SyntaxContext>,
90 export: Option<RcStr>,
91 import_source: ImportSource,
98 },
99}
100
101#[derive(Debug)]
103pub enum ImportSource {
104 Module {
106 asset: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
107 },
108 External { request: RcStr, ty: ExternalType },
112}
113
114impl ImportSource {
115 pub async fn get_namespace_ident(
117 &self,
118 chunking_context: Vc<Box<dyn ChunkingContext>>,
119 ) -> Result<String> {
120 Ok(match self {
121 ImportSource::Module { asset } => {
122 ReferencedAsset::get_ident_from_placeable(asset, chunking_context).await?
123 }
124 ImportSource::External { request, ty } => {
125 magic_identifier::mangle(&format!("{ty} external {request}"))
126 }
127 })
128 }
129}
130
131impl ReferencedAssetIdent {
132 pub fn into_module_namespace_ident(self) -> Option<(String, Option<SyntaxContext>)> {
133 match self {
134 ReferencedAssetIdent::Module {
135 namespace_ident,
136 ctxt,
137 ..
138 } => Some((namespace_ident, ctxt)),
139 ReferencedAssetIdent::LocalBinding { .. } => None,
140 }
141 }
142
143 pub fn as_expr_individual(&self, span: Span) -> Either<Ident, MemberExpr> {
144 match self {
145 ReferencedAssetIdent::LocalBinding {
146 ident,
147 ctxt,
148 liveness: _,
149 } => Either::Left(Ident::new(ident.as_str().into(), span, *ctxt)),
150 ReferencedAssetIdent::Module {
151 namespace_ident,
152 ctxt,
153 export,
154 import_source: _,
155 } => {
156 if let Some(export) = export {
157 Either::Right(MemberExpr {
158 span,
159 obj: Box::new(Expr::Ident(Ident::new(
160 namespace_ident.as_str().into(),
161 DUMMY_SP,
162 ctxt.unwrap_or_default(),
163 ))),
164 prop: MemberProp::Computed(ComputedPropName {
165 span: DUMMY_SP,
166 expr: Box::new(Expr::Lit(Lit::Str(Str {
167 span: DUMMY_SP,
168 value: export.as_str().into(),
169 raw: None,
170 }))),
171 }),
172 })
173 } else {
174 Either::Left(Ident::new(
175 namespace_ident.as_str().into(),
176 span,
177 ctxt.unwrap_or_default(),
178 ))
179 }
180 }
181 }
182 }
183 pub fn as_expr(&self, span: Span, is_callee: bool) -> Expr {
184 match self.as_expr_individual(span) {
185 Either::Left(ident) => ident.into(),
186 Either::Right(member) => {
187 if is_callee {
188 Expr::Seq(SeqExpr {
189 exprs: vec![
190 Box::new(Expr::Lit(Lit::Num(Number {
191 span: DUMMY_SP,
192 value: 0.0,
193 raw: None,
194 }))),
195 Box::new(member.into()),
196 ],
197 span: DUMMY_SP,
198 })
199 } else {
200 member.into()
201 }
202 }
203 }
204 }
205}
206
207impl ReferencedAsset {
208 pub async fn get_ident(
209 &self,
210 chunking_context: Vc<Box<dyn ChunkingContext>>,
211 export: Option<RcStr>,
212 scope_hoisting_context: ScopeHoistingContext<'_>,
213 ) -> Result<Option<ReferencedAssetIdent>> {
214 self.get_ident_inner(chunking_context, export, scope_hoisting_context, None)
215 .await
216 }
217
218 async fn get_ident_inner(
219 &self,
220 chunking_context: Vc<Box<dyn ChunkingContext>>,
221 export: Option<RcStr>,
222 scope_hoisting_context: ScopeHoistingContext<'_>,
223 initial: Option<&ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>>,
224 ) -> Result<Option<ReferencedAssetIdent>> {
225 Ok(match self {
226 ReferencedAsset::Some(asset) => {
227 if let Some(ctxt) = scope_hoisting_context.get_module_syntax_context(*asset)
228 && let Some(export) = &export
229 && let EcmascriptExports::EsmExports(exports) = *asset.get_exports().await?
230 {
231 let exports = exports.expand_exports(ModuleExportUsageInfo::all()).await?;
232 let esm_export = exports.exports.get(export);
233 match esm_export {
234 Some(EsmExport::LocalBinding(_name, liveness)) => {
235 return Ok(Some(ReferencedAssetIdent::LocalBinding {
239 ident: export.clone(),
240 ctxt,
241 liveness: *liveness,
242 }));
243 }
244 Some(b @ EsmExport::ImportedBinding(esm_ref, _, _))
245 | Some(b @ EsmExport::ImportedNamespace(esm_ref)) => {
246 let imported = if let EsmExport::ImportedBinding(_, export, _) = b {
247 Some(export.clone())
248 } else {
249 None
250 };
251
252 let referenced_asset =
253 ReferencedAsset::from_resolve_result(esm_ref.resolve_reference())
254 .await?;
255
256 if let Some(&initial) = initial
257 && referenced_asset == ReferencedAsset::Some(initial)
258 {
259 CircularReExport {
262 export: export.clone(),
263 import: imported.clone(),
264 module: *asset,
265 module_cycle: initial,
266 }
267 .resolved_cell()
268 .emit();
269 return Ok(None);
270 }
271
272 return Ok(
275 match Box::pin(referenced_asset.get_ident_inner(
276 chunking_context,
277 imported,
278 scope_hoisting_context,
279 Some(asset),
280 ))
281 .await?
282 {
283 Some(ReferencedAssetIdent::Module {
284 namespace_ident,
285 ctxt: None,
289 export,
290 import_source,
291 }) => Some(ReferencedAssetIdent::Module {
292 namespace_ident,
293 ctxt: Some(ctxt),
294 export,
295 import_source,
296 }),
297 ident => ident,
298 },
299 );
300 }
301 Some(EsmExport::Error) | None => {
302 }
305 }
306 }
307
308 let import_source = ImportSource::Module { asset: *asset };
309 Some(ReferencedAssetIdent::Module {
310 namespace_ident: import_source.get_namespace_ident(chunking_context).await?,
311 ctxt: None,
312 export: match &export {
317 Some(export) => {
318 Some(generated_export_key(*asset, chunking_context, export).await?)
319 }
320 None => None,
321 },
322 import_source,
323 })
324 }
325 ReferencedAsset::External(request, ty) => {
326 let import_source = ImportSource::External {
327 request: request.clone(),
328 ty: *ty,
329 };
330 Some(ReferencedAssetIdent::Module {
331 namespace_ident: import_source.get_namespace_ident(chunking_context).await?,
332 ctxt: None,
333 export,
334 import_source,
335 })
336 }
337 ReferencedAsset::NonPlaceable(module) => {
338 CodeGenerationIssue {
342 severity: IssueSeverity::Error,
343 title: StyledString::Text(rcstr!("non-ecmascript placeable asset"))
344 .resolved_cell(),
345 message: StyledString::Text(
346 format!(
347 "{} has no ECMAScript exports, so {} can't be read from it. It can \
348 only be imported for its side effects.",
349 module.ident().to_string().await?,
350 match &export {
351 Some(export) => format!("the export {export:?}"),
352 None => "a namespace".to_string(),
353 }
354 )
355 .into(),
356 )
357 .resolved_cell(),
358 path: module.ident().await?.path.clone(),
359 source: None,
360 }
361 .resolved_cell()
362 .emit();
363 None
364 }
365 ReferencedAsset::None | ReferencedAsset::Unresolvable => None,
366 })
367 }
368
369 pub(crate) async fn get_ident_from_placeable(
370 asset: &Vc<Box<dyn EcmascriptChunkPlaceable>>,
371 chunking_context: Vc<Box<dyn ChunkingContext>>,
372 ) -> Result<String> {
373 let id = asset.chunk_item_id(chunking_context).await?;
374 Ok(magic_identifier::mangle(&format!("imported module {id}")))
377 }
378}
379
380impl ReferencedAsset {
381 pub async fn from_resolve_result(resolve_result: Vc<ModuleResolveResult>) -> Result<Self> {
382 let result = resolve_result.await?;
384 if result.is_unresolvable() {
385 return Ok(ReferencedAsset::Unresolvable);
386 }
387 let mut non_placeable = None;
388 for (_, result) in result.primary.iter() {
389 match result {
390 ModuleResolveResultItem::External {
391 name: request, ty, ..
392 } => {
393 return Ok(ReferencedAsset::External(request.clone(), *ty));
394 }
395 ModuleResolveResultItem::Module(module) => {
396 if let Some(placeable) =
397 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(*module)
398 {
399 return Ok(ReferencedAsset::Some(placeable));
400 }
401 non_placeable = non_placeable.or(Some(*module));
402 }
403 _ => {}
405 }
406 }
407 Ok(match non_placeable {
408 Some(module) => ReferencedAsset::NonPlaceable(module),
409 None => ReferencedAsset::None,
410 })
411 }
412}
413
414#[turbo_tasks::value(transparent)]
415pub struct EsmAssetReferences(Vec<ResolvedVc<EsmAssetReference>>);
416
417#[turbo_tasks::value_impl]
418impl EsmAssetReferences {
419 #[turbo_tasks::function]
420 pub fn empty() -> Vc<Self> {
421 Vc::cell(Vec::new())
422 }
423}
424
425#[turbo_tasks::value(shared)]
426#[derive(Hash, Debug, ValueToString)]
427#[value_to_string("import {request}")]
428pub struct EsmAssetReference {
429 pub module: ResolvedVc<EcmascriptModuleAsset>,
430 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
432 pub request: RcStr,
434 pub issue_source: IssueSource,
435 pub export_name: Option<ModulePart>,
436 pub import_usage: ImportUsage,
437 pub import_externals: bool,
438 pub module_fragments_enabled: bool,
439 pub is_pure_import: bool,
440 extras: Option<Box<EsmReferenceExtras>>,
444}
445
446pub struct EsmAssetReferenceOptions {
451 pub issue_source: IssueSource,
452 pub annotations: Option<ImportAnnotations>,
453 pub export_name: Option<ModulePart>,
454 pub import_usage: ImportUsage,
455 pub import_externals: bool,
456 pub module_fragments_enabled: bool,
457 pub export_usage_passthrough: Option<bool>,
460 pub resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
461}
462
463#[derive(
467 Clone,
468 Default,
469 PartialEq,
470 Eq,
471 Hash,
472 Debug,
473 TraceRawVcs,
474 ValueDebugFormat,
475 NonLocalValue,
476 Encode,
477 Decode,
478)]
479struct EsmReferenceExtras {
480 turbopack_loader: Option<WebpackLoaderItem>,
482 turbopack_rename_as: Option<RcStr>,
484 turbopack_module_type: Option<RcStr>,
486 module_type: Option<RcStr>,
488 chunking_type: Option<SpecifiedChunkingType>,
490 export_usage_passthrough: Option<bool>,
493 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
495}
496
497fn merge_export_usage_passthrough(
498 explicit: Option<bool>,
499 annotation_passthrough: bool,
500) -> Option<bool> {
501 annotation_passthrough.then_some(true).or(explicit)
504}
505
506impl EsmReferenceExtras {
507 fn new(
510 annotations: Option<&ImportAnnotations>,
511 export_usage_passthrough: Option<bool>,
512 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
513 ) -> Option<Box<Self>> {
514 let extras = EsmReferenceExtras {
515 turbopack_loader: annotations.and_then(|a| a.turbopack_loader().cloned()),
516 turbopack_rename_as: annotations.and_then(|a| a.turbopack_rename_as().cloned()),
517 turbopack_module_type: annotations.and_then(|a| a.turbopack_module_type().cloned()),
518 module_type: annotations
519 .and_then(|a| a.module_type())
520 .map(|m| RcStr::from(&*m.to_string_lossy())),
521 chunking_type: annotations.and_then(|a| a.chunking_type()),
522 export_usage_passthrough: merge_export_usage_passthrough(
523 export_usage_passthrough,
524 annotations.is_some_and(|a| a.export_usage_passthrough()),
525 ),
526 resolve_override,
527 };
528 (extras != EsmReferenceExtras::default()).then(|| Box::new(extras))
529 }
530}
531
532#[cfg(test)]
533mod tests {
534 use super::merge_export_usage_passthrough;
535
536 #[test]
537 fn annotation_passthrough_cannot_be_weakened() {
538 assert_eq!(merge_export_usage_passthrough(None, false), None);
539 assert_eq!(
540 merge_export_usage_passthrough(Some(false), false),
541 Some(false)
542 );
543 assert_eq!(
544 merge_export_usage_passthrough(Some(true), false),
545 Some(true)
546 );
547 assert_eq!(merge_export_usage_passthrough(None, true), Some(true));
548 assert_eq!(
549 merge_export_usage_passthrough(Some(false), true),
550 Some(true)
551 );
552 assert_eq!(merge_export_usage_passthrough(Some(true), true), Some(true));
553 }
554}
555
556impl EsmAssetReference {
557 async fn new_inner(
558 module: ResolvedVc<EcmascriptModuleAsset>,
559 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
560 request: RcStr,
561 options: EsmAssetReferenceOptions,
562 is_pure_import: bool,
563 ) -> Result<Self> {
564 let EsmAssetReferenceOptions {
565 issue_source,
566 annotations,
567 export_name,
568 import_usage,
569 import_externals,
570 module_fragments_enabled,
571 export_usage_passthrough,
572 resolve_override,
573 } = options;
574
575 let origin = if let Some(transition) = annotations.as_ref().and_then(|a| a.transition()) {
578 origin
579 .with_transition(transition.into())
580 .await?
581 .to_resolved()
582 .await?
583 } else {
584 origin
585 };
586 Ok(EsmAssetReference {
587 module,
588 origin,
589 request,
590 issue_source,
591 export_name,
592 import_usage,
593 import_externals,
594 module_fragments_enabled,
595 is_pure_import,
596 extras: EsmReferenceExtras::new(
597 annotations.as_ref(),
598 export_usage_passthrough,
599 resolve_override,
600 ),
601 })
602 }
603
604 pub async fn new(
605 module: ResolvedVc<EcmascriptModuleAsset>,
606 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
607 request: RcStr,
608 options: EsmAssetReferenceOptions,
609 ) -> Result<Self> {
610 Self::new_inner(
611 module, origin, request, options, false,
612 )
613 .await
614 }
615
616 pub async fn new_pure(
617 module: ResolvedVc<EcmascriptModuleAsset>,
618 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
619 request: RcStr,
620 options: EsmAssetReferenceOptions,
621 ) -> Result<Self> {
622 Self::new_inner(
623 module, origin, request, options, true,
624 )
625 .await
626 }
627
628 pub fn rewrite_for_export(&self, export_name: ModulePart) -> Self {
635 EsmAssetReference {
636 module: self.module,
637 origin: self.origin,
638 request: self.request.clone(),
639 issue_source: self.issue_source,
640 export_name: Some(export_name),
641 import_usage: self.import_usage.clone(),
645 import_externals: self.import_externals,
646 module_fragments_enabled: self.module_fragments_enabled,
647 is_pure_import: self.is_pure_import,
648 extras: self.extras.clone(),
649 }
650 }
651
652 pub(crate) fn get_referenced_asset(
653 self: Vc<Self>,
654 ) -> impl Future<Output = Result<ReferencedAsset>> {
655 ReferencedAsset::from_resolve_result(self.resolve_reference())
656 }
657}
658
659#[turbo_tasks::value_impl]
660impl ModuleReference for EsmAssetReference {
661 #[turbo_tasks::function]
662 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
663 let extras = self.extras.as_deref();
664 if let Some(resolved) = extras.and_then(|e| e.resolve_override) {
665 return Ok(*ModuleResolveResult::module(resolved));
666 }
667 let ty = if let Some(loader) = extras.and_then(|e| e.turbopack_loader.as_ref()) {
668 let origin_ref = self.origin.into_trait_ref().await?;
670 let origin_path = origin_ref.origin_path();
671 let loader_request = Request::parse(loader.loader.clone().into());
672 let resolved = resolve(
673 origin_path.parent(),
674 ReferenceType::Loader,
675 loader_request,
676 origin_ref.resolve_options(),
677 );
678 let loader_fs_path = if let Some(source) = resolved.await?.first_source() {
679 source.ident().await?.path.clone()
680 } else {
681 bail!("Unable to resolve turbopackLoader '{}'", loader.loader);
682 };
683
684 EcmaScriptModulesReferenceSubType::ImportWithTurbopackUse {
685 loader: ResolvedWebpackLoaderItem {
686 loader: loader_fs_path,
687 options: loader.options.clone(),
688 },
689 rename_as: extras.and_then(|e| e.turbopack_rename_as.clone()),
690 module_type: extras.and_then(|e| e.turbopack_module_type.clone()),
691 }
692 } else if let Some(module_type) = extras.and_then(|e| e.module_type.as_ref()) {
693 EcmaScriptModulesReferenceSubType::ImportWithType(module_type.clone())
694 } else if let Some(part) = &self.export_name {
695 EcmaScriptModulesReferenceSubType::ImportPart(part.clone())
696 } else {
697 EcmaScriptModulesReferenceSubType::Import
698 };
699
700 let request = Request::parse(self.request.clone().into());
701
702 if self.module_fragments_enabled {
703 if let Some(ModulePart::Evaluation) = &self.export_name
704 && *self.module.side_effects().await? == ModuleSideEffects::SideEffectFree
705 {
706 return Ok(ModuleResolveResult {
707 primary: Box::new([(RequestKey::default(), ModuleResolveResultItem::Ignore)]),
708 affecting_sources: Default::default(),
709 }
710 .cell());
711 }
712
713 if let Request::Module { module, .. } = &*request.await?
714 && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
715 {
716 if let Some(part) = &self.export_name {
717 return Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
718 EcmascriptModulePartAsset::select_part(*self.module, part.clone())
719 .to_resolved()
720 .await?,
721 )));
722 }
723 bail!("export_name is required for part import")
724 }
725 }
726
727 let result = esm_resolve(
728 *self.origin,
729 request,
730 ty,
731 ResolveErrorMode::Error,
732 Some(self.issue_source),
733 )
734 .await?;
735
736 if let Some(ModulePart::Export(export_name)) = &self.export_name {
737 for &module in result.await?.primary_modules().await?.iter() {
738 if let Some(module) = ResolvedVc::try_downcast(module)
739 && *is_export_missing(*module, export_name.clone()).await?
740 {
741 InvalidExport {
742 export: export_name.clone(),
743 module,
744 source: self.issue_source,
745 }
746 .resolved_cell()
747 .emit();
748 }
749 }
750 }
751
752 Ok(result)
753 }
754
755 fn chunking_type(&self) -> Option<ChunkingType> {
756 self.extras
757 .as_deref()
758 .and_then(|e| e.chunking_type)
759 .map_or_else(
760 || {
761 Some(ChunkingType::Parallel {
762 inherit_async: true,
763 hoisted: true,
764 })
765 },
766 |c| c.as_chunking_type(true, true),
767 )
768 }
769
770 fn binding_usage(&self) -> BindingUsage {
771 let export_usage_passthrough = self
772 .extras
773 .as_deref()
774 .and_then(|extras| extras.export_usage_passthrough);
775 BindingUsage {
776 import: self.import_usage.clone(),
777 export: match (&self.export_name, export_usage_passthrough) {
778 (Some(ModulePart::Evaluation), _) => ExportUsage::Evaluation,
781 (_, Some(namespace_object_may_escape)) => ExportUsage::Passthrough {
782 namespace_object_may_escape,
783 },
784 (Some(ModulePart::Export(export_name)), _) => {
785 ExportUsage::Named(export_name.clone())
786 }
787 _ => ExportUsage::All,
788 },
789 }
790 }
791
792 fn source(&self) -> Option<IssueSource> {
793 Some(self.issue_source)
794 }
795}
796
797impl EsmAssetReference {
798 pub async fn code_generation(
799 self: ResolvedVc<Self>,
800 chunking_context: Vc<Box<dyn ChunkingContext>>,
801 scope_hoisting_context: ScopeHoistingContext<'_>,
802 ) -> Result<CodeGeneration> {
803 let this = &*self.await?;
804
805 if chunking_context
806 .unused_references()
807 .contains_key(&ResolvedVc::upcast(self))
808 .await?
809 {
810 return Ok(CodeGeneration::empty());
811 }
812
813 if this
815 .extras
816 .as_deref()
817 .and_then(|e| e.chunking_type)
818 .is_none_or(|v| v != SpecifiedChunkingType::None)
819 {
820 let import_externals = this.import_externals;
821 let referenced_asset = self.get_referenced_asset().await?;
822
823 match &referenced_asset {
824 ReferencedAsset::Unresolvable => {
825 let request = &this.request;
828 let stmt = Stmt::Expr(ExprStmt {
829 expr: Box::new(throw_module_not_found_expr(request)),
830 span: DUMMY_SP,
831 });
832 return Ok(CodeGeneration::hoisted_stmt(
833 format!("throw {request}").into(),
834 stmt,
835 ));
836 }
837 ReferencedAsset::None | ReferencedAsset::NonPlaceable(_) => {}
840 _ => {
841 let mut result = vec![];
842
843 let merged_index = if let ReferencedAsset::Some(asset) = &referenced_asset {
844 scope_hoisting_context.get_module_index(*asset)
845 } else {
846 None
847 };
848
849 if let Some(merged_index) = merged_index {
850 result.push(CodeGenerationHoistedStmt::new(
853 format!("hoisted {merged_index}").into(),
854 quote!(
855 "__turbopack_merged_esm__($id);" as Stmt,
856 id: Expr = Lit::Num(merged_index.into()).into(),
857 ),
858 ));
859 }
860
861 if merged_index.is_some()
862 && matches!(this.export_name, Some(ModulePart::Evaluation))
863 {
864 } else {
868 let ident = referenced_asset
869 .get_ident(
870 chunking_context,
871 this.export_name.as_ref().and_then(|e| match e {
872 ModulePart::Export(export_name) => Some(export_name.clone()),
873 _ => None,
874 }),
875 scope_hoisting_context,
876 )
877 .await?;
878 drop(referenced_asset);
884 match ident {
885 Some(ReferencedAssetIdent::LocalBinding { .. }) => {
886 }
888 Some(ReferencedAssetIdent::Module {
889 namespace_ident,
890 ctxt,
891 export: _,
892 import_source,
893 }) => {
894 let span = this
895 .issue_source
896 .to_swc_offsets()
897 .await?
898 .map_or(DUMMY_SP, |(start, end)| {
899 Span::new(BytePos(start), BytePos(end))
900 });
901 let name = Ident::new(
902 namespace_ident.into(),
903 DUMMY_SP,
904 ctxt.unwrap_or_default(),
905 );
906 let (key, mut call_expr) = match import_source {
907 ImportSource::Module { asset } => {
908 let id = asset.chunk_item_id(chunking_context).await?;
909 (
914 format!("{} {:?}", id, ctxt).into(),
915 quote!(
916 "$turbopack_import($id)" as Expr,
917 turbopack_import: Expr = TURBOPACK_IMPORT.into(),
918 id: Expr = module_id_to_lit(&id),
919 ),
920 )
921 }
922 ImportSource::External {
923 request,
924 ty: ExternalType::EcmaScriptModule,
925 } => {
926 if !*chunking_context
927 .environment()
928 .supports_esm_externals()
929 .await?
930 {
931 turbobail!(
932 "the chunking context ({}) does not support \
933 external modules (esm request: {request})",
934 chunking_context.name()
935 );
936 }
937 let call = if import_externals {
938 quote!(
939 "$turbopack_external_import($id)" as Expr,
940 turbopack_external_import: Expr = TURBOPACK_EXTERNAL_IMPORT.into(),
941 id: Expr = Expr::Lit(request.to_string().into())
942 )
943 } else {
944 quote!(
945 "$turbopack_external_require($id, () => require($id), true)" as Expr,
946 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
947 id: Expr = Expr::Lit(request.to_string().into())
948 )
949 };
950 (name.sym.as_str().into(), call)
951 }
952 ImportSource::External {
953 request,
954 ty: ExternalType::CommonJs | ExternalType::Url,
955 } => {
956 if !*chunking_context
957 .environment()
958 .supports_commonjs_externals()
959 .await?
960 {
961 turbobail!(
962 "the chunking context ({}) does not support \
963 external modules (request: {request})",
964 chunking_context.name()
965 );
966 }
967 let call = quote!(
968 "$turbopack_external_require($id, () => require($id), true)" as Expr,
969 turbopack_external_require: Expr = TURBOPACK_EXTERNAL_REQUIRE.into(),
970 id: Expr = Expr::Lit(request.to_string().into())
971 );
972 (name.sym.as_str().into(), call)
973 }
974 #[allow(unreachable_patterns)]
976 ImportSource::External { request, ty, .. } => {
977 bail!(
978 "Unsupported external type {:?} for ESM reference \
979 with request: {:?}",
980 ty,
981 request
982 )
983 }
984 };
985 if this.is_pure_import {
986 call_expr.set_span(PURE_SP);
987 }
988 result.push(CodeGenerationHoistedStmt::new(
989 key,
990 var_decl_with_span(
991 quote!(
992 "var $name = $call;" as Stmt,
993 name = name,
994 call: Expr = call_expr
995 ),
996 span,
997 ),
998 ));
999 }
1000 None => {
1001 }
1003 }
1004 }
1005 return Ok(CodeGeneration::hoisted_stmts(result));
1006 }
1007 }
1008 };
1009
1010 Ok(CodeGeneration::empty())
1011 }
1012}
1013
1014fn var_decl_with_span(mut decl: Stmt, span: Span) -> Stmt {
1015 match &mut decl {
1016 Stmt::Decl(Decl::Var(decl)) => decl.span = span,
1017 _ => panic!("Expected Stmt::Decl::Var"),
1018 };
1019 decl
1020}
1021
1022#[turbo_tasks::value(shared)]
1023pub struct InvalidExport {
1024 export: RcStr,
1025 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1026 source: IssueSource,
1027}
1028
1029#[async_trait]
1030#[turbo_tasks::value_impl]
1031impl Issue for InvalidExport {
1032 fn severity(&self) -> IssueSeverity {
1033 IssueSeverity::Error
1034 }
1035
1036 async fn title(&self) -> Result<StyledString> {
1037 Ok(StyledString::Line(vec![
1038 StyledString::Text(rcstr!("Export ")),
1039 StyledString::Code(self.export.clone()),
1040 StyledString::Text(rcstr!(" doesn't exist in target module")),
1041 ]))
1042 }
1043
1044 fn stage(&self) -> IssueStage {
1045 IssueStage::Bindings
1046 }
1047
1048 async fn file_path(&self) -> Result<FileSystemPath> {
1049 self.source.file_path().await
1050 }
1051
1052 async fn description(&self) -> Result<Option<StyledString>> {
1053 let export_names = all_known_export_names(*self.module).await?;
1054 let did_you_mean = export_names
1055 .iter()
1056 .map(|s| (s, jaro(self.export.as_str(), s.as_str())))
1057 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
1058 .map(|(s, _)| s);
1059 Ok(Some(StyledString::Stack(vec![
1060 StyledString::Line(vec![
1061 StyledString::Text(rcstr!("The export ")),
1062 StyledString::Code(self.export.clone()),
1063 StyledString::Text(rcstr!(" was not found in module ")),
1064 StyledString::Strong(self.module.ident().to_string().owned().await?),
1065 StyledString::Text(rcstr!(".")),
1066 ]),
1067 if let Some(did_you_mean) = did_you_mean {
1068 StyledString::Line(vec![
1069 StyledString::Text(rcstr!("Did you mean to import ")),
1070 StyledString::Code(did_you_mean.clone()),
1071 StyledString::Text(rcstr!("?")),
1072 ])
1073 } else {
1074 StyledString::Strong(rcstr!("The module has no exports at all."))
1075 },
1076 StyledString::Text(
1077 "All exports of the module are statically known (It doesn't have dynamic \
1078 exports). So it's known statically that the requested export doesn't exist."
1079 .into(),
1080 ),
1081 ])))
1082 }
1083
1084 async fn detail(&self) -> Result<Option<StyledString>> {
1085 let export_names = all_known_export_names(*self.module).await?;
1086 Ok(Some(StyledString::Line(vec![
1087 StyledString::Text(rcstr!("These are the exports of the module:\n")),
1088 StyledString::Code(
1089 export_names
1090 .iter()
1091 .map(|s| s.as_str())
1092 .intersperse(", ")
1093 .collect::<String>()
1094 .into(),
1095 ),
1096 ])))
1097 }
1098
1099 fn source(&self) -> Option<IssueSource> {
1100 Some(self.source)
1101 }
1102}
1103
1104#[turbo_tasks::value(shared)]
1105pub struct CircularReExport {
1106 export: RcStr,
1107 import: Option<RcStr>,
1108 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1109 module_cycle: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
1110}
1111
1112#[async_trait]
1113#[turbo_tasks::value_impl]
1114impl Issue for CircularReExport {
1115 fn severity(&self) -> IssueSeverity {
1116 IssueSeverity::Error
1117 }
1118
1119 async fn title(&self) -> Result<StyledString> {
1120 Ok(StyledString::Line(vec![
1121 StyledString::Text(rcstr!("Export ")),
1122 StyledString::Code(self.export.clone()),
1123 StyledString::Text(rcstr!(" is a circular re-export")),
1124 ]))
1125 }
1126
1127 fn stage(&self) -> IssueStage {
1128 IssueStage::Bindings
1129 }
1130
1131 async fn file_path(&self) -> Result<FileSystemPath> {
1132 Ok(self.module.ident().await?.path.clone())
1133 }
1134
1135 async fn description(&self) -> Result<Option<StyledString>> {
1136 Ok(Some(StyledString::Stack(vec![
1137 StyledString::Line(vec![StyledString::Text(rcstr!("The export"))]),
1138 StyledString::Line(vec![
1139 StyledString::Code(self.export.clone()),
1140 StyledString::Text(rcstr!(" of module ")),
1141 StyledString::Strong(self.module.ident().to_string().owned().await?),
1142 ]),
1143 StyledString::Line(vec![StyledString::Text(rcstr!(
1144 "is a re-export of the export"
1145 ))]),
1146 StyledString::Line(vec![
1147 StyledString::Code(self.import.clone().unwrap_or_else(|| rcstr!("*"))),
1148 StyledString::Text(rcstr!(" of module ")),
1149 StyledString::Strong(self.module_cycle.ident().to_string().owned().await?),
1150 StyledString::Text(rcstr!(".")),
1151 ]),
1152 ])))
1153 }
1154
1155 fn source(&self) -> Option<IssueSource> {
1156 None
1159 }
1160}