turbopack_ecmascript/worker_chunk/
module.rs1use anyhow::{Result, bail};
2use indoc::formatdoc;
3use turbo_rcstr::rcstr;
4use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
5use turbo_tasks_fs::{FileSystem, FileSystemPath};
6use turbopack_core::{
7 chunk::{
8 AsyncModuleInfo, ChunkData, ChunkGroupType, ChunkableModule, ChunkingContext,
9 ChunkingContextExt, ChunkingType, ChunksData, EvaluatableAsset, ModuleChunkItemIdExt,
10 ModuleId, availability_info::AvailabilityInfo,
11 },
12 context::AssetContext,
13 file_source::FileSource,
14 ident::AssetIdent,
15 module::{Module, ModuleSideEffects},
16 module_graph::{ModuleGraph, chunk_group_info::ChunkGroupKey},
17 output::{OutputAsset, OutputAssets, OutputAssetsWithReferenced},
18 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
19 reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType},
20 resolve::{ExportUsage, ModuleResolveResult},
21};
22
23use super::worker_type::WorkerType;
24use crate::{
25 chunk::{
26 EcmascriptChunkItemContent, EcmascriptChunkItemOptions, EcmascriptChunkPlaceable,
27 EcmascriptExports, data::EcmascriptChunkData, ecmascript_chunk_item,
28 },
29 embed_js::embed_fs,
30 references::esm::generated_export_key,
31 runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_REQUIRE},
32 utils::{StringifyJs, StringifyModuleId},
33};
34
35#[turbo_tasks::value]
39pub struct WorkerLoaderModule {
40 pub inner: ResolvedVc<Box<dyn ChunkableModule>>,
41 pub worker_type: WorkerType,
42 pub asset_context: ResolvedVc<Box<dyn AssetContext>>,
43}
44
45#[turbo_tasks::value_impl]
46impl WorkerLoaderModule {
47 #[turbo_tasks::function]
48 pub fn new(
49 module: ResolvedVc<Box<dyn ChunkableModule>>,
50 worker_type: WorkerType,
51 asset_context: ResolvedVc<Box<dyn AssetContext>>,
52 ) -> Vc<Self> {
53 Self::cell(WorkerLoaderModule {
54 inner: module,
55 worker_type,
56 asset_context,
57 })
58 }
59
60 #[turbo_tasks::function]
61 async fn chunk_group(
62 self: Vc<Self>,
63 chunking_context: Vc<Box<dyn ChunkingContext>>,
64 module_graph: Vc<ModuleGraph>,
65 ) -> Result<Vc<OutputAssetsWithReferenced>> {
66 let this = self.await?;
67 Ok(match this.worker_type {
68 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
69 let ident = this
70 .inner
71 .ident()
72 .owned()
73 .await?
74 .with_modifier(this.worker_type.chunk_modifier_str())
75 .into_vc();
76 chunking_context.evaluated_chunk_group_assets(
77 ident,
78 ChunkGroupKey::Isolated(ResolvedVc::upcast(this.inner)),
79 module_graph,
80 OutputAssets::empty(),
81 AvailabilityInfo::root(),
82 )
83 }
84 WorkerType::NodeWorkerThread => {
87 let Some(evaluatable) =
88 ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(this.inner)
89 else {
90 bail!("Worker module must be evaluatable");
91 };
92
93 let worker_path = self
94 .node_worker_entry_path(chunking_context)
95 .owned()
96 .await?;
97
98 let entry_result = chunking_context
99 .root_entry_chunk_group(
100 worker_path,
101 ChunkGroupKey::Isolated(ResolvedVc::upcast(evaluatable)),
102 module_graph,
103 OutputAssets::empty(),
104 OutputAssets::empty(),
105 )
106 .await?;
107
108 OutputAssetsWithReferenced {
109 assets: ResolvedVc::cell(vec![entry_result.asset]),
110 referenced_assets: ResolvedVc::cell(vec![]),
111 references: ResolvedVc::cell(vec![]),
112 }
113 .cell()
114 }
115 })
116 }
117
118 #[turbo_tasks::function]
121 fn node_worker_entry_path(
122 &self,
123 chunking_context: Vc<Box<dyn ChunkingContext>>,
124 ) -> Vc<FileSystemPath> {
125 chunking_context.chunk_path(
126 None,
127 self.inner.ident(),
128 Some(rcstr!("[worker thread]")),
129 rcstr!(".js"),
130 )
131 }
132
133 #[turbo_tasks::function]
134 async fn chunks_data(
135 self: Vc<Self>,
136 chunking_context: Vc<Box<dyn ChunkingContext>>,
137 module_graph: Vc<ModuleGraph>,
138 ) -> Result<Vc<ChunksData>> {
139 Ok(ChunkData::from_assets(
140 chunking_context.output_root().owned().await?,
141 *self
142 .chunk_group(chunking_context, module_graph)
143 .await?
144 .assets,
145 ))
146 }
147
148 #[turbo_tasks::function]
151 async fn create_worker_module(self: Vc<Self>) -> Result<Vc<Box<dyn Module>>> {
152 let this = self.await?;
153 let helper = match this.worker_type {
154 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
155 rcstr!("worker/browser/createWorker.ts")
156 }
157 WorkerType::NodeWorkerThread => rcstr!("worker/node/createWorker.ts"),
158 };
159 Ok(this
160 .asset_context
161 .process(
162 Vc::upcast(FileSource::new(embed_fs().root().await?.join(&helper)?)),
163 ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::Import),
164 )
165 .module())
166 }
167
168 #[turbo_tasks::function]
170 async fn chunk_group_with_type(
171 self: Vc<Self>,
172 chunking_context: Vc<Box<dyn ChunkingContext>>,
173 module_graph: Vc<ModuleGraph>,
174 ) -> Result<Vc<OutputAssetsWithReferenced>> {
175 let this = self.await?;
176 Ok(match this.worker_type {
177 WorkerType::WebWorker | WorkerType::SharedWebWorker => self
178 .chunk_group(chunking_context, module_graph)
179 .concatenate_asset(chunking_context.worker_entrypoint()),
180 WorkerType::NodeWorkerThread => {
181 self.chunk_group(chunking_context, module_graph)
183 }
184 })
185 }
186}
187
188#[turbo_tasks::value_impl]
189impl Module for WorkerLoaderModule {
190 #[turbo_tasks::function]
191 async fn ident(&self) -> Result<Vc<AssetIdent>> {
192 Ok(self
193 .inner
194 .ident()
195 .owned()
196 .await?
197 .with_modifier(self.worker_type.modifier_str())
198 .into_vc())
199 }
200
201 #[turbo_tasks::function]
202 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
203 Vc::cell(None)
204 }
205
206 #[turbo_tasks::function]
207 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
208 let this = self.await?;
209 Ok(Vc::cell(vec![
210 ResolvedVc::upcast(
211 WorkerModuleReference::new(*ResolvedVc::upcast(this.inner), this.worker_type)
212 .to_resolved()
213 .await?,
214 ),
215 ResolvedVc::upcast(
216 SingleChunkableModuleReference::new(
217 self.create_worker_module(),
218 rcstr!("createWorker"),
219 ExportUsage::named(rcstr!("default")),
220 )
221 .to_resolved()
222 .await?,
223 ),
224 ]))
225 }
226
227 #[turbo_tasks::function]
228 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
229 ModuleSideEffects::SideEffectFree.cell()
230 }
231}
232
233#[turbo_tasks::value_impl]
234impl ChunkableModule for WorkerLoaderModule {
235 #[turbo_tasks::function]
236 fn as_chunk_item(
237 self: ResolvedVc<Self>,
238 module_graph: ResolvedVc<ModuleGraph>,
239 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
240 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
241 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
242 }
243}
244
245#[turbo_tasks::value_impl]
246impl EcmascriptChunkPlaceable for WorkerLoaderModule {
247 #[turbo_tasks::function]
248 fn get_exports(&self) -> Vc<EcmascriptExports> {
249 EcmascriptExports::Value.cell()
250 }
251
252 #[turbo_tasks::function]
253 async fn chunk_item_content(
254 self: Vc<Self>,
255 chunking_context: Vc<Box<dyn ChunkingContext>>,
256 module_graph: Vc<ModuleGraph>,
257 _async_module_info: Option<Vc<AsyncModuleInfo>>,
258 estimated: bool,
259 ) -> Result<Vc<EcmascriptChunkItemContent>> {
260 let this = self.await?;
261 let options = EcmascriptChunkItemOptions {
262 supports_arrow_functions: *chunking_context
263 .environment()
264 .runtime_versions()
265 .supports_arrow_functions()
266 .await?,
267 ..Default::default()
268 };
269
270 if estimated {
271 let fake_id = ModuleId::String(rcstr!("a_fake_module"));
280 return Ok(EcmascriptChunkItemContent {
281 inner_code: formatdoc! {
282 r#"
283 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})["default"](__dirname + "/" + {worker_path:#}));
284 "#,
285 worker_path = StringifyJs(&"a_fake_path_for_size_estimation"),
286 workers_module = StringifyModuleId(&fake_id),
287 }
288 .into(),
289 options,
290 ..Default::default()
291 }
292 .cell());
293 }
294
295 let create_worker_module = self.create_worker_module();
296 let create_worker_id = create_worker_module.chunk_item_id(chunking_context).await?;
297 let create_worker_export = match ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(
301 create_worker_module.to_resolved().await?,
302 ) {
303 Some(placeable) => {
304 generated_export_key(placeable, chunking_context, &rcstr!("default")).await?
305 }
306 None => rcstr!("default"),
307 };
308
309 let code = match this.worker_type {
310 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
311 let entrypoint_full_path = chunking_context.worker_entrypoint().path().await?;
315
316 let output_root = chunking_context.output_root().owned().await?;
318 let entrypoint_path = output_root
319 .get_path_to(&entrypoint_full_path)
320 .map(|s| s.to_string())
321 .unwrap_or_else(|| entrypoint_full_path.path.to_string());
322
323 let chunks_data = self.chunks_data(chunking_context, module_graph).await?;
325 let chunks_data = chunks_data.iter().try_join().await?;
326 let chunks_data: Vec<_> = chunks_data
327 .iter()
328 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
329 .collect();
330
331 formatdoc! {
332 r#"
333 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})[{export:#}]({entrypoint}, {chunks}));
334 "#,
335 entrypoint = StringifyJs(&entrypoint_path),
336 chunks = StringifyJs(&chunks_data),
337 workers_module = StringifyModuleId(&create_worker_id),
338 export = StringifyJs(&create_worker_export),
339 }
340 }
341 WorkerType::NodeWorkerThread => {
342 let entry_path = self.node_worker_entry_path(chunking_context).await?;
345
346 formatdoc! {
356 r#"
357 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})[{export:#}](__dirname + "/" + {worker_path:#}));
358 "#,
359 worker_path = StringifyJs(entry_path.file_name()),
360 workers_module = StringifyModuleId(&create_worker_id),
361 export = StringifyJs(&create_worker_export),
362 }
363 }
364 };
365
366 Ok(EcmascriptChunkItemContent {
367 inner_code: code.into(),
368 options,
369 ..Default::default()
370 }
371 .cell())
372 }
373
374 #[turbo_tasks::function]
375 fn chunk_item_output_assets(
376 self: Vc<Self>,
377 chunking_context: Vc<Box<dyn ChunkingContext>>,
378 module_graph: Vc<ModuleGraph>,
379 ) -> Vc<OutputAssetsWithReferenced> {
380 self.chunk_group_with_type(chunking_context, module_graph)
381 }
382}
383
384#[turbo_tasks::value]
385#[derive(ValueToString)]
386#[value_to_string("{} module", self.worker_type.friendly_str())]
387struct WorkerModuleReference {
388 module: ResolvedVc<Box<dyn Module>>,
389 worker_type: WorkerType,
390}
391
392#[turbo_tasks::value_impl]
393impl WorkerModuleReference {
394 #[turbo_tasks::function]
395 pub fn new(module: ResolvedVc<Box<dyn Module>>, worker_type: WorkerType) -> Vc<Self> {
396 Self::cell(WorkerModuleReference {
397 module,
398 worker_type,
399 })
400 }
401}
402
403#[turbo_tasks::value_impl]
404impl ModuleReference for WorkerModuleReference {
405 #[turbo_tasks::function]
406 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
407 *ModuleResolveResult::module(self.module)
408 }
409
410 fn chunking_type(&self) -> Option<ChunkingType> {
411 Some(ChunkingType::Isolated {
412 _ty: match self.worker_type {
413 WorkerType::SharedWebWorker | WorkerType::WebWorker => ChunkGroupType::Evaluated,
414 WorkerType::NodeWorkerThread => ChunkGroupType::Entry,
415 },
416 merge_tag: None,
417 })
418 }
419}