1use anyhow::Result;
2use bincode::{Decode, Encode};
3use swc_core::{
4 common::util::take::Take,
5 ecma::ast::{CallExpr, Callee, Expr, ExprOrSpread, Lit},
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 asset_context = self.origin.asset_context().to_resolved().await?;
120
121 let result = match (&self.worker_type, &self.request) {
122 (WorkerType::WebWorker | WorkerType::SharedWebWorker, WorkerRequest::Url(request)) => {
123 url_resolve(
125 *self.origin,
126 **request,
127 self.worker_type.reference_type(),
128 Some(self.issue_source),
129 self.error_mode,
130 )
131 }
132 (
133 WorkerType::NodeWorkerThread,
134 WorkerRequest::Pattern {
135 context_dir,
136 path,
137 collect_affecting_sources,
138 },
139 ) => {
140 let result = resolve_raw(
142 context_dir.clone(),
143 **path,
144 *collect_affecting_sources,
145 false,
146 );
147 let reference_type = ReferenceType::Worker(WorkerReferenceSubType::NodeWorker);
148 let result = asset_context.process_resolve_result(result, reference_type.clone());
149
150 handle_resolve_error(
152 result,
153 reference_type.clone(),
154 *self.origin,
155 Request::parse(path.owned().await?),
156 self.origin.resolve_options(),
157 self.error_mode,
158 Some(self.issue_source),
159 )
160 .await?
161 }
162 _ => {
163 unreachable!("WorkerType and WorkerRequest mismatch");
165 }
166 };
167
168 if self.tracing_only {
171 return Ok(result);
172 }
173
174 let result_ref = result.await?;
176 let mut primary = Vec::with_capacity(result_ref.primary.len());
177
178 for (request_key, resolve_item) in result_ref.primary.iter() {
179 match resolve_item {
180 ModuleResolveResultItem::Module(module) => {
181 let Some(chunkable) =
182 ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(*module)
183 else {
184 CodeGenerationIssue {
185 severity: self.get_module_type_issue_severity().await?,
186 title: StyledString::Text(rcstr!("non-chunkable module"))
187 .resolved_cell(),
188 message: StyledString::Text(
189 turbofmt!(
190 "Worker entry point module '{}' is not chunkable and cannot \
191 be used as a worker module. This may happen if the module \
192 type doesn't support bundling.",
193 module.ident()
194 )
195 .await?,
196 )
197 .resolved_cell(),
198 path: self.origin.origin_path().owned().await?,
199 source: Some(self.issue_source),
200 }
201 .resolved_cell()
202 .emit();
203 continue;
204 };
205
206 if matches!(self.worker_type, WorkerType::NodeWorkerThread)
209 && ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(chunkable)
210 .is_none()
211 {
212 CodeGenerationIssue {
213 severity: self.get_module_type_issue_severity().await?,
214 title: StyledString::Text(rcstr!("non-evaluatable module"))
215 .resolved_cell(),
216 message: StyledString::Text(
217 turbofmt!(
218 "Worker thread entry point module '{}' must be evaluatable to \
219 serve as an entry point. This module cannot be used as a \
220 Node.js worker_threads Worker entry point because it doesn't \
221 support direct evaluation.",
222 module.ident()
223 )
224 .await?,
225 )
226 .resolved_cell(),
227 path: self.origin.origin_path().owned().await?,
228 source: Some(self.issue_source),
229 }
230 .resolved_cell()
231 .emit();
232 continue;
233 }
234
235 let loader =
236 WorkerLoaderModule::new(*chunkable, self.worker_type, *asset_context)
237 .to_resolved()
238 .await?;
239
240 primary.push((
241 request_key.clone(),
242 ModuleResolveResultItem::Module(ResolvedVc::upcast(loader)),
243 ));
244 }
245 _ => {
247 primary.push((request_key.clone(), resolve_item.clone()));
248 }
249 }
250 }
251
252 Ok(ModuleResolveResult {
253 primary: primary.into_boxed_slice(),
254 affecting_sources: result_ref.affecting_sources.clone(),
255 }
256 .cell())
257 }
258
259 fn chunking_type(&self) -> Option<ChunkingType> {
260 Some(ChunkingType::Parallel {
261 inherit_async: false,
262 hoisted: false,
263 })
264 }
265}
266
267impl WorkerAssetReference {
268 async fn get_module_type_issue_severity(&self) -> Result<IssueSeverity> {
270 Ok(
271 if self.error_mode != ResolveErrorMode::Error
272 || self.origin.resolve_options().await?.loose_errors
273 {
274 IssueSeverity::Warning
275 } else {
276 IssueSeverity::Error
277 },
278 )
279 }
280}
281
282#[turbo_tasks::value_impl]
283impl ValueToString for WorkerAssetReference {
284 #[turbo_tasks::function]
285 async fn to_string(&self) -> Result<Vc<RcStr>> {
286 let worker_type = match self.worker_type {
287 WorkerType::WebWorker => "WebWorker",
288 WorkerType::SharedWebWorker => "SharedWorker",
289 WorkerType::NodeWorkerThread => "NodeWorkerThread",
290 };
291 let request = match &self.request {
292 WorkerRequest::Url(request) => request.to_string(),
293 WorkerRequest::Pattern { path, .. } => path.to_string(),
294 };
295 Ok(Vc::cell(turbofmt!("new {worker_type}({request})").await?))
296 }
297}
298
299impl IntoCodeGenReference for WorkerAssetReference {
300 fn into_code_gen_reference(
301 self,
302 path: AstPath,
303 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
304 let reference = self.resolved_cell();
305 (
306 ResolvedVc::upcast(reference),
307 CodeGen::WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen { reference, path }),
308 )
309 }
310}
311
312#[derive(
313 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
314)]
315pub struct WorkerAssetReferenceCodeGen {
316 reference: ResolvedVc<WorkerAssetReference>,
317 path: AstPath,
318}
319
320impl WorkerAssetReferenceCodeGen {
321 pub async fn code_generation(
322 &self,
323 chunking_context: Vc<Box<dyn ChunkingContext>>,
324 ) -> Result<CodeGeneration> {
325 let reference = self.reference.await?;
326
327 let request = match &reference.request {
329 WorkerRequest::Url(request) => **request,
330 WorkerRequest::Pattern { path, .. } => Request::parse(path.owned().await?),
331 };
332
333 let pm = PatternMapping::resolve_request(
335 request,
336 *reference.origin,
337 chunking_context,
338 self.reference.resolve_reference(),
339 ResolveType::ChunkItem,
340 )
341 .await?;
342
343 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
347 let message = if let Expr::New(new_expr) = expr {
348 if let Some(args) = &mut new_expr.args {
349 match args.first_mut() {
350 Some(ExprOrSpread {
351 spread: None,
352 expr: url_expr,
353 }) => {
354 let constructor = new_expr.callee.take();
356
357 let require_call = pm.create_require(*url_expr.take());
359
360 let mut call_args = vec![ExprOrSpread {
362 spread: None,
363 expr: constructor,
364 }];
365 call_args.extend(args.drain(1..));
367
368 *expr = Expr::Call(CallExpr {
370 span: new_expr.span,
371 callee: Callee::Expr(Box::new(require_call)),
372 args: call_args,
373 ..Default::default()
374 });
375 return;
376 }
377 Some(ExprOrSpread {
379 spread: Some(_),
380 expr: _,
381 }) => "spread operator is illegal in new Worker() expressions.",
382 _ => "new Worker() expressions require at least 1 argument",
383 }
384 } else {
385 "new Worker() expressions require at least 1 argument"
386 }
387 } else {
388 "visitor must be executed on a NewExpr"
389 };
390 *expr = *quote_expr!(
391 "(() => { throw new Error($message); })()",
392 message: Expr = Expr::Lit(Lit::Str(message.into()))
393 );
394 });
395
396 Ok(CodeGeneration::visitors(vec![visitor]))
397 }
398}