Skip to main content

turbopack_ecmascript/manifest/
chunk_asset.rs

1use anyhow::Result;
2use indoc::formatdoc;
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
5use turbopack_core::{
6    chunk::{
7        AsyncModuleInfo, ChunkData, ChunkableModule, ChunkingContext, ChunkingContextExt,
8        ChunksData, availability_info::AvailabilityInfo,
9    },
10    ident::AssetIdent,
11    module::{Module, ModuleSideEffects},
12    module_graph::{
13        ModuleGraph, chunk_group_info::ChunkGroup, module_batch::ChunkableModuleOrBatch,
14    },
15    output::OutputAssetsWithReferenced,
16    reference::{ModuleReferences, SingleOutputAssetReference},
17};
18
19use crate::{
20    chunk::{
21        EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
22        data::EcmascriptChunkData, ecmascript_chunk_item,
23    },
24    runtime_functions::TURBOPACK_EXPORT_VALUE,
25    utils::StringifyJs,
26};
27
28/// The manifest module is deferred until requested by the manifest loader
29/// item when the dynamic `import()` expression is reached.
30///
31/// Its responsibility is to generate a Promise that will resolve only after
32/// all the necessary chunks needed by the dynamic import are loaded by the client.
33///
34/// Splitting the dynamic import into a quickly generate-able manifest loader
35/// item and a slow-to-generate manifest chunk allows for faster incremental
36/// compilation. The traversal won't be performed until the dynamic import is
37/// actually reached, instead of eagerly as part of the chunk that the dynamic
38/// import appears in.
39#[turbo_tasks::value(shared)]
40pub struct ManifestAsyncModule {
41    pub inner: ResolvedVc<Box<dyn ChunkableModule>>,
42    pub module_graph: ResolvedVc<ModuleGraph>,
43    pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
44    pub availability_info: AvailabilityInfo,
45}
46
47#[turbo_tasks::value_impl]
48impl ManifestAsyncModule {
49    #[turbo_tasks::function]
50    pub fn new(
51        module: ResolvedVc<Box<dyn ChunkableModule>>,
52        module_graph: ResolvedVc<ModuleGraph>,
53        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
54        availability_info: AvailabilityInfo,
55    ) -> Vc<Self> {
56        Self::cell(ManifestAsyncModule {
57            inner: module,
58            module_graph,
59            chunking_context,
60            availability_info,
61        })
62    }
63
64    #[turbo_tasks::function]
65    pub(super) fn chunk_group(&self) -> Vc<OutputAssetsWithReferenced> {
66        self.chunking_context.chunk_group_assets(
67            self.inner.ident(),
68            ChunkGroup::Async(ResolvedVc::upcast(self.inner)),
69            *self.module_graph,
70            self.availability_info,
71        )
72    }
73
74    #[turbo_tasks::function]
75    pub async fn manifest_chunk_group(
76        self: ResolvedVc<Self>,
77    ) -> Result<Vc<OutputAssetsWithReferenced>> {
78        let this = self.await?;
79        if let Some(chunk_items) = this.availability_info.available_modules() {
80            let inner_module = ResolvedVc::upcast(this.inner);
81            let batches = this
82                .module_graph
83                .module_batches(this.chunking_context.batching_config())
84                .await?;
85            let module_or_batch = batches.get_entry(inner_module).await?;
86            if let Some(chunkable_module_or_batch) =
87                ChunkableModuleOrBatch::from_module_or_batch(module_or_batch)
88                && *chunk_items.get(chunkable_module_or_batch.into()).await?
89            {
90                return Ok(OutputAssetsWithReferenced {
91                    assets: ResolvedVc::cell(vec![]),
92                    referenced_assets: ResolvedVc::cell(vec![]),
93                    references: ResolvedVc::cell(vec![]),
94                }
95                .cell());
96            }
97        }
98        Ok(this.chunking_context.chunk_group_assets(
99            self.ident(),
100            ChunkGroup::Async(ResolvedVc::upcast(self)),
101            *this.module_graph,
102            this.availability_info,
103        ))
104    }
105
106    #[turbo_tasks::function]
107    pub fn module_ident(&self) -> Vc<AssetIdent> {
108        self.inner.ident()
109    }
110
111    #[turbo_tasks::function]
112    pub async fn content_ident(&self) -> Result<Vc<AssetIdent>> {
113        let mut ident = self.inner.ident();
114        if let Some(available_modules) = self.availability_info.available_modules() {
115            ident = ident.with_modifier(available_modules.hash().await?.to_string().into());
116        }
117        Ok(ident)
118    }
119
120    #[turbo_tasks::function]
121    async fn chunks_data(self: Vc<Self>) -> Result<Vc<ChunksData>> {
122        let this = self.await?;
123        Ok(ChunkData::from_assets(
124            this.chunking_context.output_root().owned().await?,
125            *self.chunk_group().await?.assets,
126        ))
127    }
128}
129
130fn manifest_chunk_reference_description() -> RcStr {
131    rcstr!("manifest chunk")
132}
133
134#[turbo_tasks::value_impl]
135impl Module for ManifestAsyncModule {
136    #[turbo_tasks::function]
137    fn ident(&self) -> Vc<AssetIdent> {
138        self.inner
139            .ident()
140            .with_modifier(manifest_chunk_reference_description())
141    }
142
143    #[turbo_tasks::function]
144    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
145        Vc::cell(None)
146    }
147
148    #[turbo_tasks::function]
149    async fn references(self: Vc<Self>) -> Result<Vc<ModuleReferences>> {
150        let assets = self.chunk_group().expand_all_assets().await?;
151
152        Ok(Vc::cell(
153            assets
154                .into_iter()
155                .copied()
156                .map(|chunk| async move {
157                    Ok(ResolvedVc::upcast(
158                        SingleOutputAssetReference::new(
159                            *chunk,
160                            manifest_chunk_reference_description(),
161                        )
162                        .to_resolved()
163                        .await?,
164                    ))
165                })
166                .try_join()
167                .await?,
168        ))
169    }
170
171    #[turbo_tasks::function]
172    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
173        ModuleSideEffects::SideEffectFree.cell()
174    }
175}
176
177#[turbo_tasks::value_impl]
178impl ChunkableModule for ManifestAsyncModule {
179    #[turbo_tasks::function]
180    fn as_chunk_item(
181        self: ResolvedVc<Self>,
182        module_graph: ResolvedVc<ModuleGraph>,
183        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
184    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
185        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
186    }
187}
188
189#[turbo_tasks::value_impl]
190impl EcmascriptChunkPlaceable for ManifestAsyncModule {
191    #[turbo_tasks::function]
192    fn get_exports(&self) -> Vc<EcmascriptExports> {
193        EcmascriptExports::Value.cell()
194    }
195
196    #[turbo_tasks::function]
197    async fn chunk_item_content(
198        self: Vc<Self>,
199        _chunking_context: Vc<Box<dyn ChunkingContext>>,
200        _module_graph: Vc<ModuleGraph>,
201        _async_module_info: Option<Vc<AsyncModuleInfo>>,
202        _estimated: bool,
203    ) -> Result<Vc<EcmascriptChunkItemContent>> {
204        let chunks_data = self.chunks_data().await?;
205        let chunks_data = chunks_data.iter().try_join().await?;
206        let chunks_data: Vec<_> = chunks_data
207            .iter()
208            .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
209            .collect();
210
211        let code = formatdoc! {
212            r#"
213                {TURBOPACK_EXPORT_VALUE}({:#});
214            "#,
215            StringifyJs(&chunks_data)
216        };
217
218        Ok(EcmascriptChunkItemContent {
219            inner_code: code.into(),
220            ..Default::default()
221        }
222        .cell())
223    }
224
225    #[turbo_tasks::function]
226    fn chunk_item_content_ident(
227        self: Vc<Self>,
228        _chunking_context: Vc<Box<dyn ChunkingContext>>,
229        _module_graph: Vc<ModuleGraph>,
230    ) -> Vc<AssetIdent> {
231        self.content_ident()
232    }
233
234    #[turbo_tasks::function]
235    fn chunk_item_output_assets(
236        self: Vc<Self>,
237        _chunking_context: Vc<Box<dyn ChunkingContext>>,
238        _module_graph: Vc<ModuleGraph>,
239    ) -> Vc<OutputAssetsWithReferenced> {
240        self.chunk_group()
241    }
242}