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, HmrChunkListSource, availability_info::AvailabilityInfo,
9    },
10    ident::AssetIdent,
11    module::{Module, ModuleSideEffects},
12    module_graph::{
13        ModuleGraph, chunk_group_info::ChunkGroupKey, module_batch::ChunkableModuleOrBatch,
14    },
15    output::{OutputAssets, OutputAssetsWithReferenced},
16};
17
18use crate::{
19    async_chunk::proxy::{LazyCompilationProxyModule, lazy_compilation_state},
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            ChunkGroupKey::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        let inner_module = ResolvedVc::upcast(this.inner);
80        let batches = this
81            .module_graph
82            .module_batches(this.chunking_context.batching_config())
83            .await?;
84        let module_or_batch = batches.get_entry(inner_module).await?;
85        if let Some(chunkable_module_or_batch) =
86            ChunkableModuleOrBatch::from_module_or_batch(module_or_batch)
87            && this
88                .availability_info
89                .is_available(this.module_graph, chunkable_module_or_batch)
90                .await?
91        {
92            return Ok(OutputAssetsWithReferenced {
93                assets: ResolvedVc::cell(vec![]),
94                referenced_assets: ResolvedVc::cell(vec![]),
95                references: ResolvedVc::cell(vec![]),
96            }
97            .cell());
98        }
99        let chunk_item = self.as_chunk_item(*this.module_graph, *this.chunking_context);
100        let chunk = this
101            .chunking_context
102            .standalone_chunk(chunk_item)
103            .to_resolved()
104            .await?;
105        Ok(OutputAssetsWithReferenced {
106            assets: ResolvedVc::cell(vec![chunk]),
107            referenced_assets: ResolvedVc::cell(vec![]),
108            references: ResolvedVc::cell(vec![]),
109        }
110        .cell())
111    }
112
113    /// Without a chunk list of its own, modules that are only reachable through this dynamic
114    /// import never receive updates.
115    #[turbo_tasks::function]
116    async fn hmr_chunk_list(self: Vc<Self>) -> Result<Vc<OutputAssets>> {
117        let this = self.await?;
118        let ident = self.ident();
119        // The same import can have different chunks depending on which modules its parent
120        // already provides. Keep those chunk lists distinct without hashing their contents,
121        // so their paths remain stable when chunks are added or removed during HMR.
122        let ident = if let Some(availability_ident) = this.availability_info.ident().await? {
123            ident
124                .owned()
125                .await?
126                .with_modifier(availability_ident)
127                .into_vc()
128        } else {
129            ident
130        };
131        Ok(this.chunking_context.hmr_chunk_list(
132            ident,
133            *self.chunk_group().await?.assets,
134            HmrChunkListSource::Dynamic,
135        ))
136    }
137
138    #[turbo_tasks::function]
139    pub fn module_ident(&self) -> Vc<AssetIdent> {
140        self.inner.ident()
141    }
142
143    #[turbo_tasks::function]
144    pub async fn content_ident(&self) -> Result<Vc<AssetIdent>> {
145        let ident = self.inner.ident();
146        Ok(
147            if let Some(availability_ident) = self.availability_info.ident().await? {
148                ident
149                    .owned()
150                    .await?
151                    .with_modifier(availability_ident)
152                    .into_vc()
153            } else {
154                ident
155            },
156        )
157    }
158
159    #[turbo_tasks::function]
160    async fn chunks_data(self: Vc<Self>) -> Result<Vc<ChunksData>> {
161        let this = self.await?;
162        Ok(ChunkData::from_assets(
163            this.chunking_context.output_root().owned().await?,
164            self.chunk_group()
165                .await?
166                .assets
167                .concatenate(self.hmr_chunk_list()),
168        ))
169    }
170}
171
172fn manifest_chunk_reference_description() -> RcStr {
173    rcstr!("manifest chunk")
174}
175
176#[turbo_tasks::value_impl]
177impl Module for ManifestAsyncModule {
178    #[turbo_tasks::function]
179    async fn ident(&self) -> Result<Vc<AssetIdent>> {
180        let ident = self
181            .inner
182            .ident()
183            .owned()
184            .await?
185            .with_modifier(manifest_chunk_reference_description());
186        // Requesting the manifest chunk of a lazily compiled dynamic import is what activates it,
187        // so the key has to survive into the file name, and the path is the only part of an ident
188        // that appears there literally. It must not move to the proxy's own ident, which also
189        // names chunks that ship with the entrypoint and would activate the import on page load.
190        let Some(proxy) = ResolvedVc::try_downcast_type::<LazyCompilationProxyModule>(self.inner)
191        else {
192            return Ok(ident.into_vc());
193        };
194        Ok(ident
195            .rename_as(&format!("*.{}.js", proxy.await?.key))
196            .into_vc())
197    }
198
199    #[turbo_tasks::function]
200    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
201        Vc::cell(None)
202    }
203
204    #[turbo_tasks::function]
205    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
206        ModuleSideEffects::SideEffectFree.cell()
207    }
208}
209
210#[turbo_tasks::value_impl]
211impl ChunkableModule for ManifestAsyncModule {
212    #[turbo_tasks::function]
213    fn as_chunk_item(
214        self: ResolvedVc<Self>,
215        module_graph: ResolvedVc<ModuleGraph>,
216        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
217    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
218        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
219    }
220}
221
222#[turbo_tasks::value_impl]
223impl EcmascriptChunkPlaceable for ManifestAsyncModule {
224    #[turbo_tasks::function]
225    fn get_exports(&self) -> Vc<EcmascriptExports> {
226        EcmascriptExports::Value.cell()
227    }
228
229    #[turbo_tasks::function]
230    async fn chunk_item_content(
231        self: Vc<Self>,
232        _chunking_context: Vc<Box<dyn ChunkingContext>>,
233        _module_graph: Vc<ModuleGraph>,
234        _async_module_info: Option<Vc<AsyncModuleInfo>>,
235        _estimated: bool,
236    ) -> Result<Vc<EcmascriptChunkItemContent>> {
237        let chunks_data = self.chunks_data().await?;
238        let chunks_data = chunks_data.iter().try_join().await?;
239        let chunks_data: Vec<_> = chunks_data
240            .iter()
241            .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
242            .collect();
243
244        let code = formatdoc! {
245            r#"
246                {TURBOPACK_EXPORT_VALUE}({:#});
247            "#,
248            StringifyJs(&chunks_data)
249        };
250
251        Ok(EcmascriptChunkItemContent {
252            inner_code: code.into(),
253            ..Default::default()
254        }
255        .cell())
256    }
257
258    #[turbo_tasks::function]
259    fn chunk_item_content_ident(
260        self: Vc<Self>,
261        _chunking_context: Vc<Box<dyn ChunkingContext>>,
262        _module_graph: Vc<ModuleGraph>,
263    ) -> Vc<AssetIdent> {
264        self.content_ident()
265    }
266
267    #[turbo_tasks::function]
268    async fn chunk_item_output_assets(
269        self: Vc<Self>,
270        _chunking_context: Vc<Box<dyn ChunkingContext>>,
271        _module_graph: Vc<ModuleGraph>,
272    ) -> Result<Vc<OutputAssetsWithReferenced>> {
273        // An unactivated proxy has no references, so the chunk list would name nothing
274        if let Some(proxy) =
275            ResolvedVc::try_downcast_type::<LazyCompilationProxyModule>(self.await?.inner)
276            && !lazy_compilation_state(proxy.await?.key.clone())
277                .await?
278                .is_active()
279        {
280            return Ok(self.chunk_group());
281        }
282        Ok(self
283            .chunk_group()
284            .concatenate(OutputAssetsWithReferenced::from_assets(
285                self.hmr_chunk_list(),
286            )))
287    }
288}