turbopack_ecmascript/references/
hot_module.rs1use std::mem::take;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use swc_core::{
6 common::{DUMMY_SP, SyntaxContext},
7 ecma::ast::{
8 ArrowExpr, ArrowFunctionBody, CallExpr, Callee, Expr, ExprOrSpread, ExprStmt, FunctionBody,
9 Ident, Stmt,
10 },
11 quote,
12};
13use turbo_tasks::{
14 NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc, debug::ValueDebugFormat,
15};
16use turbopack_core::{
17 chunk::{ChunkingContext, ChunkingType, ModuleChunkItemIdExt},
18 issue::IssueSource,
19 reference::ModuleReference,
20 reference_type::{CommonJsReferenceSubType, EcmaScriptModulesReferenceSubType},
21 resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
22};
23use turbopack_resolve::ecmascript::{cjs_resolve, esm_resolve};
24
25use crate::{
26 ScopeHoistingContext,
27 ast_path_trie::{AstPathId, AstPathTrie},
28 code_gen::{CodeGen, CodeGeneration},
29 create_visitor,
30 references::{
31 esm::{EsmAssetReference, base::ReferencedAsset},
32 pattern_mapping::{PatternMapping, ResolveType},
33 },
34 runtime_functions::TURBOPACK_IMPORT,
35 utils::module_id_to_lit,
36};
37
38#[turbo_tasks::value]
42#[derive(Hash, Debug, ValueToString)]
43#[value_to_string("module.hot.accept/decline {request}")]
44pub struct ModuleHotReferenceAssetReference {
45 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
46 pub request: ResolvedVc<Request>,
47 issue_source: IssueSource,
48 error_mode: ResolveErrorMode,
49 is_esm: bool,
50}
51
52#[turbo_tasks::value_impl]
53impl ModuleHotReferenceAssetReference {
54 #[turbo_tasks::function]
55 pub fn new(
56 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
57 request: ResolvedVc<Request>,
58 issue_source: IssueSource,
59 error_mode: ResolveErrorMode,
60 is_esm: bool,
61 ) -> Vc<Self> {
62 Self::cell(ModuleHotReferenceAssetReference {
63 origin,
64 request,
65 issue_source,
66 error_mode,
67 is_esm,
68 })
69 }
70}
71
72#[turbo_tasks::value_impl]
73impl ModuleReference for ModuleHotReferenceAssetReference {
74 #[turbo_tasks::function]
75 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
76 if self.is_esm {
77 esm_resolve(
78 *self.origin,
79 *self.request,
80 EcmaScriptModulesReferenceSubType::Undefined,
81 self.error_mode,
82 Some(self.issue_source),
83 )
84 .await
85 } else {
86 Ok(cjs_resolve(
87 *self.origin,
88 *self.request,
89 CommonJsReferenceSubType::Undefined,
90 Some(self.issue_source),
91 self.error_mode,
92 ))
93 }
94 }
95
96 fn chunking_type(&self) -> Option<ChunkingType> {
97 Some(ChunkingType::Parallel {
98 inherit_async: false,
99 hoisted: false,
100 })
101 }
102}
103
104#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
105pub struct ModuleHotReferenceCodeGen {
106 references: Vec<ResolvedVc<ModuleHotReferenceAssetReference>>,
107 esm_references: Vec<Option<ResolvedVc<EsmAssetReference>>>,
111 path: AstPathId,
112}
113
114impl ModuleHotReferenceCodeGen {
115 pub fn new(
116 references: Vec<ResolvedVc<ModuleHotReferenceAssetReference>>,
117 esm_references: Vec<Option<ResolvedVc<EsmAssetReference>>>,
118 path: AstPathId,
119 ) -> Self {
120 ModuleHotReferenceCodeGen {
121 references,
122 esm_references,
123 path,
124 }
125 }
126
127 pub async fn code_generation(
128 &self,
129 trie: &AstPathTrie,
130 chunking_context: Vc<Box<dyn ChunkingContext>>,
131 scope_hoisting_context: ScopeHoistingContext<'_>,
132 ) -> Result<CodeGeneration> {
133 let resolved_ids: Vec<ReadRef<PatternMapping>> = self
134 .references
135 .iter()
136 .map(async |reference| {
137 let r = reference.await?;
138 let resolve_result = reference.resolve_reference();
139 PatternMapping::resolve_request(
140 *r.request,
141 *r.origin,
142 chunking_context,
143 resolve_result,
144 ResolveType::ChunkItem,
145 None,
146 )
147 .await
148 })
149 .try_join()
150 .await?;
151
152 let esm_reimports: Vec<Option<(String, SyntaxContext, Expr)>> = self
155 .esm_references
156 .iter()
157 .map(async |esm_ref| {
158 let Some(esm_ref) = esm_ref else {
159 return Ok(None);
160 };
161 let referenced_asset = esm_ref.get_referenced_asset().await?;
162 match &referenced_asset {
163 ReferencedAsset::Some(asset) => {
164 let ident = referenced_asset
165 .get_ident(chunking_context, None, scope_hoisting_context)
166 .await?;
167 if let Some((namespace_ident, ctxt)) =
168 ident.and_then(|i| i.into_module_namespace_ident())
169 {
170 let id = asset.chunk_item_id(chunking_context).await?;
171 let module_id_expr = module_id_to_lit(&id);
172 return Ok(Some((
173 namespace_ident,
174 ctxt.unwrap_or_default(),
175 module_id_expr,
176 )));
177 }
178 Ok(None)
179 }
180 _ => Ok(None),
181 }
182 })
183 .try_join()
184 .await?;
185
186 let is_single = self.references.len() == 1;
187
188 let mut reimport_stmts: Vec<Stmt> = Vec::new();
190 for (namespace_ident, ctxt, module_id_expr) in esm_reimports.iter().flatten() {
191 let name = Ident::new(namespace_ident.as_str().into(), DUMMY_SP, *ctxt);
192 let turbopack_import: Expr = TURBOPACK_IMPORT.into();
193 reimport_stmts.push(quote!(
194 "$name = $turbopack_import($id);" as Stmt,
195 name = name,
196 turbopack_import: Expr = turbopack_import,
197 id: Expr = module_id_expr.clone(),
198 ));
199 }
200 let has_reimports = !reimport_stmts.is_empty();
201
202 let mut visitors = Vec::new();
203 visitors.push(create_visitor!(
204 trie,
205 self.path,
206 visit_mut_expr,
207 |expr: &mut Expr| {
208 if let Expr::Call(call_expr) = expr {
209 if call_expr.args.is_empty() {
210 return;
211 }
212 if is_single {
214 let key_expr = take(&mut *call_expr.args[0].expr);
215 *call_expr.args[0].expr = resolved_ids[0].create_id(key_expr);
216 } else if let Expr::Array(array_lit) = &mut *call_expr.args[0].expr {
217 for (i, elem) in array_lit.elems.iter_mut().enumerate() {
218 if let Some(elem) = elem
219 && i < resolved_ids.len()
220 {
221 let key_expr = take(&mut *elem.expr);
222 *elem.expr = resolved_ids[i].create_id(key_expr);
223 }
224 }
225 }
226
227 if has_reimports {
229 let mut wrapper_stmts = reimport_stmts.clone();
230
231 if call_expr.args.len() >= 2 {
232 let user_cb = take(&mut *call_expr.args[1].expr);
234 wrapper_stmts.push(Stmt::Expr(ExprStmt {
235 span: DUMMY_SP,
236 expr: Box::new(Expr::Call(CallExpr {
237 span: DUMMY_SP,
238 callee: Callee::Expr(Box::new(user_cb)),
239 args: vec![],
240 ..Default::default()
241 })),
242 }));
243 *call_expr.args[1].expr = Expr::Arrow(ArrowExpr {
244 span: DUMMY_SP,
245 params: vec![],
246 body: Box::new(ArrowFunctionBody::FunctionBody(FunctionBody {
247 span: DUMMY_SP,
248 stmts: wrapper_stmts,
249 })),
250 ..Default::default()
251 });
252 } else {
253 call_expr.args.push(ExprOrSpread {
255 spread: None,
256 expr: Box::new(Expr::Arrow(ArrowExpr {
257 span: DUMMY_SP,
258 params: vec![],
259 body: Box::new(ArrowFunctionBody::FunctionBody(FunctionBody {
260 span: DUMMY_SP,
261 stmts: wrapper_stmts,
262 })),
263 ..Default::default()
264 })),
265 });
266 }
267 }
268 }
269 }
270 ));
271
272 Ok(CodeGeneration::visitors(visitors))
273 }
274}
275
276impl From<ModuleHotReferenceCodeGen> for CodeGen {
277 fn from(val: ModuleHotReferenceCodeGen) -> Self {
278 CodeGen::ModuleHotReferenceCodeGen(val)
279 }
280}