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