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