1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4 common::util::take::Take,
5 ecma::ast::{CallExpr, Expr, ExprOrSpread, Lit},
6 quote,
7};
8use turbo_tasks::{
9 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
10};
11use turbopack_core::{
12 chunk::{ChunkingContext, ChunkingType},
13 issue::IssueSource,
14 reference::ModuleReference,
15 reference_type::CommonJsReferenceSubType,
16 resolve::{ModuleResolveResult, ResolveErrorMode, origin::ResolveOrigin, parse::Request},
17};
18use turbopack_resolve::ecmascript::cjs_resolve;
19
20use crate::{
21 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
22 create_visitor,
23 references::{
24 AstPath,
25 pattern_mapping::{PatternMapping, ResolveType},
26 util::SpecifiedChunkingType,
27 },
28 runtime_functions::TURBOPACK_CACHE,
29};
30
31#[turbo_tasks::value]
32#[derive(Hash, Debug, ValueToString)]
33#[value_to_string("generic commonjs {request}")]
34pub struct CjsAssetReference {
35 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
36 pub request: ResolvedVc<Request>,
37 pub issue_source: IssueSource,
38 pub error_mode: ResolveErrorMode,
39}
40
41#[turbo_tasks::value_impl]
42impl CjsAssetReference {
43 #[turbo_tasks::function]
44 pub fn new(
45 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
46 request: ResolvedVc<Request>,
47 issue_source: IssueSource,
48 error_mode: ResolveErrorMode,
49 ) -> Result<Vc<Self>> {
50 Ok(Self::cell(CjsAssetReference {
51 origin,
52 request,
53 issue_source,
54 error_mode,
55 }))
56 }
57}
58
59#[turbo_tasks::value_impl]
60impl ModuleReference for CjsAssetReference {
61 #[turbo_tasks::function]
62 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
63 cjs_resolve(
64 *self.origin,
65 *self.request,
66 CommonJsReferenceSubType::Undefined,
67 Some(self.issue_source),
68 self.error_mode,
69 )
70 }
71
72 fn chunking_type(&self) -> Option<ChunkingType> {
73 Some(ChunkingType::Parallel {
74 inherit_async: false,
75 hoisted: false,
76 })
77 }
78}
79
80#[turbo_tasks::value]
81#[derive(Hash, Debug, ValueToString)]
82#[value_to_string("require {request}")]
83pub struct CjsRequireAssetReference {
84 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
85 request: ResolvedVc<Request>,
86 issue_source: IssueSource,
87 error_mode: ResolveErrorMode,
88 chunking_type_attribute: Option<SpecifiedChunkingType>,
89}
90
91impl CjsRequireAssetReference {
92 pub fn new(
93 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
94 request: ResolvedVc<Request>,
95 issue_source: IssueSource,
96 error_mode: ResolveErrorMode,
97 chunking_type_attribute: Option<SpecifiedChunkingType>,
98 ) -> Self {
99 CjsRequireAssetReference {
100 origin,
101 request,
102 issue_source,
103 error_mode,
104 chunking_type_attribute,
105 }
106 }
107}
108
109#[turbo_tasks::value_impl]
110impl ModuleReference for CjsRequireAssetReference {
111 #[turbo_tasks::function]
112 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
113 cjs_resolve(
114 *self.origin,
115 *self.request,
116 CommonJsReferenceSubType::Undefined,
117 Some(self.issue_source),
118 self.error_mode,
119 )
120 }
121
122 fn chunking_type(&self) -> Option<ChunkingType> {
123 self.chunking_type_attribute.map_or_else(
124 || {
125 Some(ChunkingType::Parallel {
126 inherit_async: false,
127 hoisted: false,
128 })
129 },
130 |c| c.as_chunking_type(false, false),
131 )
132 }
133}
134
135impl IntoCodeGenReference for CjsRequireAssetReference {
136 fn into_code_gen_reference(
137 self,
138 path: AstPath,
139 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
140 let reference = self.resolved_cell();
141 (
142 ResolvedVc::upcast(reference),
143 CodeGen::CjsRequireAssetReferenceCodeGen(CjsRequireAssetReferenceCodeGen {
144 reference,
145 path,
146 }),
147 )
148 }
149}
150
151#[derive(
152 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
153)]
154pub struct CjsRequireAssetReferenceCodeGen {
155 reference: ResolvedVc<CjsRequireAssetReference>,
156 path: AstPath,
157}
158
159impl CjsRequireAssetReferenceCodeGen {
160 pub async fn code_generation(
161 &self,
162 chunking_context: Vc<Box<dyn ChunkingContext>>,
163 ) -> Result<CodeGeneration> {
164 let reference = self.reference.await?;
165
166 let pm = PatternMapping::resolve_request(
167 *reference.request,
168 *reference.origin,
169 chunking_context,
170 self.reference.resolve_reference(),
171 ResolveType::ChunkItem,
172 )
173 .await?;
174 let mut visitors = Vec::new();
175
176 visitors.push(create_visitor!(
177 self.path,
178 visit_mut_expr,
179 |expr: &mut Expr| {
180 let old_expr = expr.take();
181 let message = if let Expr::Call(CallExpr { args, .. }) = old_expr {
182 match args.into_iter().next() {
183 Some(ExprOrSpread {
184 spread: None,
185 expr: key_expr,
186 }) => {
187 *expr = pm.create_require(*key_expr);
188 return;
189 }
190 Some(ExprOrSpread {
191 spread: Some(_),
192 expr: _,
193 }) => "spread operator is not analyze-able in require() expressions.",
194 _ => "require() expressions require at least 1 argument",
195 }
196 } else {
197 "visitor must be executed on a CallExpr"
198 };
199 *expr = quote!(
200 "(() => { throw new Error($message); })()" as Expr,
201 message: Expr = Expr::Lit(Lit::Str(message.into()))
202 );
203 }
204 ));
205
206 Ok(CodeGeneration::visitors(visitors))
207 }
208}
209
210#[turbo_tasks::value]
211#[derive(Hash, Debug, ValueToString)]
212#[value_to_string("require.resolve {request}")]
213pub struct CjsRequireResolveAssetReference {
214 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
215 request: ResolvedVc<Request>,
216 issue_source: IssueSource,
217 error_mode: ResolveErrorMode,
218 chunking_type_attribute: Option<SpecifiedChunkingType>,
219}
220
221impl CjsRequireResolveAssetReference {
222 pub fn new(
223 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
224 request: ResolvedVc<Request>,
225 issue_source: IssueSource,
226 error_mode: ResolveErrorMode,
227 chunking_type_attribute: Option<SpecifiedChunkingType>,
228 ) -> Self {
229 CjsRequireResolveAssetReference {
230 origin,
231 request,
232 issue_source,
233 error_mode,
234 chunking_type_attribute,
235 }
236 }
237}
238
239#[turbo_tasks::value_impl]
240impl ModuleReference for CjsRequireResolveAssetReference {
241 #[turbo_tasks::function]
242 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
243 cjs_resolve(
244 *self.origin,
245 *self.request,
246 CommonJsReferenceSubType::Undefined,
247 Some(self.issue_source),
248 self.error_mode,
249 )
250 }
251
252 fn chunking_type(&self) -> Option<ChunkingType> {
253 self.chunking_type_attribute.map_or_else(
254 || {
255 Some(ChunkingType::Parallel {
256 inherit_async: false,
257 hoisted: false,
258 })
259 },
260 |c| c.as_chunking_type(false, false),
261 )
262 }
263}
264
265impl IntoCodeGenReference for CjsRequireResolveAssetReference {
266 fn into_code_gen_reference(
267 self,
268 path: AstPath,
269 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
270 let reference = self.resolved_cell();
271 (
272 ResolvedVc::upcast(reference),
273 CodeGen::CjsRequireResolveAssetReferenceCodeGen(
274 CjsRequireResolveAssetReferenceCodeGen { reference, path },
275 ),
276 )
277 }
278}
279
280#[derive(
281 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
282)]
283pub struct CjsRequireResolveAssetReferenceCodeGen {
284 reference: ResolvedVc<CjsRequireResolveAssetReference>,
285 path: AstPath,
286}
287
288impl CjsRequireResolveAssetReferenceCodeGen {
289 pub async fn code_generation(
290 &self,
291 chunking_context: Vc<Box<dyn ChunkingContext>>,
292 ) -> Result<CodeGeneration> {
293 let reference = self.reference.await?;
294
295 let pm = PatternMapping::resolve_request(
296 *reference.request,
297 *reference.origin,
298 chunking_context,
299 self.reference.resolve_reference(),
300 ResolveType::ChunkItem,
301 )
302 .await?;
303 let mut visitors = Vec::new();
304
305 visitors.push(create_visitor!(
307 self.path,
308 visit_mut_expr,
309 |expr: &mut Expr| {
310 if let Expr::Call(call_expr) = expr {
311 let args = std::mem::take(&mut call_expr.args);
312 *expr = match args.into_iter().next() {
313 Some(ExprOrSpread { expr, spread: None }) => pm.create_id(*expr),
314 other => {
315 let message = match other {
316 Some(ExprOrSpread {
318 spread: Some(_),
319 expr: _,
320 }) => {
321 "spread operator is not analyze-able in require() expressions."
322 }
323 _ => "require() expressions require at least 1 argument",
324 };
325 quote!(
326 "(() => { throw new Error($message); })()" as Expr,
327 message: Expr = Expr::Lit(Lit::Str(message.into()))
328 )
329 }
330 };
331 }
332 }
336 ));
337
338 Ok(CodeGeneration::visitors(visitors))
339 }
340}
341
342#[derive(
343 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
344)]
345pub struct CjsRequireCacheAccess {
346 pub path: AstPath,
347}
348impl CjsRequireCacheAccess {
349 pub fn new(path: AstPath) -> Self {
350 CjsRequireCacheAccess { path }
351 }
352
353 pub async fn code_generation(
354 &self,
355 _chunking_context: Vc<Box<dyn ChunkingContext>>,
356 ) -> Result<CodeGeneration> {
357 let mut visitors = Vec::new();
358
359 visitors.push(create_visitor!(
360 self.path,
361 visit_mut_expr,
362 |expr: &mut Expr| {
363 if let Expr::Member(_) = expr {
364 *expr = TURBOPACK_CACHE.into();
365 } else {
366 unreachable!("`CjsRequireCacheAccess` is only created from `MemberExpr`");
367 }
368 }
369 ));
370
371 Ok(CodeGeneration::visitors(visitors))
372 }
373}
374
375impl From<CjsRequireCacheAccess> for CodeGen {
376 fn from(val: CjsRequireCacheAccess) -> Self {
377 CodeGen::CjsRequireCacheAccess(val)
378 }
379}