turbopack_ecmascript/references/esm/
dynamic.rs1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4 common::{DUMMY_SP, util::take::Take},
5 ecma::ast::{CallExpr, Callee, Expr, ExprOrSpread, Lit},
6 quote_expr,
7};
8use turbo_tasks::{
9 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10};
11use turbopack_core::{
12 chunk::{ChunkingContext, ChunkingType},
13 environment::ChunkLoading,
14 issue::IssueSource,
15 module::Module,
16 reference::ModuleReference,
17 reference_type::EcmaScriptModulesReferenceSubType,
18 resolve::{
19 BindingUsage, ExportUsage, ModuleResolveResult, ResolveErrorMode,
20 origin::{ResolveOrigin, ResolveOriginExt},
21 parse::Request,
22 },
23};
24use turbopack_resolve::ecmascript::esm_resolve;
25
26use crate::{
27 analyzer::imports::ImportAnnotations,
28 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
29 create_visitor,
30 references::{
31 AstPath,
32 pattern_mapping::{PatternMapping, ResolveType},
33 },
34};
35
36#[turbo_tasks::value]
37#[derive(Hash, Debug, ValueToString)]
38#[value_to_string("dynamic import {request}")]
39pub struct EsmAsyncAssetReference {
40 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
41 pub request: ResolvedVc<Request>,
42 pub annotations: ImportAnnotations,
43 pub issue_source: IssueSource,
44 pub error_mode: ResolveErrorMode,
45 pub import_externals: bool,
46 pub export_usage: ExportUsage,
50 pub resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
51}
52
53impl EsmAsyncAssetReference {
54 fn get_origin(&self) -> Vc<Box<dyn ResolveOrigin>> {
55 if let Some(transition) = self.annotations.transition() {
56 self.origin.with_transition(transition.into())
57 } else {
58 *self.origin
59 }
60 }
61}
62
63impl EsmAsyncAssetReference {
64 pub fn new(
65 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
66 request: ResolvedVc<Request>,
67 issue_source: IssueSource,
68 annotations: ImportAnnotations,
69 error_mode: ResolveErrorMode,
70 import_externals: bool,
71 export_usage: ExportUsage,
72 resolve_override: Option<ResolvedVc<Box<dyn Module>>>,
73 ) -> Self {
74 EsmAsyncAssetReference {
75 origin,
76 request,
77 issue_source,
78 annotations,
79 error_mode,
80 import_externals,
81 export_usage,
82 resolve_override,
83 }
84 }
85}
86
87#[turbo_tasks::value_impl]
88impl ModuleReference for EsmAsyncAssetReference {
89 #[turbo_tasks::function]
90 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
91 if let Some(resolved) = &self.resolve_override {
92 return Ok(*ModuleResolveResult::module(*resolved));
93 }
94
95 esm_resolve(
96 *self.get_origin().to_resolved().await?,
97 *self.request,
98 EcmaScriptModulesReferenceSubType::DynamicImport,
99 self.error_mode,
100 Some(self.issue_source),
101 )
102 .await
103 }
104
105 fn chunking_type(&self) -> Option<ChunkingType> {
106 Some(ChunkingType::Async)
107 }
108
109 fn binding_usage(&self) -> BindingUsage {
110 BindingUsage {
111 import: Default::default(),
112 export: self.export_usage.clone(),
113 }
114 }
115}
116
117impl IntoCodeGenReference for EsmAsyncAssetReference {
118 fn into_code_gen_reference(
119 self,
120 path: AstPath,
121 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
122 let reference = self.resolved_cell();
123 (
124 ResolvedVc::upcast(reference),
125 CodeGen::EsmAsyncAssetReferenceCodeGen(EsmAsyncAssetReferenceCodeGen {
126 reference,
127 path,
128 }),
129 )
130 }
131}
132
133#[derive(
134 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
135)]
136pub struct EsmAsyncAssetReferenceCodeGen {
137 path: AstPath,
138 reference: ResolvedVc<EsmAsyncAssetReference>,
139}
140
141impl EsmAsyncAssetReferenceCodeGen {
142 pub async fn code_generation(
143 &self,
144 chunking_context: Vc<Box<dyn ChunkingContext>>,
145 ) -> Result<CodeGeneration> {
146 let reference = self.reference.await?;
147
148 let pm = PatternMapping::resolve_request(
149 *reference.request,
150 *reference.origin,
151 chunking_context,
152 self.reference.resolve_reference(),
153 if matches!(
154 *chunking_context.environment().chunk_loading().await?,
155 ChunkLoading::Edge
156 ) {
157 ResolveType::ChunkItem
158 } else {
159 ResolveType::AsyncChunkLoader
160 },
161 )
162 .await?;
163
164 let import_externals = reference.import_externals;
165
166 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
167 let old_expr = expr.take();
168 let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
169 match args.into_iter().next() {
170 Some(ExprOrSpread {
171 spread: None,
172 expr: key_expr,
173 }) => {
174 *expr = pm.create_import(*key_expr, import_externals);
175 return;
176 }
177 Some(ExprOrSpread {
179 spread: Some(_),
180 expr: _,
181 }) => "spread operator is illegal in import() expressions.",
182 _ => "import() expressions require at least 1 argument",
183 }
184 } else {
185 "visitor must be executed on a CallExpr"
186 };
187 let error = quote_expr!(
188 "() => { throw new Error($message); }",
189 message: Expr = Expr::Lit(Lit::Str(message.into()))
190 );
191 *expr = Expr::Call(CallExpr {
192 callee: Callee::Expr(quote_expr!("Promise.resolve().then")),
193 args: vec![ExprOrSpread {
194 spread: None,
195 expr: error,
196 }],
197 span: DUMMY_SP,
198 ..Default::default()
199 });
200 });
201
202 Ok(CodeGeneration::visitors(vec![visitor]))
203 }
204}