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_rcstr::RcStr;
14use turbo_tasks::{
15 NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc, debug::ValueDebugFormat,
16 trace::TraceRawVcs,
17};
18use turbopack_core::{
19 chunk::{ChunkingContext, ChunkingType, ModuleChunkItemIdExt},
20 issue::IssueSource,
21 reference::ModuleReference,
22 reference_type::{CommonJsReferenceSubType, EcmaScriptModulesReferenceSubType},
23 resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
24};
25use turbopack_resolve::ecmascript::{cjs_resolve, esm_resolve};
26
27use crate::{
28 ScopeHoistingContext,
29 code_gen::{CodeGen, CodeGeneration},
30 create_visitor,
31 references::{
32 AstPath,
33 esm::{EsmAssetReference, base::ReferencedAsset},
34 pattern_mapping::{PatternMapping, ResolveType},
35 },
36 runtime_functions::TURBOPACK_IMPORT,
37 utils::module_id_to_lit,
38};
39
40#[turbo_tasks::value]
44#[derive(Hash, Debug)]
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
73impl ModuleHotReferenceAssetReference {
74 pub async fn resolve(&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
97#[turbo_tasks::value_impl]
98impl ValueToString for ModuleHotReferenceAssetReference {
99 #[turbo_tasks::function]
100 async fn to_string(&self) -> Result<Vc<RcStr>> {
101 let request_str = self.request.to_string().await?;
102 Ok(Vc::cell(
103 format!("module.hot.accept/decline {}", request_str).into(),
104 ))
105 }
106}
107
108#[turbo_tasks::value_impl]
109impl ModuleReference for ModuleHotReferenceAssetReference {
110 #[turbo_tasks::function]
111 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
112 self.resolve().await
113 }
114
115 fn chunking_type(&self) -> Option<ChunkingType> {
116 Some(ChunkingType::Parallel {
117 inherit_async: false,
118 hoisted: false,
119 })
120 }
121}
122
123#[derive(
124 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
125)]
126pub struct ModuleHotReferenceCodeGen {
127 references: Vec<ResolvedVc<ModuleHotReferenceAssetReference>>,
128 esm_references: Vec<Option<ResolvedVc<EsmAssetReference>>>,
132 path: AstPath,
133}
134
135impl ModuleHotReferenceCodeGen {
136 pub fn new(
137 references: Vec<ResolvedVc<ModuleHotReferenceAssetReference>>,
138 esm_references: Vec<Option<ResolvedVc<EsmAssetReference>>>,
139 path: AstPath,
140 ) -> Self {
141 ModuleHotReferenceCodeGen {
142 references,
143 esm_references,
144 path,
145 }
146 }
147
148 pub async fn code_generation(
149 &self,
150 chunking_context: Vc<Box<dyn ChunkingContext>>,
151 scope_hoisting_context: ScopeHoistingContext<'_>,
152 ) -> Result<CodeGeneration> {
153 let resolved_ids: Vec<ReadRef<PatternMapping>> = self
154 .references
155 .iter()
156 .map(|reference| async move {
157 let r = reference.await?;
158 let resolve_result = r.resolve().await?;
159 PatternMapping::resolve_request(
160 *r.request,
161 *r.origin,
162 chunking_context,
163 resolve_result,
164 ResolveType::ChunkItem,
165 )
166 .await
167 })
168 .try_join()
169 .await?;
170
171 let esm_reimports: Vec<Option<(String, SyntaxContext, Expr)>> = self
174 .esm_references
175 .iter()
176 .map(|esm_ref| async move {
177 let Some(esm_ref) = esm_ref else {
178 return Ok(None);
179 };
180 let referenced_asset = esm_ref.get_referenced_asset().await?;
181 match &*referenced_asset {
182 ReferencedAsset::Some(asset) => {
183 let imported_module = &*referenced_asset;
184 let ident = imported_module
185 .get_ident(chunking_context, None, scope_hoisting_context)
186 .await?;
187 if let Some((namespace_ident, ctxt)) =
188 ident.and_then(|i| i.into_module_namespace_ident())
189 {
190 let id = asset.chunk_item_id(chunking_context).await?;
191 let module_id_expr = module_id_to_lit(&id);
192 return Ok(Some((
193 namespace_ident,
194 ctxt.unwrap_or_default(),
195 module_id_expr,
196 )));
197 }
198 Ok(None)
199 }
200 _ => Ok(None),
201 }
202 })
203 .try_join()
204 .await?;
205
206 let is_single = self.references.len() == 1;
207
208 let mut reimport_stmts: Vec<Stmt> = Vec::new();
210 for (namespace_ident, ctxt, module_id_expr) in esm_reimports.iter().flatten() {
211 let name = Ident::new(namespace_ident.as_str().into(), DUMMY_SP, *ctxt);
212 let turbopack_import: Expr = TURBOPACK_IMPORT.into();
213 reimport_stmts.push(quote!(
214 "$name = $turbopack_import($id);" as Stmt,
215 name = name,
216 turbopack_import: Expr = turbopack_import,
217 id: Expr = module_id_expr.clone(),
218 ));
219 }
220 let has_reimports = !reimport_stmts.is_empty();
221
222 let mut visitors = Vec::new();
223 visitors.push(create_visitor!(
224 self.path,
225 visit_mut_expr,
226 |expr: &mut Expr| {
227 if let Expr::Call(call_expr) = expr {
228 if call_expr.args.is_empty() {
229 return;
230 }
231 if is_single {
233 let key_expr = take(&mut *call_expr.args[0].expr);
234 *call_expr.args[0].expr = resolved_ids[0].create_id(key_expr);
235 } else if let Expr::Array(array_lit) = &mut *call_expr.args[0].expr {
236 for (i, elem) in array_lit.elems.iter_mut().enumerate() {
237 if let Some(elem) = elem
238 && i < resolved_ids.len()
239 {
240 let key_expr = take(&mut *elem.expr);
241 *elem.expr = resolved_ids[i].create_id(key_expr);
242 }
243 }
244 }
245
246 if has_reimports {
248 let mut wrapper_stmts = reimport_stmts.clone();
249
250 if call_expr.args.len() >= 2 {
251 let user_cb = take(&mut *call_expr.args[1].expr);
253 wrapper_stmts.push(Stmt::Expr(ExprStmt {
254 span: DUMMY_SP,
255 expr: Box::new(Expr::Call(CallExpr {
256 span: DUMMY_SP,
257 callee: Callee::Expr(Box::new(user_cb)),
258 args: vec![],
259 ..Default::default()
260 })),
261 }));
262 *call_expr.args[1].expr = Expr::Arrow(ArrowExpr {
263 span: DUMMY_SP,
264 params: vec![],
265 body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
266 span: DUMMY_SP,
267 stmts: wrapper_stmts,
268 ..Default::default()
269 })),
270 ..Default::default()
271 });
272 } else {
273 call_expr.args.push(ExprOrSpread {
275 spread: None,
276 expr: Box::new(Expr::Arrow(ArrowExpr {
277 span: DUMMY_SP,
278 params: vec![],
279 body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
280 span: DUMMY_SP,
281 stmts: wrapper_stmts,
282 ..Default::default()
283 })),
284 ..Default::default()
285 })),
286 });
287 }
288 }
289 }
290 }
291 ));
292
293 Ok(CodeGeneration::visitors(visitors))
294 }
295}
296
297impl From<ModuleHotReferenceCodeGen> for CodeGen {
298 fn from(val: ModuleHotReferenceCodeGen) -> Self {
299 CodeGen::ModuleHotReferenceCodeGen(val)
300 }
301}