turbopack_ecmascript/references/
worker.rs1use anyhow::{Result, bail};
2use bincode::{Decode, Encode};
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(
129 PartialEq, Eq, TraceRawVcs, ValueDebugFormat, NonLocalValue, Hash, Debug, Encode, Decode,
130)]
131pub struct WorkerAssetReferenceCodeGen {
132 reference: ResolvedVc<WorkerAssetReference>,
133 path: AstPath,
134}
135
136impl WorkerAssetReferenceCodeGen {
137 pub async fn code_generation(
138 &self,
139 chunking_context: Vc<Box<dyn ChunkingContext>>,
140 ) -> Result<CodeGeneration> {
141 let Some(loader) = self.reference.await?.worker_loader_module().await? else {
142 bail!("Worker loader could not be created");
143 };
144
145 let item_id = chunking_context
146 .chunk_item_id_from_ident(loader.ident())
147 .await?;
148
149 let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
150 let message = if let Expr::New(NewExpr { args, .. }) = expr {
151 if let Some(args) = args {
152 match args.first_mut() {
153 Some(ExprOrSpread { spread: None, expr }) => {
154 let item_id = module_id_to_lit(&item_id);
155 *expr = quote_expr!(
156 "$turbopack_require($item_id)",
157 turbopack_require: Expr = TURBOPACK_REQUIRE.into(),
158 item_id: Expr = item_id
159 );
160
161 if let Some(opts) = args.get_mut(1)
162 && opts.spread.is_none()
163 {
164 *opts.expr = *quote_expr!(
165 "{...$opts, type: undefined}",
166 opts: Expr = (*opts.expr).take()
167 );
168 }
169 return;
170 }
171 Some(ExprOrSpread {
173 spread: Some(_),
174 expr: _,
175 }) => "spread operator is illegal in new Worker() expressions.",
176 _ => "new Worker() expressions require at least 1 argument",
177 }
178 } else {
179 "new Worker() expressions require at least 1 argument"
180 }
181 } else {
182 "visitor must be executed on a NewExpr"
183 };
184 *expr = *quote_expr!(
185 "(() => { throw new Error($message); })()",
186 message: Expr = Expr::Lit(Lit::Str(message.into()))
187 );
188 });
189
190 Ok(CodeGeneration::visitors(vec![visitor]))
191 }
192}