turbopack_ecmascript/references/
worker.rs

1use 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            // TODO support more worker types
64            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                        // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
173                        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}