turbopack_ecmascript/async_chunk/
module.rs1use anyhow::Result;
2use indoc::formatdoc;
3use tracing::Instrument;
4use turbo_rcstr::rcstr;
5use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
6use turbopack_core::{
7 chunk::{
8 AsyncModuleInfo, ChunkData, ChunkableModule, ChunkingContext, ChunkingContextExt,
9 ChunksData, ModuleChunkItemIdExt, availability_info::AvailabilityInfo,
10 },
11 ident::AssetIdent,
12 module::{Module, ModuleSideEffects},
13 module_graph::{
14 ModuleGraph, chunk_group_info::ChunkGroup, module_batch::ChunkableModuleOrBatch,
15 },
16 output::OutputAssetsWithReferenced,
17 reference::{ModuleReferences, SingleModuleReference},
18};
19
20use crate::{
21 chunk::{
22 EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
23 data::EcmascriptChunkData, ecmascript_chunk_item,
24 },
25 runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_LOAD},
26 utils::{StringifyJs, StringifyModuleId},
27};
28
29#[turbo_tasks::value]
32pub struct AsyncLoaderModule {
33 pub inner: ResolvedVc<Box<dyn ChunkableModule>>,
34 pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
35 pub availability_info: AvailabilityInfo,
36}
37
38#[turbo_tasks::value_impl]
39impl AsyncLoaderModule {
40 #[turbo_tasks::function]
41 pub fn new(
42 module: ResolvedVc<Box<dyn ChunkableModule>>,
43 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
44 availability_info: AvailabilityInfo,
45 ) -> Vc<Self> {
46 Self::cell(AsyncLoaderModule {
47 inner: module,
48 chunking_context,
49 availability_info,
50 })
51 }
52
53 #[turbo_tasks::function]
54 pub fn asset_ident_for(module: Vc<Box<dyn ChunkableModule>>) -> Vc<AssetIdent> {
55 module.ident().with_modifier(rcstr!("async loader"))
56 }
57
58 #[turbo_tasks::function]
59 pub(super) async fn chunk_group(
60 &self,
61 module_graph: Vc<ModuleGraph>,
62 ) -> Result<Vc<OutputAssetsWithReferenced>> {
63 if let Some(chunk_items) = self.availability_info.available_modules() {
64 let inner_module = ResolvedVc::upcast(self.inner);
65 let batches = module_graph
66 .module_batches(self.chunking_context.batching_config())
67 .await?;
68 let module_or_batch = batches.get_entry(inner_module).await?;
69 if let Some(chunkable_module_or_batch) =
70 ChunkableModuleOrBatch::from_module_or_batch(module_or_batch)
71 && *chunk_items.get(chunkable_module_or_batch.into()).await?
72 {
73 return Ok(OutputAssetsWithReferenced {
74 assets: ResolvedVc::cell(vec![]),
75 referenced_assets: ResolvedVc::cell(vec![]),
76 references: ResolvedVc::cell(vec![]),
77 }
78 .cell());
79 }
80 }
81 Ok(self.chunking_context.chunk_group_assets(
82 self.inner.ident(),
83 ChunkGroup::Async(ResolvedVc::upcast(self.inner)),
84 module_graph,
85 self.availability_info,
86 ))
87 }
88
89 #[turbo_tasks::function]
90 async fn chunks_data(self: Vc<Self>, module_graph: Vc<ModuleGraph>) -> Result<Vc<ChunksData>> {
91 let this = self.await?;
92 let span = tracing::info_span!(
93 "compute async chunks",
94 name = self.ident().to_string().await?.as_str()
95 );
96 async move {
97 Ok(ChunkData::from_assets(
98 this.chunking_context.output_root().owned().await?,
99 *self.chunk_group(module_graph).await?.assets,
100 ))
101 }
102 .instrument(span)
103 .await
104 }
105}
106
107#[turbo_tasks::value_impl]
108impl Module for AsyncLoaderModule {
109 #[turbo_tasks::function]
110 fn ident(&self) -> Vc<AssetIdent> {
111 Self::asset_ident_for(*self.inner)
112 }
113
114 #[turbo_tasks::function]
115 fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
116 Vc::cell(None)
117 }
118
119 #[turbo_tasks::function]
120 async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
121 Ok(Vc::cell(vec![ResolvedVc::upcast(
122 SingleModuleReference::new(
123 *ResolvedVc::upcast(self.await?.inner),
124 rcstr!("async module"),
125 )
126 .to_resolved()
127 .await?,
128 )]))
129 }
130
131 #[turbo_tasks::function]
132 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
133 ModuleSideEffects::SideEffectFree.cell()
134 }
135}
136
137#[turbo_tasks::value_impl]
138impl ChunkableModule for AsyncLoaderModule {
139 #[turbo_tasks::function]
140 fn as_chunk_item(
141 self: ResolvedVc<Self>,
142 module_graph: ResolvedVc<ModuleGraph>,
143 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
144 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
145 ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
146 }
147}
148
149#[turbo_tasks::value_impl]
150impl EcmascriptChunkPlaceable for AsyncLoaderModule {
151 #[turbo_tasks::function]
152 fn get_exports(&self) -> Vc<EcmascriptExports> {
153 EcmascriptExports::Value.cell()
154 }
155
156 #[turbo_tasks::function]
157 async fn chunk_item_content(
158 self: Vc<Self>,
159 chunking_context: Vc<Box<dyn ChunkingContext>>,
160 module_graph: Vc<ModuleGraph>,
161 _async_module_info: Option<Vc<AsyncModuleInfo>>,
162 estimated: bool,
163 ) -> Result<Vc<EcmascriptChunkItemContent>> {
164 if estimated {
165 let code = formatdoc! {
166 r#"
167 {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
168 return Promise.all([].map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{}});
169 }});
170 "#,
171 };
172 return Ok(EcmascriptChunkItemContent {
173 inner_code: code.into(),
174 ..Default::default()
175 }
176 .cell());
177 }
178
179 let this = self.await?;
180
181 let id = if let Some(placeable) =
182 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(this.inner)
183 {
184 Some(placeable.chunk_item_id(chunking_context).await?)
185 } else {
186 None
187 };
188 let id = id.as_ref();
189
190 let chunks_data = self.chunks_data(module_graph).await?;
191 let chunks_data = chunks_data.iter().try_join().await?;
192 let chunks_data: Vec<_> = chunks_data
193 .iter()
194 .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
195 .collect();
196
197 let code = match (id, chunks_data.is_empty()) {
198 (Some(id), true) => {
199 formatdoc! {
200 r#"
201 {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
202 return Promise.resolve().then(() => {{
203 return parentImport({id});
204 }});
205 }});
206 "#,
207 id = StringifyModuleId(id),
208 }
209 }
210 (Some(id), false) => {
211 formatdoc! {
212 r#"
213 {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
214 return Promise.all({chunks:#}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{
215 return parentImport({id});
216 }});
217 }});
218 "#,
219 chunks = StringifyJs(&chunks_data),
220 id = StringifyModuleId(id),
221 }
222 }
223 (None, true) => {
224 formatdoc! {
225 r#"
226 {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
227 return Promise.resolve();
228 }});
229 "#,
230 }
231 }
232 (None, false) => {
233 formatdoc! {
234 r#"
235 {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
236 return Promise.all({chunks:#}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{}});
237 }});
238 "#,
239 chunks = StringifyJs(&chunks_data),
240 }
241 }
242 };
243
244 Ok(EcmascriptChunkItemContent {
245 inner_code: code.into(),
246 ..Default::default()
247 }
248 .cell())
249 }
250
251 #[turbo_tasks::function]
252 async fn chunk_item_content_ident(
253 self: Vc<Self>,
254 _chunking_context: Vc<Box<dyn ChunkingContext>>,
255 module_graph: Vc<ModuleGraph>,
256 ) -> Result<Vc<AssetIdent>> {
257 let mut ident = self.ident();
258
259 let this = self.await?;
260
261 let nested_async_availability = this
262 .chunking_context
263 .is_nested_async_availability_enabled()
264 .await?;
265
266 let availability_ident = if *nested_async_availability {
267 Some(
268 self.chunks_data(module_graph)
269 .hash()
270 .await?
271 .to_string()
272 .into(),
273 )
274 } else {
275 this.availability_info.ident().await?
276 };
277
278 if let Some(availability_ident) = availability_ident {
279 ident = ident.with_modifier(availability_ident)
280 }
281
282 Ok(ident)
283 }
284
285 #[turbo_tasks::function]
286 fn chunk_item_output_assets(
287 self: Vc<Self>,
288 _chunking_context: Vc<Box<dyn ChunkingContext>>,
289 module_graph: Vc<ModuleGraph>,
290 ) -> Vc<OutputAssetsWithReferenced> {
291 self.chunk_group(module_graph)
292 }
293}