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 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 ChunkGroup::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 = chunking_context
94 .chunk_path(
95 None,
96 this.inner.ident(),
97 Some(rcstr!("[worker thread]")),
98 rcstr!(".js"),
99 )
100 .owned()
101 .await?;
102
103 let entry_result = chunking_context
104 .root_entry_chunk_group(
105 worker_path,
106 ChunkGroup::Isolated(ResolvedVc::upcast(evaluatable)),
107 module_graph,
108 OutputAssets::empty(),
109 OutputAssets::empty(),
110 )
111 .await?;
112
113 OutputAssetsWithReferenced {
114 assets: ResolvedVc::cell(vec![entry_result.asset]),
115 referenced_assets: ResolvedVc::cell(vec![]),
116 references: ResolvedVc::cell(vec![]),
117 }
118 .cell()
119 }
120 })
121 }
122
123 #[turbo_tasks::function]
124 async fn chunks_data(
125 self: Vc<Self>,
126 chunking_context: Vc<Box<dyn ChunkingContext>>,
127 module_graph: Vc<ModuleGraph>,
128 ) -> Result<Vc<ChunksData>> {
129 Ok(ChunkData::from_assets(
130 chunking_context.output_root().owned().await?,
131 *self
132 .chunk_group(chunking_context, module_graph)
133 .await?
134 .assets,
135 ))
136 }
137
138 #[turbo_tasks::function]
141 async fn create_worker_module(self: Vc<Self>) -> Result<Vc<Box<dyn Module>>> {
142 let this = self.await?;
143 let helper = match this.worker_type {
144 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
145 rcstr!("worker/browser/createWorker.ts")
146 }
147 WorkerType::NodeWorkerThread => rcstr!("worker/node/createWorker.ts"),
148 };
149 Ok(this
150 .asset_context
151 .process(
152 Vc::upcast(FileSource::new(embed_fs().root().await?.join(&helper)?)),
153 ReferenceType::EcmaScriptModules(EcmaScriptModulesReferenceSubType::Import),
154 )
155 .module())
156 }
157
158 #[turbo_tasks::function]
160 async fn chunk_group_with_type(
161 self: Vc<Self>,
162 chunking_context: Vc<Box<dyn ChunkingContext>>,
163 module_graph: Vc<ModuleGraph>,
164 ) -> Result<Vc<OutputAssetsWithReferenced>> {
165 let this = self.await?;
166 Ok(match this.worker_type {
167 WorkerType::WebWorker | WorkerType::SharedWebWorker => self
168 .chunk_group(chunking_context, module_graph)
169 .concatenate_asset(chunking_context.worker_entrypoint()),
170 WorkerType::NodeWorkerThread => {
171 self.chunk_group(chunking_context, module_graph)
173 }
174 })
175 }
176}
177
178#[turbo_tasks::value_impl]
179impl Module for WorkerLoaderModule {
180 #[turbo_tasks::function]
181 async fn ident(&self) -> Result<Vc<AssetIdent>> {
182 Ok(self
183 .inner
184 .ident()
185 .owned()
186 .await?
187 .with_modifier(self.worker_type.modifier_str())
188 .into_vc())
189 }
190
191 #[turbo_tasks::function]
192 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
193 Vc::cell(None)
194 }
195
196 #[turbo_tasks::function]
197 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
198 let this = self.await?;
199 Ok(Vc::cell(vec![
200 ResolvedVc::upcast(
201 WorkerModuleReference::new(*ResolvedVc::upcast(this.inner), this.worker_type)
202 .to_resolved()
203 .await?,
204 ),
205 ResolvedVc::upcast(
206 SingleChunkableModuleReference::new(
207 self.create_worker_module(),
208 rcstr!("createWorker"),
209 ExportUsage::named(rcstr!("default")),
210 )
211 .to_resolved()
212 .await?,
213 ),
214 ]))
215 }
216
217 #[turbo_tasks::function]
218 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
219 ModuleSideEffects::SideEffectFree.cell()
220 }
221}
222
223#[turbo_tasks::value_impl]
224impl ChunkableModule for WorkerLoaderModule {
225 #[turbo_tasks::function]
226 fn as_chunk_item(
227 self: ResolvedVc<Self>,
228 module_graph: ResolvedVc<ModuleGraph>,
229 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
230 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
231 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
232 }
233}
234
235#[turbo_tasks::value_impl]
236impl EcmascriptChunkPlaceable for WorkerLoaderModule {
237 #[turbo_tasks::function]
238 fn get_exports(&self) -> Vc<EcmascriptExports> {
239 EcmascriptExports::Value.cell()
240 }
241
242 #[turbo_tasks::function]
243 async fn chunk_item_content(
244 self: Vc<Self>,
245 chunking_context: Vc<Box<dyn ChunkingContext>>,
246 module_graph: Vc<ModuleGraph>,
247 _async_module_info: Option<Vc<AsyncModuleInfo>>,
248 estimated: bool,
249 ) -> Result<Vc<EcmascriptChunkItemContent>> {
250 let this = self.await?;
251 let options = EcmascriptChunkItemOptions {
252 supports_arrow_functions: *chunking_context
253 .environment()
254 .runtime_versions()
255 .supports_arrow_functions()
256 .await?,
257 ..Default::default()
258 };
259
260 if estimated {
261 let fake_id = ModuleId::String(rcstr!("a_fake_module"));
270 return Ok(EcmascriptChunkItemContent {
271 inner_code: formatdoc! {
272 r#"
273 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})["default"](__dirname + "/" + {worker_path:#}));
274 "#,
275 worker_path = StringifyJs(&"a_fake_path_for_size_estimation"),
276 workers_module = StringifyModuleId(&fake_id),
277 }
278 .into(),
279 options,
280 ..Default::default()
281 }
282 .cell());
283 }
284
285 let create_worker_module = self.create_worker_module();
286 let create_worker_id = create_worker_module.chunk_item_id(chunking_context).await?;
287 let create_worker_export = match ResolvedVc::try_sidecast::<Box<dyn EcmascriptChunkPlaceable>>(
291 create_worker_module.to_resolved().await?,
292 ) {
293 Some(placeable) => {
294 generated_export_key(placeable, chunking_context, &rcstr!("default")).await?
295 }
296 None => rcstr!("default"),
297 };
298
299 let code = match this.worker_type {
300 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
301 let entrypoint_full_path = chunking_context.worker_entrypoint().path().await?;
305
306 let output_root = chunking_context.output_root().owned().await?;
308 let entrypoint_path = output_root
309 .get_path_to(&entrypoint_full_path)
310 .map(|s| s.to_string())
311 .unwrap_or_else(|| entrypoint_full_path.path.to_string());
312
313 let chunks_data = self.chunks_data(chunking_context, module_graph).await?;
315 let chunks_data = chunks_data.iter().try_join().await?;
316 let chunks_data: Vec<_> = chunks_data
317 .iter()
318 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
319 .collect();
320
321 formatdoc! {
322 r#"
323 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})[{export:#}]({entrypoint}, {chunks}));
324 "#,
325 entrypoint = StringifyJs(&entrypoint_path),
326 chunks = StringifyJs(&chunks_data),
327 workers_module = StringifyModuleId(&create_worker_id),
328 export = StringifyJs(&create_worker_export),
329 }
330 }
331 WorkerType::NodeWorkerThread => {
332 let chunk_group = self.chunk_group(chunking_context, module_graph).await?;
336 let assets = chunk_group.assets.await?;
337
338 let Some(entry_asset) = assets.last() else {
345 bail!("cannot find worker entry point asset");
346 };
347 let entry_path = entry_asset.path().await?;
348
349 formatdoc! {
355 r#"
356 {TURBOPACK_EXPORT_VALUE}({TURBOPACK_REQUIRE}({workers_module})[{export:#}](__dirname + "/" + {worker_path:#}));
357 "#,
358 worker_path = StringifyJs(entry_path.file_name()),
359 workers_module = StringifyModuleId(&create_worker_id),
360 export = StringifyJs(&create_worker_export),
361 }
362 }
363 };
364
365 Ok(EcmascriptChunkItemContent {
366 inner_code: code.into(),
367 options,
368 ..Default::default()
369 }
370 .cell())
371 }
372
373 #[turbo_tasks::function]
374 fn chunk_item_output_assets(
375 self: Vc<Self>,
376 chunking_context: Vc<Box<dyn ChunkingContext>>,
377 module_graph: Vc<ModuleGraph>,
378 ) -> Vc<OutputAssetsWithReferenced> {
379 self.chunk_group_with_type(chunking_context, module_graph)
380 }
381}
382
383#[turbo_tasks::value]
384#[derive(ValueToString)]
385#[value_to_string("{} module", self.worker_type.friendly_str())]
386struct WorkerModuleReference {
387 module: ResolvedVc<Box<dyn Module>>,
388 worker_type: WorkerType,
389}
390
391#[turbo_tasks::value_impl]
392impl WorkerModuleReference {
393 #[turbo_tasks::function]
394 pub fn new(module: ResolvedVc<Box<dyn Module>>, worker_type: WorkerType) -> Vc<Self> {
395 Self::cell(WorkerModuleReference {
396 module,
397 worker_type,
398 })
399 }
400}
401
402#[turbo_tasks::value_impl]
403impl ModuleReference for WorkerModuleReference {
404 #[turbo_tasks::function]
405 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
406 *ModuleResolveResult::module(self.module)
407 }
408
409 fn chunking_type(&self) -> Option<ChunkingType> {
410 Some(ChunkingType::Isolated {
411 _ty: match self.worker_type {
412 WorkerType::SharedWebWorker | WorkerType::WebWorker => ChunkGroupType::Evaluated,
413 WorkerType::NodeWorkerThread => ChunkGroupType::Entry,
414 },
415 merge_tag: None,
416 })
417 }
418}