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};
12use turbo_tasks_fs::FileSystemPath;
13use turbopack_core::{
14 chunk::{ChunkableModule, ChunkableModuleReference, ChunkingContext, 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, handle_resolve_error,
22 origin::ResolveOrigin, parse::Request, pattern::Pattern, resolve_raw, url_resolve,
23 },
24};
25
26use crate::{
27 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
28 create_visitor,
29 references::{
30 AstPath,
31 pattern_mapping::{PatternMapping, ResolveType},
32 },
33 worker_chunk::{WorkerType, module::WorkerLoaderModule},
34};
35
36#[turbo_tasks::value]
39#[derive(Hash, Debug)]
40pub struct WorkerAssetReference {
41 pub worker_type: WorkerType,
42 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
43 pub request: WorkerRequest,
44 pub issue_source: IssueSource,
45 pub error_mode: ResolveErrorMode,
46 pub tracing_only: bool,
49}
50
51#[turbo_tasks::value]
53#[derive(Hash, Debug, Clone)]
54pub enum WorkerRequest {
55 Url(ResolvedVc<Request>),
57 Pattern {
60 context_dir: FileSystemPath,
61 path: ResolvedVc<Pattern>,
62 collect_affecting_sources: bool,
63 },
64}
65
66impl WorkerAssetReference {
67 pub fn new_web_worker(
68 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
69 request: ResolvedVc<Request>,
70 issue_source: IssueSource,
71 error_mode: ResolveErrorMode,
72 tracing_only: bool,
73 is_shared: bool,
74 ) -> Self {
75 WorkerAssetReference {
76 worker_type: if is_shared {
77 WorkerType::SharedWebWorker
78 } else {
79 WorkerType::WebWorker
80 },
81 origin,
82 request: WorkerRequest::Url(request),
83 issue_source,
84 error_mode,
85 tracing_only,
86 }
87 }
88
89 pub fn new_node_worker_thread(
90 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
91 context_dir: FileSystemPath,
92 path: ResolvedVc<Pattern>,
93 collect_affecting_sources: bool,
94 issue_source: IssueSource,
95 error_mode: ResolveErrorMode,
96 tracing_only: bool,
97 ) -> Self {
98 WorkerAssetReference {
99 worker_type: WorkerType::NodeWorkerThread,
100 origin,
101 request: WorkerRequest::Pattern {
102 context_dir,
103 path,
104 collect_affecting_sources,
105 },
106 issue_source,
107 error_mode,
108 tracing_only,
109 }
110 }
111}
112
113#[turbo_tasks::value_impl]
114impl ModuleReference for WorkerAssetReference {
115 #[turbo_tasks::function]
116 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
117 let asset_context = self.origin.asset_context().to_resolved().await?;
118
119 let result = match (&self.worker_type, &self.request) {
120 (WorkerType::WebWorker | WorkerType::SharedWebWorker, WorkerRequest::Url(request)) => {
121 url_resolve(
123 *self.origin,
124 **request,
125 self.worker_type.reference_type(),
126 Some(self.issue_source),
127 self.error_mode,
128 )
129 }
130 (
131 WorkerType::NodeWorkerThread,
132 WorkerRequest::Pattern {
133 context_dir,
134 path,
135 collect_affecting_sources,
136 },
137 ) => {
138 let result = resolve_raw(
140 context_dir.clone(),
141 **path,
142 *collect_affecting_sources,
143 false,
144 );
145 let reference_type = ReferenceType::Worker(WorkerReferenceSubType::NodeWorker);
146 let result = asset_context.process_resolve_result(result, reference_type.clone());
147
148 handle_resolve_error(
150 result,
151 reference_type.clone(),
152 *self.origin,
153 Request::parse(path.owned().await?),
154 self.origin.resolve_options(),
155 self.error_mode,
156 Some(self.issue_source),
157 )
158 .await?
159 }
160 _ => {
161 unreachable!("WorkerType and WorkerRequest mismatch");
163 }
164 };
165
166 if self.tracing_only {
169 return Ok(result);
170 }
171
172 let result_ref = result.await?;
174 let mut primary = Vec::with_capacity(result_ref.primary.len());
175
176 for (request_key, resolve_item) in result_ref.primary.iter() {
177 match resolve_item {
178 ModuleResolveResultItem::Module(module) => {
179 let module_ident = module.ident().to_string().await?;
180
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 format!(
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 .into(),
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 format!(
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 .into(),
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
260impl WorkerAssetReference {
261 async fn get_module_type_issue_severity(&self) -> Result<IssueSeverity> {
263 Ok(
264 if self.error_mode != ResolveErrorMode::Error
265 || self.origin.resolve_options().await?.loose_errors
266 {
267 IssueSeverity::Warning
268 } else {
269 IssueSeverity::Error
270 },
271 )
272 }
273}
274
275#[turbo_tasks::value_impl]
276impl ValueToString for WorkerAssetReference {
277 #[turbo_tasks::function]
278 async fn to_string(&self) -> Result<Vc<RcStr>> {
279 Ok(Vc::cell(
280 format!(
281 "new {}({})",
282 match self.worker_type {
283 WorkerType::WebWorker => "WebWorker",
284 WorkerType::SharedWebWorker => "SharedWorker",
285 WorkerType::NodeWorkerThread => "NodeWorkerThread",
286 },
287 match &self.request {
288 WorkerRequest::Url(request) => request.to_string().await?,
289 WorkerRequest::Pattern { path, .. } => path.to_string().await?,
290 }
291 )
292 .into(),
293 ))
294 }
295}
296
297#[turbo_tasks::value_impl]
298impl ChunkableModuleReference for WorkerAssetReference {}
299
300impl IntoCodeGenReference for WorkerAssetReference {
301 fn into_code_gen_reference(
302 self,
303 path: AstPath,
304 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
305 let reference = self.resolved_cell();
306 (
307 ResolvedVc::upcast(reference),
308 CodeGen::WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen { reference, path }),
309 )
310 }
311}
312
313#[derive(
314 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
315)]
316pub struct WorkerAssetReferenceCodeGen {
317 reference: ResolvedVc<WorkerAssetReference>,
318 path: AstPath,
319}
320
321impl WorkerAssetReferenceCodeGen {
322 pub async fn code_generation(
323 &self,
324 chunking_context: Vc<Box<dyn ChunkingContext>>,
325 ) -> Result<CodeGeneration> {
326 let reference = self.reference.await?;
327
328 let request = match &reference.request {
330 WorkerRequest::Url(request) => **request,
331 WorkerRequest::Pattern { path, .. } => Request::parse(path.owned().await?),
332 };
333
334 let pm = PatternMapping::resolve_request(
336 request,
337 *reference.origin,
338 chunking_context,
339 self.reference.resolve_reference(),
340 ResolveType::ChunkItem,
341 )
342 .await?;
343
344 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
348 let message = if let Expr::New(new_expr) = expr {
349 if let Some(args) = &mut new_expr.args {
350 match args.first_mut() {
351 Some(ExprOrSpread {
352 spread: None,
353 expr: url_expr,
354 }) => {
355 let constructor = new_expr.callee.take();
357
358 let require_call = pm.create_require(*url_expr.take());
360
361 let mut call_args = vec![ExprOrSpread {
363 spread: None,
364 expr: constructor,
365 }];
366 call_args.extend(args.drain(1..));
368
369 *expr = Expr::Call(CallExpr {
371 span: new_expr.span,
372 callee: Callee::Expr(Box::new(require_call)),
373 args: call_args,
374 ..Default::default()
375 });
376 return;
377 }
378 Some(ExprOrSpread {
380 spread: Some(_),
381 expr: _,
382 }) => "spread operator is illegal in new Worker() expressions.",
383 _ => "new Worker() expressions require at least 1 argument",
384 }
385 } else {
386 "new Worker() expressions require at least 1 argument"
387 }
388 } else {
389 "visitor must be executed on a NewExpr"
390 };
391 *expr = *quote_expr!(
392 "(() => { throw new Error($message); })()",
393 message: Expr = Expr::Lit(Lit::Str(message.into()))
394 );
395 });
396
397 Ok(CodeGeneration::visitors(vec![visitor]))
398 }
399}