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