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_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
312 ResolvedVc::upcast(self.resolved_cell())
313 }
314
315 fn into_code_gen_reference(
316 self,
317 path: AstPath,
318 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
319 let reference = self.resolved_cell();
320 (
321 ResolvedVc::upcast(reference),
322 CodeGen::WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen { reference, path }),
323 )
324 }
325}
326
327#[derive(
328 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
329)]
330pub struct WorkerAssetReferenceCodeGen {
331 reference: ResolvedVc<WorkerAssetReference>,
332 path: AstPath,
333}
334
335impl WorkerAssetReferenceCodeGen {
336 pub async fn code_generation(
337 &self,
338 chunking_context: Vc<Box<dyn ChunkingContext>>,
339 ) -> Result<CodeGeneration> {
340 let reference = self.reference.await?;
341
342 let (request, request_key) = match &reference.request {
344 WorkerRequest::Url(request) => (**request, request.await?.request()),
345 WorkerRequest::Pattern { path, .. } => (Request::parse(path.owned().await?), None),
346 };
347
348 let pm = PatternMapping::resolve_request(
350 request,
351 *reference.origin,
352 chunking_context,
353 self.reference.resolve_reference(),
354 ResolveType::ChunkItem,
355 None,
356 )
357 .await?;
358
359 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
363 let message = if let Expr::New(new_expr) = expr {
364 if let Some(args) = &mut new_expr.args {
365 match args.first_mut() {
366 Some(ExprOrSpread {
367 spread: None,
368 expr: url_expr,
369 }) => {
370 let constructor = new_expr.callee.take();
372
373 let key_expr = match &request_key {
377 Some(request) => Expr::Lit(Lit::Str(request.as_str().into())),
378 None => *url_expr.take(),
379 };
380 let require_call = pm.create_require(key_expr);
381
382 let mut call_args = vec![ExprOrSpread {
384 spread: None,
385 expr: constructor,
386 }];
387 call_args.extend(args.drain(1..));
389
390 *expr = Expr::Call(CallExpr {
392 span: new_expr.span,
393 callee: Callee::Expr(Box::new(require_call)),
394 args: call_args,
395 ..Default::default()
396 });
397 return;
398 }
399 Some(ExprOrSpread {
401 spread: Some(_),
402 expr: _,
403 }) => "spread operator is illegal in new Worker() expressions.",
404 _ => "new Worker() expressions require at least 1 argument",
405 }
406 } else {
407 "new Worker() expressions require at least 1 argument"
408 }
409 } else {
410 "visitor must be executed on a NewExpr"
411 };
412 *expr = *quote_expr!(
413 "(() => { throw new Error($message); })()",
414 message: Expr = Expr::Lit(Lit::Str(message.into()))
415 );
416 });
417
418 Ok(CodeGeneration::visitors(vec![visitor]))
419 }
420}
421
422#[derive(
423 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
424)]
425pub enum WorkerGlobalPlaceholder {
426 ForwardedGlobals,
428 BasePath,
430}
431
432#[derive(
433 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode,
434)]
435pub struct WorkerGlobalsReplacementCodeGen {
436 placeholder: WorkerGlobalPlaceholder,
438 path: AstPath,
439}
440
441impl WorkerGlobalsReplacementCodeGen {
442 pub fn new(placeholder: WorkerGlobalPlaceholder, path: AstPath) -> Self {
443 WorkerGlobalsReplacementCodeGen { placeholder, path }
444 }
445
446 pub async fn code_generation(
447 &self,
448 chunking_context: Vc<Box<dyn ChunkingContext>>,
449 ) -> Result<CodeGeneration> {
450 let options = chunking_context.worker_configuration_options().await?;
451 let value: Expr = match self.placeholder {
452 WorkerGlobalPlaceholder::ForwardedGlobals => Expr::Array(ArrayLit {
453 span: DUMMY_SP,
454 elems: options
455 .forwarded_globals
456 .iter()
457 .map(|global| Some(Expr::Lit(Lit::Str(global.as_str().into())).into()))
458 .collect(),
459 }),
460 WorkerGlobalPlaceholder::BasePath => match &options.asset_prefix {
461 Some(asset_prefix) => Expr::Lit(Lit::Str(asset_prefix.as_str().into())),
462 None => Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
463 },
464 };
465
466 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
467 *expr = value.clone();
468 });
469
470 Ok(CodeGeneration::visitors(vec![visitor]))
471 }
472}
473
474impl From<WorkerGlobalsReplacementCodeGen> for CodeGen {
475 fn from(val: WorkerGlobalsReplacementCodeGen) -> Self {
476 CodeGen::WorkerGlobalsReplacementCodeGen(val)
477 }
478}