turbopack_ecmascript/worker_chunk/
module.rs1use anyhow::{Result, bail};
2use indoc::formatdoc;
3use turbo_rcstr::rcstr;
4use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
5use turbopack_core::{
6 chunk::{
7 AsyncModuleInfo, ChunkData, ChunkGroupType, ChunkableModule, ChunkingContext,
8 ChunkingContextExt, ChunkingType, ChunksData, EvaluatableAsset,
9 availability_info::AvailabilityInfo,
10 },
11 context::AssetContext,
12 ident::AssetIdent,
13 module::{Module, ModuleSideEffects},
14 module_graph::{ModuleGraph, chunk_group_info::ChunkGroup},
15 output::{OutputAsset, OutputAssets, OutputAssetsWithReferenced},
16 reference::{ModuleReference, ModuleReferences},
17 resolve::ModuleResolveResult,
18};
19
20use super::worker_type::WorkerType;
21use crate::{
22 chunk::{
23 EcmascriptChunkItemContent, EcmascriptChunkItemOptions, EcmascriptChunkPlaceable,
24 EcmascriptExports, data::EcmascriptChunkData, ecmascript_chunk_item,
25 },
26 runtime_functions::{TURBOPACK_CREATE_WORKER, TURBOPACK_EXPORT_VALUE},
27 utils::StringifyJs,
28};
29
30#[turbo_tasks::value]
34pub struct WorkerLoaderModule {
35 pub inner: ResolvedVc<Box<dyn ChunkableModule>>,
36 pub worker_type: WorkerType,
37 pub asset_context: ResolvedVc<Box<dyn AssetContext>>,
38}
39
40#[turbo_tasks::value_impl]
41impl WorkerLoaderModule {
42 #[turbo_tasks::function]
43 pub fn new(
44 module: ResolvedVc<Box<dyn ChunkableModule>>,
45 worker_type: WorkerType,
46 asset_context: ResolvedVc<Box<dyn AssetContext>>,
47 ) -> Vc<Self> {
48 Self::cell(WorkerLoaderModule {
49 inner: module,
50 worker_type,
51 asset_context,
52 })
53 }
54
55 #[turbo_tasks::function]
56 async fn chunk_group(
57 self: Vc<Self>,
58 chunking_context: Vc<Box<dyn ChunkingContext>>,
59 module_graph: Vc<ModuleGraph>,
60 ) -> Result<Vc<OutputAssetsWithReferenced>> {
61 let this = self.await?;
62 Ok(match this.worker_type {
63 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
64 let ident = this
65 .inner
66 .ident()
67 .owned()
68 .await?
69 .with_modifier(this.worker_type.chunk_modifier_str())
70 .into_vc();
71 chunking_context.evaluated_chunk_group_assets(
72 ident,
73 ChunkGroup::Isolated(ResolvedVc::upcast(this.inner)),
74 module_graph,
75 AvailabilityInfo::root(),
76 )
77 }
78 WorkerType::NodeWorkerThread => {
81 let Some(evaluatable) =
82 ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(this.inner)
83 else {
84 bail!("Worker module must be evaluatable");
85 };
86
87 let worker_path = chunking_context
88 .chunk_path(
89 None,
90 this.inner.ident(),
91 Some(rcstr!("[worker thread]")),
92 rcstr!(".js"),
93 )
94 .owned()
95 .await?;
96
97 let entry_result = chunking_context
98 .root_entry_chunk_group(
99 worker_path,
100 ChunkGroup::Isolated(ResolvedVc::upcast(evaluatable)),
101 module_graph,
102 OutputAssets::empty(),
103 OutputAssets::empty(),
104 )
105 .await?;
106
107 OutputAssetsWithReferenced {
108 assets: ResolvedVc::cell(vec![entry_result.asset]),
109 referenced_assets: ResolvedVc::cell(vec![]),
110 references: ResolvedVc::cell(vec![]),
111 }
112 .cell()
113 }
114 })
115 }
116
117 #[turbo_tasks::function]
118 async fn chunks_data(
119 self: Vc<Self>,
120 chunking_context: Vc<Box<dyn ChunkingContext>>,
121 module_graph: Vc<ModuleGraph>,
122 ) -> Result<Vc<ChunksData>> {
123 Ok(ChunkData::from_assets(
124 chunking_context.output_root().owned().await?,
125 *self
126 .chunk_group(chunking_context, module_graph)
127 .await?
128 .assets,
129 ))
130 }
131
132 #[turbo_tasks::function]
134 async fn chunk_group_with_type(
135 self: Vc<Self>,
136 chunking_context: Vc<Box<dyn ChunkingContext>>,
137 module_graph: Vc<ModuleGraph>,
138 ) -> Result<Vc<OutputAssetsWithReferenced>> {
139 let this = self.await?;
140 Ok(match this.worker_type {
141 WorkerType::WebWorker | WorkerType::SharedWebWorker => self
142 .chunk_group(chunking_context, module_graph)
143 .concatenate_asset(chunking_context.worker_entrypoint()),
144 WorkerType::NodeWorkerThread => {
145 self.chunk_group(chunking_context, module_graph)
147 }
148 })
149 }
150}
151
152#[turbo_tasks::value_impl]
153impl Module for WorkerLoaderModule {
154 #[turbo_tasks::function]
155 async fn ident(&self) -> Result<Vc<AssetIdent>> {
156 Ok(self
157 .inner
158 .ident()
159 .owned()
160 .await?
161 .with_modifier(self.worker_type.modifier_str())
162 .into_vc())
163 }
164
165 #[turbo_tasks::function]
166 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
167 Vc::cell(None)
168 }
169
170 #[turbo_tasks::function]
171 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
172 let this = self.await?;
173 Ok(Vc::cell(vec![ResolvedVc::upcast(
174 WorkerModuleReference::new(*ResolvedVc::upcast(this.inner), this.worker_type)
175 .to_resolved()
176 .await?,
177 )]))
178 }
179
180 #[turbo_tasks::function]
181 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
182 ModuleSideEffects::SideEffectFree.cell()
183 }
184}
185
186#[turbo_tasks::value_impl]
187impl ChunkableModule for WorkerLoaderModule {
188 #[turbo_tasks::function]
189 fn as_chunk_item(
190 self: ResolvedVc<Self>,
191 module_graph: ResolvedVc<ModuleGraph>,
192 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
193 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
194 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
195 }
196}
197
198#[turbo_tasks::value_impl]
199impl EcmascriptChunkPlaceable for WorkerLoaderModule {
200 #[turbo_tasks::function]
201 fn get_exports(&self) -> Vc<EcmascriptExports> {
202 EcmascriptExports::Value.cell()
203 }
204
205 #[turbo_tasks::function]
206 async fn chunk_item_content(
207 self: Vc<Self>,
208 chunking_context: Vc<Box<dyn ChunkingContext>>,
209 module_graph: Vc<ModuleGraph>,
210 _async_module_info: Option<Vc<AsyncModuleInfo>>,
211 estimated: bool,
212 ) -> Result<Vc<EcmascriptChunkItemContent>> {
213 let this = self.await?;
214 let options = EcmascriptChunkItemOptions {
215 supports_arrow_functions: *chunking_context
216 .environment()
217 .runtime_versions()
218 .supports_arrow_functions()
219 .await?,
220 ..Default::default()
221 };
222
223 if estimated {
224 return Ok(EcmascriptChunkItemContent {
229 inner_code: formatdoc! {
230 r#"
231 {TURBOPACK_EXPORT_VALUE}(function(Ctor, opts) {{
232 return {TURBOPACK_CREATE_WORKER}(Ctor, __dirname + "/" + {worker_path:#}, opts);
233 }});
234 "#,
235 worker_path = StringifyJs(&"a_fake_path_for_size_estimation"),
236 }
237 .into(),
238 options,
239 ..Default::default()
240 }
241 .cell());
242 }
243
244 let code = match this.worker_type {
245 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
246 let entrypoint_full_path = chunking_context.worker_entrypoint().path().await?;
250
251 let output_root = chunking_context.output_root().owned().await?;
253 let entrypoint_path = output_root
254 .get_path_to(&entrypoint_full_path)
255 .map(|s| s.to_string())
256 .unwrap_or_else(|| entrypoint_full_path.path.to_string());
257
258 let chunks_data = self.chunks_data(chunking_context, module_graph).await?;
260 let chunks_data = chunks_data.iter().try_join().await?;
261 let chunks_data: Vec<_> = chunks_data
262 .iter()
263 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
264 .collect();
265
266 formatdoc! {
267 r#"
268 {TURBOPACK_EXPORT_VALUE}(function(Ctor, opts) {{
269 return {TURBOPACK_CREATE_WORKER}(Ctor, {entrypoint}, {chunks}, opts);
270 }});
271 "#,
272 entrypoint = StringifyJs(&entrypoint_path),
273 chunks = StringifyJs(&chunks_data),
274 }
275 }
276 WorkerType::NodeWorkerThread => {
277 let chunk_group = self.chunk_group(chunking_context, module_graph).await?;
281 let assets = chunk_group.assets.await?;
282
283 let Some(entry_asset) = assets.last() else {
290 bail!("cannot find worker entry point asset");
291 };
292 let entry_path = entry_asset.path().await?;
293
294 formatdoc! {
300 r#"
301 {TURBOPACK_EXPORT_VALUE}(function(Ctor, opts) {{
302 return {TURBOPACK_CREATE_WORKER}(Ctor, __dirname + "/" + {worker_path:#}, opts);
303 }});
304 "#,
305 worker_path = StringifyJs(entry_path.file_name()),
306 }
307 }
308 };
309
310 Ok(EcmascriptChunkItemContent {
311 inner_code: code.into(),
312 options,
313 ..Default::default()
314 }
315 .cell())
316 }
317
318 #[turbo_tasks::function]
319 fn chunk_item_output_assets(
320 self: Vc<Self>,
321 chunking_context: Vc<Box<dyn ChunkingContext>>,
322 module_graph: Vc<ModuleGraph>,
323 ) -> Vc<OutputAssetsWithReferenced> {
324 self.chunk_group_with_type(chunking_context, module_graph)
325 }
326}
327
328#[turbo_tasks::value]
329#[derive(ValueToString)]
330#[value_to_string("{} module", self.worker_type.friendly_str())]
331struct WorkerModuleReference {
332 module: ResolvedVc<Box<dyn Module>>,
333 worker_type: WorkerType,
334}
335
336#[turbo_tasks::value_impl]
337impl WorkerModuleReference {
338 #[turbo_tasks::function]
339 pub fn new(module: ResolvedVc<Box<dyn Module>>, worker_type: WorkerType) -> Vc<Self> {
340 Self::cell(WorkerModuleReference {
341 module,
342 worker_type,
343 })
344 }
345}
346
347#[turbo_tasks::value_impl]
348impl ModuleReference for WorkerModuleReference {
349 #[turbo_tasks::function]
350 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
351 *ModuleResolveResult::module(self.module)
352 }
353
354 fn chunking_type(&self) -> Option<ChunkingType> {
355 Some(ChunkingType::Isolated {
356 _ty: match self.worker_type {
357 WorkerType::SharedWebWorker | WorkerType::WebWorker => ChunkGroupType::Evaluated,
358 WorkerType::NodeWorkerThread => ChunkGroupType::Entry,
359 },
360 merge_tag: None,
361 })
362 }
363}