Skip to main content

turbopack_ecmascript/async_chunk/
module.rs

1use 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/// 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 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}