Skip to main content

turbopack_ecmascript/async_chunk/
module.rs

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