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