turbopack_ecmascript/references/
worker.rs

1use 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            // 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(
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                        // These are SWC bugs: https://github.com/swc-project/swc/issues/5394
172                        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}