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;
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::ChunkGroup},
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 runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_REQUIRE},
31 utils::{StringifyJs, StringifyModuleId},
32};
33
34#[turbo_tasks::value]
38pub struct WorkerLoaderModule {
39 pub inner: ResolvedVc<Box<dyn ChunkableModule>>,
40 pub worker_type: WorkerType,
41 pub asset_context: ResolvedVc<Box<dyn AssetContext>>,
42}
43
44#[turbo_tasks::value_impl]
45impl WorkerLoaderModule {
46 #[turbo_tasks::function]
47 pub fn new(
48 module: ResolvedVc<Box<dyn ChunkableModule>>,
49 worker_type: WorkerType,
50 asset_context: ResolvedVc<Box<dyn AssetContext>>,
51 ) -> Vc<Self> {
52 Self::cell(WorkerLoaderModule {
53 inner: module,
54 worker_type,
55 asset_context,
56 })
57 }
58
59 #[turbo_tasks::function]
60 async fn chunk_group(
61 self: Vc<Self>,
62 chunking_context: Vc<Box<dyn ChunkingContext>>,
63 module_graph: Vc<ModuleGraph>,
64 ) -> Result<Vc<OutputAssetsWithReferenced>> {
65 let this = self.await?;
66 Ok(match this.worker_type {
67 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
68 let ident = this
69 .inner
70 .ident()
71 .owned()
72 .await?
73 .with_modifier(this.worker_type.chunk_modifier_str())
74 .into_vc();
75 chunking_context.evaluated_chunk_group_assets(
76 ident,
77 ChunkGroup::Isolated(ResolvedVc::upcast(this.inner)),
78 module_graph,
79 OutputAssets::empty(),
80 AvailabilityInfo::root(),
81 )
82 }
83 WorkerType::NodeWorkerThread => {
86 let Some(evaluatable) =
87 ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(this.inner)
88 else {
89 bail!("Worker module must be evaluatable");
90 };
91
92 let worker_path = chunking_context
93 .chunk_path(
94 None,
95 this.inner.ident(),
96 Some(rcstr!("[worker thread]")),
97 rcstr!(".js"),
98 )
99 .owned()
100 .await?;
101
102 let entry_result = chunking_context
103 .root_entry_chunk_group(
104 worker_path,
105 ChunkGroup::Isolated(ResolvedVc::upcast(evaluatable)),
106 module_graph,
107 OutputAssets::empty(),
108 OutputAssets::empty(),
109 )
110 .await?;
111
112 OutputAssetsWithReferenced {
113 assets: ResolvedVc::cell(vec![entry_result.asset]),
114 referenced_assets: ResolvedVc::cell(vec![]),
115 references: ResolvedVc::cell(vec![]),
116 }
117 .cell()
118 }
119 })
120 }
121
122 #[turbo_tasks::function]
123 async fn chunks_data(
124 self: Vc<Self>,
125 chunking_context: Vc<Box<dyn ChunkingContext>>,
126 module_graph: Vc<ModuleGraph>,
127 ) -> Result<Vc<ChunksData>> {
128 Ok(ChunkData::from_assets(
129 chunking_context.output_root().owned().await?,
130 *self
131 .chunk_group(chunking_context, module_graph)
132 .await?
133 .assets,
134 ))
135 }
136
137 #[turbo_tasks::function]
140 async fn create_worker_module(self: Vc<Self>) -> Result<Vc<Box<dyn Module>>> {
141 let this = self.await?;
142 let helper = match this.worker_type {
143 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
144 rcstr!("worker/browser/createWorker.ts")
145 }
146 WorkerType::NodeWorkerThread => rcstr!("worker/node/createWorker.ts"),
147 };
148 Ok(this
149 .asset_context
150 .process(
151 Vc::upcast(FileSource::new(embed_fs().root().await?.join(&helper)?)),
152 ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::Import),
153 )
154 .module())
155 }
156
157 #[turbo_tasks::function]
159 async fn chunk_group_with_type(
160 self: Vc<Self>,
161 chunking_context: Vc<Box<dyn ChunkingContext>>,
162 module_graph: Vc<ModuleGraph>,
163 ) -> Result<Vc<OutputAssetsWithReferenced>> {
164 let this = self.await?;
165 Ok(match this.worker_type {
166 WorkerType::WebWorker | WorkerType::SharedWebWorker => self
167 .chunk_group(chunking_context, module_graph)
168 .concatenate_asset(chunking_context.worker_entrypoint()),
169 WorkerType::NodeWorkerThread => {
170 self.chunk_group(chunking_context, module_graph)
172 }
173 })
174 }
175}
176
177#[turbo_tasks::value_impl]
178impl Module for WorkerLoaderModule {
179 #[turbo_tasks::function]
180 async fn ident(&self) -> Result<Vc<AssetIdent>> {
181 Ok(self
182 .inner
183 .ident()
184 .owned()
185 .await?
186 .with_modifier(self.worker_type.modifier_str())
187 .into_vc())
188 }
189
190 #[turbo_tasks::function]
191 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
192 Vc::cell(None)
193 }
194
195 #[turbo_tasks::function]
196 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
197 let this = self.await?;
198 Ok(Vc::cell(vec![
199 ResolvedVc::upcast(
200 WorkerModuleReference::new(*ResolvedVc::upcast(this.inner), this.worker_type)
201 .to_resolved()
202 .await?,
203 ),
204 ResolvedVc::upcast(
205 SingleChunkableModuleReference::new(
206 self.create_worker_module(),
207 rcstr!("createWorker"),
208 ExportUsage::named(rcstr!("default")),
209 )
210 .to_resolved()
211 .await?,
212 ),
213 ]))
214 }
215
216 #[turbo_tasks::function]
217 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
218 ModuleSideEffects::SideEffectFree.cell()
219 }
220}
221
222#[turbo_tasks::value_impl]
223impl ChunkableModule for WorkerLoaderModule {
224 #[turbo_tasks::function]
225 fn as_chunk_item(
226 self: ResolvedVc<Self>,
227 module_graph: ResolvedVc<ModuleGraph>,
228 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
229 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
230 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
231 }
232}
233
234#[turbo_tasks::value_impl]
235impl EcmascriptChunkPlaceable for WorkerLoaderModule {
236 #[turbo_tasks::function]
237 fn get_exports(&self) -> Vc<EcmascriptExports> {
238 EcmascriptExports::Value.cell()
239 }
240
241 #[turbo_tasks::function]
242 async fn chunk_item_content(
243 self: Vc<Self>,
244 chunking_context: Vc<Box<dyn ChunkingContext>>,
245 module_graph: Vc<ModuleGraph>,
246 _async_module_info: Option<Vc<AsyncModuleInfo>>,
247 estimated: bool,
248 ) -> Result<Vc<EcmascriptChunkItemContent>> {
249 let this = self.await?;
250 let options = EcmascriptChunkItemOptions {
251 supports_arrow_functions: *chunking_context
252 .environment()
253 .runtime_versions()
254 .supports_arrow_functions()
255 .await?,
256 ..Default::default()
257 };
258
259 if estimated {
260 let fake_id = ModuleId::String(rcstr!("a_fake_module"));
265 return Ok(EcmascriptChunkItemContent {
266 inner_code: formatdoc! {
267 r#"
268 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})["default"](__dirname + "/" + {worker_path:#}));
269 "#,
270 worker_path = StringifyJs(&"a_fake_path_for_size_estimation"),
271 workers_module = StringifyModuleId(&fake_id),
272 }
273 .into(),
274 options,
275 ..Default::default()
276 }
277 .cell());
278 }
279
280 let create_worker_id = self
281 .create_worker_module()
282 .chunk_item_id(chunking_context)
283 .await?;
284
285 let code = match this.worker_type {
286 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
287 let entrypoint_full_path = chunking_context.worker_entrypoint().path().await?;
291
292 let output_root = chunking_context.output_root().owned().await?;
294 let entrypoint_path = output_root
295 .get_path_to(&entrypoint_full_path)
296 .map(|s| s.to_string())
297 .unwrap_or_else(|| entrypoint_full_path.path.to_string());
298
299 let chunks_data = self.chunks_data(chunking_context, module_graph).await?;
301 let chunks_data = chunks_data.iter().try_join().await?;
302 let chunks_data: Vec<_> = chunks_data
303 .iter()
304 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
305 .collect();
306
307 formatdoc! {
308 r#"
309 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})["default"]({entrypoint}, {chunks}));
310 "#,
311 entrypoint = StringifyJs(&entrypoint_path),
312 chunks = StringifyJs(&chunks_data),
313 workers_module = StringifyModuleId(&create_worker_id),
314 }
315 }
316 WorkerType::NodeWorkerThread => {
317 let chunk_group = self.chunk_group(chunking_context, module_graph).await?;
321 let assets = chunk_group.assets.await?;
322
323 let Some(entry_asset) = assets.last() else {
330 bail!("cannot find worker entry point asset");
331 };
332 let entry_path = entry_asset.path().await?;
333
334 formatdoc! {
340 r#"
341 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})["default"](__dirname + "/" + {worker_path:#}));
342 "#,
343 worker_path = StringifyJs(entry_path.file_name()),
344 workers_module = StringifyModuleId(&create_worker_id),
345 }
346 }
347 };
348
349 Ok(EcmascriptChunkItemContent {
350 inner_code: code.into(),
351 options,
352 ..Default::default()
353 }
354 .cell())
355 }
356
357 #[turbo_tasks::function]
358 fn chunk_item_output_assets(
359 self: Vc<Self>,
360 chunking_context: Vc<Box<dyn ChunkingContext>>,
361 module_graph: Vc<ModuleGraph>,
362 ) -> Vc<OutputAssetsWithReferenced> {
363 self.chunk_group_with_type(chunking_context, module_graph)
364 }
365}
366
367#[turbo_tasks::value]
368#[derive(ValueToString)]
369#[value_to_string("{} module", self.worker_type.friendly_str())]
370struct WorkerModuleReference {
371 module: ResolvedVc<Box<dyn Module>>,
372 worker_type: WorkerType,
373}
374
375#[turbo_tasks::value_impl]
376impl WorkerModuleReference {
377 #[turbo_tasks::function]
378 pub fn new(module: ResolvedVc<Box<dyn Module>>, worker_type: WorkerType) -> Vc<Self> {
379 Self::cell(WorkerModuleReference {
380 module,
381 worker_type,
382 })
383 }
384}
385
386#[turbo_tasks::value_impl]
387impl ModuleReference for WorkerModuleReference {
388 #[turbo_tasks::function]
389 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
390 *ModuleResolveResult::module(self.module)
391 }
392
393 fn chunking_type(&self) -> Option<ChunkingType> {
394 Some(ChunkingType::Isolated {
395 _ty: match self.worker_type {
396 WorkerType::SharedWebWorker | WorkerType::WebWorker => ChunkGroupType::Evaluated,
397 WorkerType::NodeWorkerThread => ChunkGroupType::Entry,
398 },
399 merge_tag: None,
400 })
401 }
402}