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 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(async |reference| {
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 None,
148 )
149 .await
150 })
151 .try_join()
152 .await?;
153
154 let esm_reimports: Vec<Option<(String, SyntaxContext, Expr)>> = self
157 .esm_references
158 .iter()
159 .map(async |esm_ref| {
160 let Some(esm_ref) = esm_ref else {
161 return Ok(None);
162 };
163 let referenced_asset = esm_ref.get_referenced_asset().await?;
164 match &referenced_asset {
165 ReferencedAsset::Some(asset) => {
166 let ident = referenced_asset
167 .get_ident(chunking_context, None, scope_hoisting_context)
168 .await?;
169 if let Some((namespace_ident, ctxt)) =
170 ident.and_then(|i| i.into_module_namespace_ident())
171 {
172 let id = asset.chunk_item_id(chunking_context).await?;
173 let module_id_expr = module_id_to_lit(&id);
174 return Ok(Some((
175 namespace_ident,
176 ctxt.unwrap_or_default(),
177 module_id_expr,
178 )));
179 }
180 Ok(None)
181 }
182 _ => Ok(None),
183 }
184 })
185 .try_join()
186 .await?;
187
188 let is_single = self.references.len() == 1;
189
190 let mut reimport_stmts: Vec<Stmt> = Vec::new();
192 for (namespace_ident, ctxt, module_id_expr) in esm_reimports.iter().flatten() {
193 let name = Ident::new(namespace_ident.as_str().into(), DUMMY_SP, *ctxt);
194 let turbopack_import: Expr = TURBOPACK_IMPORT.into();
195 reimport_stmts.push(quote!(
196 "$name = $turbopack_import($id);" as Stmt,
197 name = name,
198 turbopack_import: Expr = turbopack_import,
199 id: Expr = module_id_expr.clone(),
200 ));
201 }
202 let has_reimports = !reimport_stmts.is_empty();
203
204 let mut visitors = Vec::new();
205 visitors.push(create_visitor!(
206 self.path,
207 visit_mut_expr,
208 |expr: &mut Expr| {
209 if let Expr::Call(call_expr) = expr {
210 if call_expr.args.is_empty() {
211 return;
212 }
213 if is_single {
215 let key_expr = take(&mut *call_expr.args[0].expr);
216 *call_expr.args[0].expr = resolved_ids[0].create_id(key_expr);
217 } else if let Expr::Array(array_lit) = &mut *call_expr.args[0].expr {
218 for (i, elem) in array_lit.elems.iter_mut().enumerate() {
219 if let Some(elem) = elem
220 && i < resolved_ids.len()
221 {
222 let key_expr = take(&mut *elem.expr);
223 *elem.expr = resolved_ids[i].create_id(key_expr);
224 }
225 }
226 }
227
228 if has_reimports {
230 let mut wrapper_stmts = reimport_stmts.clone();
231
232 if call_expr.args.len() >= 2 {
233 let user_cb = take(&mut *call_expr.args[1].expr);
235 wrapper_stmts.push(Stmt::Expr(ExprStmt {
236 span: DUMMY_SP,
237 expr: Box::new(Expr::Call(CallExpr {
238 span: DUMMY_SP,
239 callee: Callee::Expr(Box::new(user_cb)),
240 args: vec![],
241 ..Default::default()
242 })),
243 }));
244 *call_expr.args[1].expr = Expr::Arrow(ArrowExpr {
245 span: DUMMY_SP,
246 params: vec![],
247 body: Box::new(ArrowFunctionBody::FunctionBody(FunctionBody {
248 span: DUMMY_SP,
249 stmts: wrapper_stmts,
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(ArrowFunctionBody::FunctionBody(FunctionBody {
261 span: DUMMY_SP,
262 stmts: wrapper_stmts,
263 })),
264 ..Default::default()
265 })),
266 });
267 }
268 }
269 }
270 }
271 ));
272
273 Ok(CodeGeneration::visitors(visitors))
274 }
275}
276
277impl From<ModuleHotReferenceCodeGen> for CodeGen {
278 fn from(val: ModuleHotReferenceCodeGen) -> Self {
279 CodeGen::ModuleHotReferenceCodeGen(val)
280 }
281}