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, turbofmt,
11};
12use turbo_tasks_fs::FileSystemPath;
13use turbopack_core::{
14 chunk::{ChunkableModule, ChunkingContext, ChunkingType, EvaluatableAsset},
15 context::AssetContext,
16 issue::{IssueExt, IssueSeverity, IssueSource, StyledString, code_gen::CodeGenerationIssue},
17 module::Module,
18 reference::ModuleReference,
19 reference_type::{ReferenceType, WorkerReferenceSubType},
20 resolve::{
21 ModuleResolveResult, ModuleResolveResultItem, ResolveErrorMode,
22 error::handle_resolve_error, origin::ResolveOrigin, parse::Request, pattern::Pattern,
23 resolve_raw, url_resolve,
24 },
25};
26
27use crate::{
28 ast_path_trie::{AstPathId, AstPathTrie, AstPathTrieBuilder},
29 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
30 create_visitor,
31 references::{
32 pattern_mapping::{PatternMapping, ResolveType},
33 raw::resolve_static_files,
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 {
61 static_files_only_context_dir: Option<FileSystemPath>,
62 fallback_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 static_files_only_context_dir: Option<FileSystemPath>,
94 fallback_context_dir: FileSystemPath,
95 path: ResolvedVc<Pattern>,
96 collect_affecting_sources: bool,
97 issue_source: IssueSource,
98 error_mode: ResolveErrorMode,
99 tracing_only: bool,
100 ) -> Self {
101 WorkerAssetReference {
102 worker_type: WorkerType::NodeWorkerThread,
103 origin,
104 request: WorkerRequest::Pattern {
105 static_files_only_context_dir,
106 fallback_context_dir,
107 path,
108 collect_affecting_sources,
109 },
110 issue_source,
111 error_mode,
112 tracing_only,
113 }
114 }
115}
116
117#[turbo_tasks::value_impl]
118impl ModuleReference for WorkerAssetReference {
119 #[turbo_tasks::function]
120 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
121 let origin = self.origin.into_trait_ref().await?;
122 let asset_context = origin.asset_context();
123
124 let result = match (&self.worker_type, &self.request) {
125 (WorkerType::WebWorker | WorkerType::SharedWebWorker, WorkerRequest::Url(request)) => {
126 url_resolve(
128 *self.origin,
129 **request,
130 self.worker_type.reference_type(),
131 Some(self.issue_source),
132 self.error_mode,
133 )
134 }
135 (
136 WorkerType::NodeWorkerThread,
137 WorkerRequest::Pattern {
138 static_files_only_context_dir,
139 fallback_context_dir,
140 path,
141 collect_affecting_sources,
142 },
143 ) => {
144 let reference_type = ReferenceType::Worker(WorkerReferenceSubType::NodeWorker);
145 let mut results = Vec::new();
146 if let Some(static_files) = resolve_static_files(
147 static_files_only_context_dir,
148 *path,
149 *collect_affecting_sources,
150 )
151 .await?
152 {
153 results.push(
154 asset_context.process_resolve_result(static_files, reference_type.clone()),
155 );
156 }
157 let fallback = resolve_raw(
158 fallback_context_dir.clone(),
159 **path,
160 *collect_affecting_sources,
161 false,
162 );
163 results
164 .push(asset_context.process_resolve_result(fallback, reference_type.clone()));
165 let result = ModuleResolveResult::concat(results);
166
167 handle_resolve_error(
169 result,
170 reference_type.clone(),
171 origin.origin_path(),
172 Request::parse(path.owned().await?),
173 origin.resolve_options(),
174 self.error_mode,
175 Some(self.issue_source),
176 )
177 .await?
178 }
179 _ => {
180 unreachable!("WorkerType and WorkerRequest mismatch");
182 }
183 };
184
185 if self.tracing_only {
188 return Ok(result);
189 }
190
191 let result_ref = result.await?;
193 let mut primary = Vec::with_capacity(result_ref.primary.len());
194
195 for (request_key, resolve_item) in result_ref.primary.iter() {
196 match resolve_item {
197 ModuleResolveResultItem::Module(module) => {
198 let Some(chunkable) =
199 ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(*module)
200 else {
201 CodeGenerationIssue {
202 severity: self.get_module_type_issue_severity().await?,
203 title: StyledString::Text(rcstr!("non-chunkable module"))
204 .resolved_cell(),
205 message: StyledString::Text(
206 turbofmt!(
207 "Worker entry point module '{}' is not chunkable and cannot \
208 be used as a worker module. This may happen if the module \
209 type doesn't support bundling.",
210 module.ident()
211 )
212 .await?,
213 )
214 .resolved_cell(),
215 path: origin.origin_path(),
216 source: Some(self.issue_source),
217 }
218 .resolved_cell()
219 .emit();
220 continue;
221 };
222
223 if matches!(self.worker_type, WorkerType::NodeWorkerThread)
226 && ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(chunkable)
227 .is_none()
228 {
229 CodeGenerationIssue {
230 severity: self.get_module_type_issue_severity().await?,
231 title: StyledString::Text(rcstr!("non-evaluatable module"))
232 .resolved_cell(),
233 message: StyledString::Text(
234 turbofmt!(
235 "Worker thread entry point module '{}' must be evaluatable to \
236 serve as an entry point. This module cannot be used as a \
237 Node.js worker_threads Worker entry point because it doesn't \
238 support direct evaluation.",
239 module.ident()
240 )
241 .await?,
242 )
243 .resolved_cell(),
244 path: origin.origin_path(),
245 source: Some(self.issue_source),
246 }
247 .resolved_cell()
248 .emit();
249 continue;
250 }
251
252 let loader =
253 WorkerLoaderModule::new(*chunkable, self.worker_type, *asset_context)
254 .to_resolved()
255 .await?;
256
257 primary.push((
258 request_key.clone(),
259 ModuleResolveResultItem::Module(ResolvedVc::upcast(loader)),
260 ));
261 }
262 _ => {
264 primary.push((request_key.clone(), resolve_item.clone()));
265 }
266 }
267 }
268
269 Ok(ModuleResolveResult {
270 primary: primary.into_boxed_slice(),
271 affecting_sources: result_ref.affecting_sources.clone(),
272 }
273 .cell())
274 }
275
276 fn chunking_type(&self) -> Option<ChunkingType> {
277 Some(ChunkingType::Parallel {
278 inherit_async: false,
279 hoisted: false,
280 })
281 }
282
283 fn source(&self) -> Option<IssueSource> {
284 Some(self.issue_source)
285 }
286}
287
288impl WorkerAssetReference {
289 async fn get_module_type_issue_severity(&self) -> Result<IssueSeverity> {
291 Ok(
292 if self.error_mode != ResolveErrorMode::Error
293 || self
294 .origin
295 .into_trait_ref()
296 .await?
297 .resolve_options()
298 .await?
299 .loose_errors
300 {
301 IssueSeverity::Warning
302 } else {
303 IssueSeverity::Error
304 },
305 )
306 }
307}
308
309#[turbo_tasks::value_impl]
310impl ValueToString for WorkerAssetReference {
311 #[turbo_tasks::function]
312 async fn to_string(&self) -> Result<Vc<RcStr>> {
313 let worker_type = match self.worker_type {
314 WorkerType::WebWorker => "WebWorker",
315 WorkerType::SharedWebWorker => "SharedWorker",
316 WorkerType::NodeWorkerThread => "NodeWorkerThread",
317 };
318 let request = match &self.request {
319 WorkerRequest::Url(request) => request.to_string(),
320 WorkerRequest::Pattern { path, .. } => path.to_string(),
321 };
322 Ok(Vc::cell(turbofmt!("new {worker_type}({request})").await?))
323 }
324}
325
326impl IntoCodeGenReference for WorkerAssetReference {
327 fn into_reference(self) -> ResolvedVc<Box<dyn ModuleReference>> {
328 ResolvedVc::upcast(self.resolved_cell())
329 }
330
331 fn into_code_gen_reference(
332 self,
333 _trie: &AstPathTrieBuilder,
334 path: AstPathId,
335 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
336 let reference = self.resolved_cell();
337 (
338 ResolvedVc::upcast(reference),
339 CodeGen::WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen { reference, path }),
340 )
341 }
342}
343
344#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode)]
345pub struct WorkerAssetReferenceCodeGen {
346 reference: ResolvedVc<WorkerAssetReference>,
347 path: AstPathId,
348}
349
350impl WorkerAssetReferenceCodeGen {
351 pub async fn code_generation(
352 &self,
353 trie: &AstPathTrie,
354 chunking_context: Vc<Box<dyn ChunkingContext>>,
355 ) -> Result<CodeGeneration> {
356 let reference = self.reference.await?;
357
358 let (request, request_key) = match &reference.request {
360 WorkerRequest::Url(request) => (**request, request.await?.request()),
361 WorkerRequest::Pattern { path, .. } => (Request::parse(path.owned().await?), None),
362 };
363
364 let pm = PatternMapping::resolve_request(
366 request,
367 *reference.origin,
368 chunking_context,
369 self.reference.resolve_reference(),
370 ResolveType::ChunkItem,
371 None,
372 )
373 .await?;
374
375 let visitor = create_visitor!(trie, self.path, visit_mut_expr, |expr: &mut Expr| {
379 let message = if let Expr::New(new_expr) = expr {
380 if let Some(args) = &mut new_expr.args {
381 match args.first_mut() {
382 Some(ExprOrSpread {
383 spread: None,
384 expr: url_expr,
385 }) => {
386 let constructor = new_expr.callee.take();
388
389 let key_expr = match &request_key {
393 Some(request) => Expr::Lit(Lit::Str(request.as_str().into())),
394 None => *url_expr.take(),
395 };
396 let require_call = pm.create_require(key_expr);
397
398 let mut call_args = vec![ExprOrSpread {
400 spread: None,
401 expr: constructor,
402 }];
403 call_args.extend(args.drain(1..));
405
406 *expr = Expr::Call(CallExpr {
408 span: new_expr.span,
409 callee: Callee::Expr(Box::new(require_call)),
410 args: call_args,
411 ..Default::default()
412 });
413 return;
414 }
415 Some(ExprOrSpread {
417 spread: Some(_),
418 expr: _,
419 }) => "spread operator is illegal in new Worker() expressions.",
420 _ => "new Worker() expressions require at least 1 argument",
421 }
422 } else {
423 "new Worker() expressions require at least 1 argument"
424 }
425 } else {
426 "visitor must be executed on a NewExpr"
427 };
428 *expr = *quote_expr!(
429 "(() => { throw new Error($message); })()",
430 message: Expr = Expr::Lit(Lit::Str(message.into()))
431 );
432 });
433
434 Ok(CodeGeneration::visitors(vec![visitor]))
435 }
436}
437
438#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode)]
439pub enum WorkerGlobalPlaceholder {
440 ForwardedGlobals,
442 BasePath,
444}
445
446#[derive(PartialEq, Eq, ValueDebugFormat, NonLocalValue, Debug, Hash, Encode, Decode)]
447pub struct WorkerGlobalsReplacementCodeGen {
448 placeholder: WorkerGlobalPlaceholder,
450 path: AstPathId,
451}
452
453impl WorkerGlobalsReplacementCodeGen {
454 pub fn new(placeholder: WorkerGlobalPlaceholder, path: AstPathId) -> Self {
455 WorkerGlobalsReplacementCodeGen { placeholder, path }
456 }
457
458 pub async fn code_generation(
459 &self,
460 trie: &AstPathTrie,
461 chunking_context: Vc<Box<dyn ChunkingContext>>,
462 ) -> Result<CodeGeneration> {
463 let options = chunking_context.worker_configuration_options().await?;
464 let value: Expr = match self.placeholder {
465 WorkerGlobalPlaceholder::ForwardedGlobals => Expr::Array(ArrayLit {
466 span: DUMMY_SP,
467 elems: options
468 .forwarded_globals
469 .iter()
470 .map(|global| Some(Expr::Lit(Lit::Str(global.as_str().into())).into()))
471 .collect(),
472 }),
473 WorkerGlobalPlaceholder::BasePath => match &options.asset_prefix {
474 Some(asset_prefix) => Expr::Lit(Lit::Str(asset_prefix.as_str().into())),
475 None => Expr::Lit(Lit::Null(Null { span: DUMMY_SP })),
476 },
477 };
478
479 let visitor = create_visitor!(trie, self.path, visit_mut_expr, |expr: &mut Expr| {
480 *expr = value.clone();
481 });
482
483 Ok(CodeGeneration::visitors(vec![visitor]))
484 }
485}
486
487impl From<WorkerGlobalsReplacementCodeGen> for CodeGen {
488 fn from(val: WorkerGlobalsReplacementCodeGen) -> Self {
489 CodeGen::WorkerGlobalsReplacementCodeGen(val)
490 }
491}