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