1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4 common::{DUMMY_SP, util::take::Take},
5 ecma::ast::{ArrayLit, CallExpr, Callee, Expr, ExprOrSpread, Lit, Null},
6 quote_expr,
7};
8use turbo_rcstr::{RcStr, rcstr};
9use turbo_tasks::{
10 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
11 turbofmt,
12};
13use turbo_tasks_fs::FileSystemPath;
14use turbopack_core::{
15 chunk::{ChunkableModule, ChunkingContext, ChunkingType, EvaluatableAsset},
16 context::AssetContext,
17 issue::{IssueExt, IssueSeverity, IssueSource, StyledString, code_gen::CodeGenerationIssue},
18 module::Module,
19 reference::ModuleReference,
20 reference_type::{ReferenceType, WorkerReferenceSubType},
21 resolve::{
22 ModuleResolveResult, ModuleResolveResultItem, ResolveErrorMode,
23 error::handle_resolve_error, origin::ResolveOrigin, parse::Request, pattern::Pattern,
24 resolve_raw, url_resolve,
25 },
26};
27
28use crate::{
29 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
30 create_visitor,
31 references::{
32 AstPath,
33 pattern_mapping::{PatternMapping, ResolveType},
34 },
35 worker_chunk::{WorkerType, module::WorkerLoaderModule},
36};
37
38#[turbo_tasks::value]
41#[derive(Hash, Debug)]
42pub struct WorkerAssetReference {
43 pub worker_type: WorkerType,
44 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
45 pub request: WorkerRequest,
46 pub issue_source: IssueSource,
47 pub error_mode: ResolveErrorMode,
48 pub tracing_only: bool,
51}
52
53#[turbo_tasks::value]
55#[derive(Hash, Debug, Clone)]
56pub enum WorkerRequest {
57 Url(ResolvedVc<Request>),
59 Pattern {
62 context_dir: FileSystemPath,
63 path: ResolvedVc<Pattern>,
64 collect_affecting_sources: bool,
65 },
66}
67
68impl WorkerAssetReference {
69 pub fn new_web_worker(
70 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
71 request: ResolvedVc<Request>,
72 issue_source: IssueSource,
73 error_mode: ResolveErrorMode,
74 tracing_only: bool,
75 is_shared: bool,
76 ) -> Self {
77 WorkerAssetReference {
78 worker_type: if is_shared {
79 WorkerType::SharedWebWorker
80 } else {
81 WorkerType::WebWorker
82 },
83 origin,
84 request: WorkerRequest::Url(request),
85 issue_source,
86 error_mode,
87 tracing_only,
88 }
89 }
90
91 pub fn new_node_worker_thread(
92 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
93 context_dir: FileSystemPath,
94 path: ResolvedVc<Pattern>,
95 collect_affecting_sources: bool,
96 issue_source: IssueSource,
97 error_mode: ResolveErrorMode,
98 tracing_only: bool,
99 ) -> Self {
100 WorkerAssetReference {
101 worker_type: WorkerType::NodeWorkerThread,
102 origin,
103 request: WorkerRequest::Pattern {
104 context_dir,
105 path,
106 collect_affecting_sources,
107 },
108 issue_source,
109 error_mode,
110 tracing_only,
111 }
112 }
113}
114
115#[turbo_tasks::value_impl]
116impl ModuleReference for WorkerAssetReference {
117 #[turbo_tasks::function]
118 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
119 let origin = self.origin.into_trait_ref().await?;
120 let asset_context = origin.asset_context();
121
122 let result = match (&self.worker_type, &self.request) {
123 (WorkerType::WebWorker | WorkerType::SharedWebWorker, WorkerRequest::Url(request)) => {
124 url_resolve(
126 *self.origin,
127 **request,
128 self.worker_type.reference_type(),
129 Some(self.issue_source),
130 self.error_mode,
131 )
132 }
133 (
134 WorkerType::NodeWorkerThread,
135 WorkerRequest::Pattern {
136 context_dir,
137 path,
138 collect_affecting_sources,
139 },
140 ) => {
141 let result = resolve_raw(
143 context_dir.clone(),
144 **path,
145 *collect_affecting_sources,
146 false,
147 );
148 let reference_type = ReferenceType::Worker(WorkerReferenceSubType::NodeWorker);
149 let result = asset_context.process_resolve_result(result, reference_type.clone());
150
151 handle_resolve_error(
153 result,
154 reference_type.clone(),
155 origin.origin_path(),
156 Request::parse(path.owned().await?),
157 origin.resolve_options(),
158 self.error_mode,
159 Some(self.issue_source),
160 )
161 .await?
162 }
163 _ => {
164 unreachable!("WorkerType and WorkerRequest mismatch");
166 }
167 };
168
169 if self.tracing_only {
172 return Ok(result);
173 }
174
175 let result_ref = result.await?;
177 let mut primary = Vec::with_capacity(result_ref.primary.len());
178
179 for (request_key, resolve_item) in result_ref.primary.iter() {
180 match resolve_item {
181 ModuleResolveResultItem::Module(module) => {
182 let Some(chunkable) =
183 ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(*module)
184 else {
185 CodeGenerationIssue {
186 severity: self.get_module_type_issue_severity().await?,
187 title: StyledString::Text(rcstr!("non-chunkable module"))
188 .resolved_cell(),
189 message: StyledString::Text(
190 turbofmt!(
191 "Worker entry point module '{}' is not chunkable and cannot \
192 be used as a worker module. This may happen if the module \
193 type doesn't support bundling.",
194 module.ident()
195 )
196 .await?,
197 )
198 .resolved_cell(),
199 path: origin.origin_path(),
200 source: Some(self.issue_source),
201 }
202 .resolved_cell()
203 .emit();
204 continue;
205 };
206
207 if matches!(self.worker_type, WorkerType::NodeWorkerThread)
210 && ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(chunkable)
211 .is_none()
212 {
213 CodeGenerationIssue {
214 severity: self.get_module_type_issue_severity().await?,
215 title: StyledString::Text(rcstr!("non-evaluatable module"))
216 .resolved_cell(),
217 message: StyledString::Text(
218 turbofmt!(
219 "Worker thread entry point module '{}' must be evaluatable to \
220 serve as an entry point. This module cannot be used as a \
221 Node.js worker_threads Worker entry point because it doesn't \
222 support direct evaluation.",
223 module.ident()
224 )
225 .await?,
226 )
227 .resolved_cell(),
228 path: origin.origin_path(),
229 source: Some(self.issue_source),
230 }
231 .resolved_cell()
232 .emit();
233 continue;
234 }
235
236 let loader =
237 WorkerLoaderModule::new(*chunkable, self.worker_type, *asset_context)
238 .to_resolved()
239 .await?;
240
241 primary.push((
242 request_key.clone(),
243 ModuleResolveResultItem::Module(ResolvedVc::upcast(loader)),
244 ));
245 }
246 _ => {
248 primary.push((request_key.clone(), resolve_item.clone()));
249 }
250 }
251 }
252
253 Ok(ModuleResolveResult {
254 primary: primary.into_boxed_slice(),
255 affecting_sources: result_ref.affecting_sources.clone(),
256 }
257 .cell())
258 }
259
260 fn chunking_type(&self) -> Option<ChunkingType> {
261 Some(ChunkingType::Parallel {
262 inherit_async: false,
263 hoisted: false,
264 })
265 }
266
267 fn source(&self) -> Option<IssueSource> {
268 Some(self.issue_source)
269 }
270}
271
272impl WorkerAssetReference {
273 async fn get_module_type_issue_severity(&self) -> Result<IssueSeverity> {
275 Ok(
276 if self.error_mode != ResolveErrorMode::Error
277 || self
278 .origin
279 .into_trait_ref()
280 .await?
281 .resolve_options()
282 .await?
283 .loose_errors
284 {
285 IssueSeverity::Warning
286 } else {
287 IssueSeverity::Error
288 },
289 )
290 }
291}
292
293#[turbo_tasks::value_impl]
294impl ValueToString for WorkerAssetReference {
295 #[turbo_tasks::function]
296 async fn to_string(&self) -> Result<Vc<RcStr>> {
297 let worker_type = match self.worker_type {
298 WorkerType::WebWorker => "WebWorker",
299 WorkerType::SharedWebWorker => "SharedWorker",
300 WorkerType::NodeWorkerThread => "NodeWorkerThread",
301 };
302 let request = match &self.request {
303 WorkerRequest::Url(request) => request.to_string(),
304 WorkerRequest::Pattern { path, .. } => path.to_string(),
305 };
306 Ok(Vc::cell(turbofmt!("new {worker_type}({request})").await?))
307 }
308}
309
310impl IntoCodeGenReference for WorkerAssetReference {
311 fn into_code_gen_reference(
312 self,
313 path: AstPath,
314 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
315 let reference = self.resolved_cell();
316 (
317 ResolvedVc::upcast(reference),
318 CodeGen::WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen { reference, path }),
319 )
320 }
321}
322
323#[derive(
324 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
325)]
326pub struct WorkerAssetReferenceCodeGen {
327 reference: ResolvedVc<WorkerAssetReference>,
328 path: AstPath,
329}
330
331impl WorkerAssetReferenceCodeGen {
332 pub async fn code_generation(
333 &self,
334 chunking_context: Vc<Box<dyn ChunkingContext>>,
335 ) -> Result<CodeGeneration> {
336 let reference = self.reference.await?;
337
338 let request = match &reference.request {
340 WorkerRequest::Url(request) => **request,
341 WorkerRequest::Pattern { path, .. } => Request::parse(path.owned().await?),
342 };
343
344 let pm = PatternMapping::resolve_request(
346 request,
347 *reference.origin,
348 chunking_context,
349 self.reference.resolve_reference(),
350 ResolveType::ChunkItem,
351 )
352 .await?;
353
354 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
358 let message = if let Expr::New(new_expr) = expr {
359 if let Some(args) = &mut new_expr.args {
360 match args.first_mut() {
361 Some(ExprOrSpread {
362 spread: None,
363 expr: url_expr,
364 }) => {
365 let constructor = new_expr.callee.take();
367
368 let require_call = pm.create_require(*url_expr.take());
370
371 let mut call_args = vec![ExprOrSpread {
373 spread: None,
374 expr: constructor,
375 }];
376 call_args.extend(args.drain(1..));
378
379 *expr = Expr::Call(CallExpr {
381 span: new_expr.span,
382 callee: Callee::Expr(Box::new(require_call)),
383 args: call_args,
384 ..Default::default()
385 });
386 return;
387 }
388 Some(ExprOrSpread {
390 spread: Some(_),
391 expr: _,
392 }) => "spread operator is illegal in new Worker() expressions.",
393 _ => "new Worker() expressions require at least 1 argument",
394 }
395 } else {
396 "new Worker() expressions require at least 1 argument"
397 }
398 } else {
399 "visitor must be executed on a NewExpr"
400 };
401 *expr = *quote_expr!(
402 "(() => { throw new Error($message); })()",
403 message: Expr = Expr::Lit(Lit::Str(message.into()))
404 );
405 });
406
407 Ok(CodeGeneration::visitors(vec![visitor]))
408 }
409}
410
411#[derive(
412 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
413)]
414pub enum WorkerGlobalPlaceholder {
415 ForwardedGlobals,
417 BasePath,
419}
420
421#[derive(
422 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
423)]
424pub struct WorkerGlobalsReplacementCodeGen {
425 placeholder: WorkerGlobalPlaceholder,
427 path: AstPath,
428}
429
430impl WorkerGlobalsReplacementCodeGen {
431 pub fn new(placeholder: WorkerGlobalPlaceholder, path: AstPath) -> Self {
432 WorkerGlobalsReplacementCodeGen { placeholder, path }
433 }
434
435 pub async fn code_generation(
436 &self,
437 chunking_context: Vc<Box<dyn ChunkingContext>>,
438 ) -> Result<CodeGeneration> {
439 let options = chunking_context.worker_configuration_options().await?;
440 let value: Expr = match self.placeholder {
441 WorkerGlobalPlaceholder::ForwardedGlobals => Expr::Array(ArrayLit {
442 span: DUMMY_SP,
443 elems: options
444 .forwarded_globals
445 .iter()
446 .map(|global| Some(Expr::Lit(Lit::Str(global.as_str().into())).into()))
447 .collect(),
448 }),
449 WorkerGlobalPlaceholder::BasePath => match &options.asset_prefix {
450 Some(asset_prefix) => Expr::Lit(Lit::Str(asset_prefix.as_str().into())),
451 None => Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
452 },
453 };
454
455 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
456 *expr = value.clone();
457 });
458
459 Ok(CodeGeneration::visitors(vec![visitor]))
460 }
461}
462
463impl From<WorkerGlobalsReplacementCodeGen> for CodeGen {
464 fn from(val: WorkerGlobalsReplacementCodeGen) -> Self {
465 CodeGen::WorkerGlobalsReplacementCodeGen(val)
466 }
467}