turbopack_ecmascript/references/
worker.rs1use anyhow::{Result, bail};
2use serde::{Deserialize, Serialize};
3use swc_core::{
4 common::util::take::Take,
5 ecma::ast::{Expr, ExprOrSpread, Lit, NewExpr},
6 quote_expr,
7};
8use turbo_rcstr::{RcStr, rcstr};
9use turbo_tasks::{
10 NonLocalValue, ResolvedVc, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
11};
12use turbopack_core::{
13 chunk::{ChunkableModule, ChunkableModuleReference, ChunkingContext},
14 issue::{IssueExt, IssueSeverity, IssueSource, StyledString, code_gen::CodeGenerationIssue},
15 module::Module,
16 reference::ModuleReference,
17 reference_type::{ReferenceType, WorkerReferenceSubType},
18 resolve::{ModuleResolveResult, origin::ResolveOrigin, parse::Request, url_resolve},
19};
20
21use crate::{
22 code_gen::{CodeGen, CodeGeneration, IntoCodeGenReference},
23 create_visitor,
24 references::AstPath,
25 runtime_functions::TURBOPACK_REQUIRE,
26 utils::module_id_to_lit,
27 worker_chunk::module::WorkerLoaderModule,
28};
29
30#[turbo_tasks::value]
31#[derive(Hash, Debug)]
32pub struct WorkerAssetReference {
33 pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
34 pub request: ResolvedVc<Request>,
35 pub issue_source: IssueSource,
36 pub in_try: bool,
37}
38
39impl WorkerAssetReference {
40 pub fn new(
41 origin: ResolvedVc<Box<dyn ResolveOrigin>>,
42 request: ResolvedVc<Request>,
43 issue_source: IssueSource,
44 in_try: bool,
45 ) -> Self {
46 WorkerAssetReference {
47 origin,
48 request,
49 issue_source,
50 in_try,
51 }
52 }
53}
54
55impl WorkerAssetReference {
56 async fn worker_loader_module(
57 self: &WorkerAssetReference,
58 ) -> Result<Option<Vc<WorkerLoaderModule>>> {
59 let module = url_resolve(
60 *self.origin,
61 *self.request,
62 ReferenceType::Worker(WorkerReferenceSubType::WebWorker),
64 Some(self.issue_source),
65 self.in_try,
66 );
67
68 let Some(module) = *module.first_module().await? else {
69 return Ok(None);
70 };
71 let Some(chunkable) = ResolvedVc::try_downcast::<Box<dyn ChunkableModule>>(module) else {
72 CodeGenerationIssue {
73 severity: IssueSeverity::Bug,
74 title: StyledString::Text(rcstr!("non-ecmascript placeable asset")).resolved_cell(),
75 message: StyledString::Text(rcstr!("asset is not placeable in ESM chunks"))
76 .resolved_cell(),
77 path: self.origin.origin_path().owned().await?,
78 }
79 .resolved_cell()
80 .emit();
81 return Ok(None);
82 };
83
84 Ok(Some(WorkerLoaderModule::new(*chunkable)))
85 }
86}
87
88#[turbo_tasks::value_impl]
89impl ModuleReference for WorkerAssetReference {
90 #[turbo_tasks::function]
91 async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
92 if let Some(worker_loader_module) = self.worker_loader_module().await? {
93 Ok(*ModuleResolveResult::module(ResolvedVc::upcast(
94 worker_loader_module.to_resolved().await?,
95 )))
96 } else {
97 Ok(*ModuleResolveResult::unresolvable())
98 }
99 }
100}
101
102#[turbo_tasks::value_impl]
103impl ValueToString for WorkerAssetReference {
104 #[turbo_tasks::function]
105 async fn to_string(&self) -> Result<Vc<RcStr>> {
106 Ok(Vc::cell(
107 format!("new Worker {}", self.request.to_string().await?,).into(),
108 ))
109 }
110}
111
112#[turbo_tasks::value_impl]
113impl ChunkableModuleReference for WorkerAssetReference {}
114
115impl IntoCodeGenReference for WorkerAssetReference {
116 fn into_code_gen_reference(
117 self,
118 path: AstPath,
119 ) -> (ResolvedVc<Box<dyn ModuleReference>>, CodeGen) {
120 let reference = self.resolved_cell();
121 (
122 ResolvedVc::upcast(reference),
123 CodeGen::WorkerAssetReferenceCodeGen(WorkerAssetReferenceCodeGen { reference, path }),
124 )
125 }
126}
127
128#[derive(PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, ValueDebugFormat, NonLocalValue)]
129pub struct WorkerAssetReferenceCodeGen {
130 reference: ResolvedVc<WorkerAssetReference>,
131 path: AstPath,
132}
133
134impl WorkerAssetReferenceCodeGen {
135 pub async fn code_generation(
136 &self,
137 chunking_context: Vc<Box<dyn ChunkingContext>>,
138 ) -> Result<CodeGeneration> {
139 let Some(loader) = self.reference.await?.worker_loader_module().await? else {
140 bail!("Worker loader could not be created");
141 };
142
143 let item_id = chunking_context
144 .chunk_item_id_from_ident(loader.ident())
145 .await?;
146
147 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
148 let message = if let Expr::New(NewExpr { args, .. }) = expr {
149 if let Some(args) = args {
150 match args.first_mut() {
151 Some(ExprOrSpread { spread: None, expr }) => {
152 let item_id = module_id_to_lit(&item_id);
153 *expr = quote_expr!(
154 "$turbopack_require($item_id)",
155 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
156 item_id: Expr = item_id
157 );
158
159 if let Some(opts) = args.get_mut(1)
160 && opts.spread.is_none()
161 {
162 *opts.expr = *quote_expr!(
163 "{...$opts, type: undefined}",
164 opts: Expr = (*opts.expr).take()
165 );
166 }
167 return;
168 }
169 Some(ExprOrSpread {
171 spread: Some(_),
172 expr: _,
173 }) => "spread operator is illegal in new Worker() expressions.",
174 _ => "new Worker() expressions require at least 1 argument",
175 }
176 } else {
177 "new Worker() expressions require at least 1 argument"
178 }
179 } else {
180 "visitor must be executed on a NewExpr"
181 };
182 *expr = *quote_expr!(
183 "(() => { throw new Error($message); })()",
184 message: Expr = Expr::Lit(Lit::Str(message.into()))
185 );
186 });
187
188 Ok(CodeGeneration::visitors(vec![visitor]))
189 }
190}