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, EcmascriptChunkPlaceable, EcmascriptExports,
24 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 => chunking_context
64 .evaluated_chunk_group_assets(
65 this.inner
66 .ident()
67 .with_modifier(this.worker_type.chunk_modifier_str()),
68 ChunkGroup::Isolated(ResolvedVc::upcast(this.inner)),
69 module_graph,
70 AvailabilityInfo::root(),
71 ),
72 WorkerType::NodeWorkerThread => {
75 let Some(evaluatable) =
76 ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(this.inner)
77 else {
78 bail!("Worker module must be evaluatable");
79 };
80
81 let worker_path = chunking_context
82 .chunk_path(
83 None,
84 this.inner.ident(),
85 Some(rcstr!("[worker thread]")),
86 rcstr!(".js"),
87 )
88 .owned()
89 .await?;
90
91 let entry_result = chunking_context
92 .root_entry_chunk_group(
93 worker_path,
94 ChunkGroup::Isolated(ResolvedVc::upcast(evaluatable)),
95 module_graph,
96 OutputAssets::empty(),
97 OutputAssets::empty(),
98 )
99 .await?;
100
101 OutputAssetsWithReferenced {
102 assets: ResolvedVc::cell(vec![entry_result.asset]),
103 referenced_assets: ResolvedVc::cell(vec![]),
104 references: ResolvedVc::cell(vec![]),
105 }
106 .cell()
107 }
108 })
109 }
110
111 #[turbo_tasks::function]
112 async fn chunks_data(
113 self: Vc<Self>,
114 chunking_context: Vc<Box<dyn ChunkingContext>>,
115 module_graph: Vc<ModuleGraph>,
116 ) -> Result<Vc<ChunksData>> {
117 Ok(ChunkData::from_assets(
118 chunking_context.output_root().owned().await?,
119 *self
120 .chunk_group(chunking_context, module_graph)
121 .await?
122 .assets,
123 ))
124 }
125
126 #[turbo_tasks::function]
128 async fn chunk_group_with_type(
129 self: Vc<Self>,
130 chunking_context: Vc<Box<dyn ChunkingContext>>,
131 module_graph: Vc<ModuleGraph>,
132 ) -> Result<Vc<OutputAssetsWithReferenced>> {
133 let this = self.await?;
134 Ok(match this.worker_type {
135 WorkerType::WebWorker | WorkerType::SharedWebWorker => self
136 .chunk_group(chunking_context, module_graph)
137 .concatenate_asset(chunking_context.worker_entrypoint()),
138 WorkerType::NodeWorkerThread => {
139 self.chunk_group(chunking_context, module_graph)
141 }
142 })
143 }
144}
145
146#[turbo_tasks::value_impl]
147impl Module for WorkerLoaderModule {
148 #[turbo_tasks::function]
149 fn ident(&self) -> Vc<AssetIdent> {
150 self.inner
151 .ident()
152 .with_modifier(self.worker_type.modifier_str())
153 }
154
155 #[turbo_tasks::function]
156 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
157 Vc::cell(None)
158 }
159
160 #[turbo_tasks::function]
161 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
162 let this = self.await?;
163 Ok(Vc::cell(vec![ResolvedVc::upcast(
164 WorkerModuleReference::new(*ResolvedVc::upcast(this.inner), this.worker_type)
165 .to_resolved()
166 .await?,
167 )]))
168 }
169
170 #[turbo_tasks::function]
171 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
172 ModuleSideEffects::SideEffectFree.cell()
173 }
174}
175
176#[turbo_tasks::value_impl]
177impl ChunkableModule for WorkerLoaderModule {
178 #[turbo_tasks::function]
179 fn as_chunk_item(
180 self: ResolvedVc<Self>,
181 module_graph: ResolvedVc<ModuleGraph>,
182 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
183 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
184 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
185 }
186}
187
188#[turbo_tasks::value_impl]
189impl EcmascriptChunkPlaceable for WorkerLoaderModule {
190 #[turbo_tasks::function]
191 fn get_exports(&self) -> Vc<EcmascriptExports> {
192 EcmascriptExports::Value.cell()
193 }
194
195 #[turbo_tasks::function]
196 async fn chunk_item_content(
197 self: Vc<Self>,
198 chunking_context: Vc<Box<dyn ChunkingContext>>,
199 module_graph: Vc<ModuleGraph>,
200 _async_module_info: Option<Vc<AsyncModuleInfo>>,
201 estimated: bool,
202 ) -> Result<Vc<EcmascriptChunkItemContent>> {
203 let this = self.await?;
204
205 if estimated {
206 return Ok(EcmascriptChunkItemContent {
211 inner_code: formatdoc! {
212 r#"
213 {TURBOPACK_EXPORT_VALUE}(function(Ctor, opts) {{
214 return {TURBOPACK_CREATE_WORKER}(Ctor, __dirname + "/" + {worker_path:#}, opts);
215 }});
216 "#,
217 worker_path = StringifyJs(&"a_fake_path_for_size_estimation"),
218 }
219 .into(),
220 ..Default::default()
221 }
222 .cell());
223 }
224
225 let code = match this.worker_type {
226 WorkerType::WebWorker | WorkerType::SharedWebWorker => {
227 let entrypoint_full_path = chunking_context.worker_entrypoint().path().await?;
231
232 let output_root = chunking_context.output_root().owned().await?;
234 let entrypoint_path = output_root
235 .get_path_to(&entrypoint_full_path)
236 .map(|s| s.to_string())
237 .unwrap_or_else(|| entrypoint_full_path.path.to_string());
238
239 let chunks_data = self.chunks_data(chunking_context, module_graph).await?;
241 let chunks_data = chunks_data.iter().try_join().await?;
242 let chunks_data: Vec<_> = chunks_data
243 .iter()
244 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
245 .collect();
246
247 formatdoc! {
248 r#"
249 {TURBOPACK_EXPORT_VALUE}(function(Ctor, opts) {{
250 return {TURBOPACK_CREATE_WORKER}(Ctor, {entrypoint}, {chunks}, opts);
251 }});
252 "#,
253 entrypoint = StringifyJs(&entrypoint_path),
254 chunks = StringifyJs(&chunks_data),
255 }
256 }
257 WorkerType::NodeWorkerThread => {
258 let chunk_group = self.chunk_group(chunking_context, module_graph).await?;
262 let assets = chunk_group.assets.await?;
263
264 let Some(entry_asset) = assets.last() else {
271 bail!("cannot find worker entry point asset");
272 };
273 let entry_path = entry_asset.path().await?;
274
275 formatdoc! {
281 r#"
282 {TURBOPACK_EXPORT_VALUE}(function(Ctor, opts) {{
283 return {TURBOPACK_CREATE_WORKER}(Ctor, __dirname + "/" + {worker_path:#}, opts);
284 }});
285 "#,
286 worker_path = StringifyJs(entry_path.file_name()),
287 }
288 }
289 };
290
291 Ok(EcmascriptChunkItemContent {
292 inner_code: code.into(),
293 ..Default::default()
294 }
295 .cell())
296 }
297
298 #[turbo_tasks::function]
299 fn chunk_item_output_assets(
300 self: Vc<Self>,
301 chunking_context: Vc<Box<dyn ChunkingContext>>,
302 module_graph: Vc<ModuleGraph>,
303 ) -> Vc<OutputAssetsWithReferenced> {
304 self.chunk_group_with_type(chunking_context, module_graph)
305 }
306}
307
308#[turbo_tasks::value]
309#[derive(ValueToString)]
310#[value_to_string("{} module", self.worker_type.friendly_str())]
311struct WorkerModuleReference {
312 module: ResolvedVc<Box<dyn Module>>,
313 worker_type: WorkerType,
314}
315
316#[turbo_tasks::value_impl]
317impl WorkerModuleReference {
318 #[turbo_tasks::function]
319 pub fn new(module: ResolvedVc<Box<dyn Module>>, worker_type: WorkerType) -> Vc<Self> {
320 Self::cell(WorkerModuleReference {
321 module,
322 worker_type,
323 })
324 }
325}
326
327#[turbo_tasks::value_impl]
328impl ModuleReference for WorkerModuleReference {
329 #[turbo_tasks::function]
330 fn resolve_reference(&self) -> Vc<ModuleResolveResult> {
331 *ModuleResolveResult::module(self.module)
332 }
333
334 fn chunking_type(&self) -> Option<ChunkingType> {
335 Some(ChunkingType::Isolated {
336 _ty: match self.worker_type {
337 WorkerType::SharedWebWorker | WorkerType::WebWorker => ChunkGroupType::Evaluated,
338 WorkerType::NodeWorkerThread => ChunkGroupType::Entry,
339 },
340 merge_tag: None,
341 })
342 }
343}