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