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