Skip to main content

turbopack_ecmascript/manifest/
loader_module.rs

1use std::io::Write as _;
2
3use anyhow::{Result, anyhow};
4use indoc::writedoc;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
7use turbopack_core::{
8    chunk::{
9        AsyncModuleInfo, ChunkData, ChunkableModule, ChunkingContext, ChunksData,
10        ModuleChunkItemIdExt,
11    },
12    ident::AssetIdent,
13    module::{Module, ModuleSideEffects},
14    module_graph::ModuleGraph,
15    output::OutputAssetsWithReferenced,
16    reference::ModuleReferences,
17};
18
19use super::chunk_asset::ManifestAsyncModule;
20use crate::{
21    chunk::{
22        EcmascriptChunkItemContent, EcmascriptChunkPlaceable, EcmascriptExports,
23        data::EcmascriptChunkData, ecmascript_chunk_item,
24    },
25    runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_LOAD, TURBOPACK_REQUIRE},
26    utils::{StringifyJs, StringifyModuleId},
27};
28
29fn modifier() -> RcStr {
30    rcstr!("loader")
31}
32
33/// The manifest loader module is shipped in the same chunk that uses the dynamic
34/// `import()` expression.
35///
36/// Its responsibility is to load the manifest chunk from the server. The
37/// dynamic import has been rewritten to import this manifest loader module,
38/// which will load the manifest chunk from the server, which will load all
39/// the chunks needed by the dynamic import. Finally, we'll be able to import
40/// the module we're trying to dynamically import.
41///
42/// Splitting the dynamic import into a quickly generate-able manifest loader
43/// module and a slow-to-generate manifest chunk allows for faster incremental
44/// compilation. The traversal won't be performed until the dynamic import is
45/// actually reached, instead of eagerly as part of the chunk that the dynamic
46/// import appears in.
47#[turbo_tasks::value]
48pub struct ManifestLoaderModule {
49    pub manifest: ResolvedVc<ManifestAsyncModule>,
50}
51
52#[turbo_tasks::value_impl]
53impl ManifestLoaderModule {
54    #[turbo_tasks::function]
55    pub fn new(manifest: ResolvedVc<ManifestAsyncModule>) -> Vc<Self> {
56        Self::cell(ManifestLoaderModule { manifest })
57    }
58
59    #[turbo_tasks::function]
60    pub async fn chunks_data(self: Vc<Self>) -> Result<Vc<ChunksData>> {
61        let this = self.await?;
62        let manifest = this.manifest.await?;
63        let chunks = this.manifest.manifest_chunk_group().await?.assets;
64        Ok(ChunkData::from_assets(
65            manifest.chunking_context.output_root().owned().await?,
66            *chunks,
67        ))
68    }
69
70    #[turbo_tasks::function]
71    pub async fn asset_ident_for(module: Vc<Box<dyn ChunkableModule>>) -> Result<Vc<AssetIdent>> {
72        Ok(module
73            .ident()
74            .owned()
75            .await?
76            .with_modifier(modifier())
77            .into_vc())
78    }
79}
80
81#[turbo_tasks::value_impl]
82impl Module for ManifestLoaderModule {
83    #[turbo_tasks::function]
84    async fn ident(&self) -> Result<Vc<AssetIdent>> {
85        Ok(self
86            .manifest
87            .module_ident()
88            .owned()
89            .await?
90            .with_modifier(modifier())
91            .into_vc())
92    }
93
94    #[turbo_tasks::function]
95    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
96        Vc::cell(None)
97    }
98
99    #[turbo_tasks::function]
100    fn references(&self) -> Vc<ModuleReferences> {
101        Vc::cell(vec![])
102    }
103
104    #[turbo_tasks::function]
105    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
106        ModuleSideEffects::SideEffectFree.cell()
107    }
108}
109
110#[turbo_tasks::value_impl]
111impl ChunkableModule for ManifestLoaderModule {
112    #[turbo_tasks::function]
113    fn as_chunk_item(
114        self: ResolvedVc<Self>,
115        module_graph: ResolvedVc<ModuleGraph>,
116        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
117    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
118        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
119    }
120}
121
122#[turbo_tasks::value_impl]
123impl EcmascriptChunkPlaceable for ManifestLoaderModule {
124    #[turbo_tasks::function]
125    fn get_exports(&self) -> Vc<EcmascriptExports> {
126        EcmascriptExports::Value.cell()
127    }
128
129    #[turbo_tasks::function]
130    async fn chunk_item_content(
131        self: Vc<Self>,
132        _chunking_context: Vc<Box<dyn ChunkingContext>>,
133        _module_graph: Vc<ModuleGraph>,
134        _async_module_info: Option<Vc<AsyncModuleInfo>>,
135        _estimated: bool,
136    ) -> Result<Vc<EcmascriptChunkItemContent>> {
137        let this = self.await?;
138        let mut code = Vec::new();
139
140        let manifest = this.manifest.await?;
141
142        // We need several items in order for a dynamic import to fully load. First, we
143        // need the chunk path of the manifest chunk, relative from the output root. The
144        // chunk is a servable file, which will contain the manifest chunk item, which
145        // will perform the actual chunk traversal and generate load statements.
146        let chunks_server_data = &*self.chunks_data().await?.iter().try_join().await?;
147
148        // We also need the manifest chunk item's id, which points to a CJS module that
149        // exports a promise for all of the necessary chunk loads.
150        let item_id = this
151            .manifest
152            .chunk_item_id(*manifest.chunking_context)
153            .await?;
154
155        // Finally, we need the id of the module that we're actually trying to
156        // dynamically import.
157        let placeable =
158            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(manifest.inner)
159                .ok_or_else(|| anyhow!("asset is not placeable in ecmascript chunk"))?;
160        let dynamic_id = placeable.chunk_item_id(*manifest.chunking_context).await?;
161
162        // This is the code that will be executed when the dynamic import is reached.
163        // It will load the manifest chunk, which will load all the chunks needed by
164        // the dynamic import, and finally we'll be able to import the module we're
165        // trying to dynamically import.
166        // This is similar to what happens when the first evaluated chunk is executed
167        // on first page load, but it's happening on-demand instead of eagerly.
168        writedoc!(
169            code,
170            r#"
171                {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
172                    return Promise.all({chunks_server_data}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{
173                        return {TURBOPACK_REQUIRE}({item_id});
174                    }}).then((chunks) => {{
175                        return Promise.all(chunks.map((chunk) => {TURBOPACK_LOAD}(chunk)));
176                    }}).then(() => {{
177                        return parentImport({dynamic_id});
178                    }});
179                }});
180            "#,
181            chunks_server_data = StringifyJs(
182                &chunks_server_data
183                    .iter()
184                    .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
185                    .collect::<Vec<_>>()
186            ),
187            item_id = StringifyModuleId(&item_id),
188            dynamic_id = StringifyModuleId(&dynamic_id),
189        )?;
190
191        Ok(EcmascriptChunkItemContent {
192            inner_code: code.into(),
193            ..Default::default()
194        }
195        .cell())
196    }
197
198    #[turbo_tasks::function]
199    fn chunk_item_content_ident(
200        self: Vc<Self>,
201        _chunking_context: Vc<Box<dyn ChunkingContext>>,
202        _module_graph: Vc<ModuleGraph>,
203    ) -> Vc<AssetIdent> {
204        self.content_ident()
205    }
206
207    #[turbo_tasks::function]
208    fn chunk_item_output_assets(
209        &self,
210        _chunking_context: Vc<Box<dyn ChunkingContext>>,
211        _module_graph: Vc<ModuleGraph>,
212    ) -> Vc<OutputAssetsWithReferenced> {
213        self.manifest.manifest_chunk_group()
214    }
215}
216
217#[turbo_tasks::value_impl]
218impl ManifestLoaderModule {
219    #[turbo_tasks::function]
220    pub async fn content_ident(&self) -> Result<Vc<AssetIdent>> {
221        Ok(self
222            .manifest
223            .content_ident()
224            .owned()
225            .await?
226            .with_modifier(modifier())
227            .into_vc())
228    }
229}