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 fn asset_ident_for(module: Vc<Box<dyn ChunkableModule>>) -> Vc<AssetIdent> {
72        module.ident().with_modifier(modifier())
73    }
74}
75
76#[turbo_tasks::value_impl]
77impl Module for ManifestLoaderModule {
78    #[turbo_tasks::function]
79    fn ident(&self) -> Vc<AssetIdent> {
80        self.manifest.module_ident().with_modifier(modifier())
81    }
82
83    #[turbo_tasks::function]
84    fn source(&self) -> Vc<turbopack_core::source::OptionSource> {
85        Vc::cell(None)
86    }
87
88    #[turbo_tasks::function]
89    fn references(&self) -> Vc<ModuleReferences> {
90        Vc::cell(vec![])
91    }
92
93    #[turbo_tasks::function]
94    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
95        ModuleSideEffects::SideEffectFree.cell()
96    }
97}
98
99#[turbo_tasks::value_impl]
100impl ChunkableModule for ManifestLoaderModule {
101    #[turbo_tasks::function]
102    fn as_chunk_item(
103        self: ResolvedVc<Self>,
104        module_graph: ResolvedVc<ModuleGraph>,
105        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
106    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
107        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
108    }
109}
110
111#[turbo_tasks::value_impl]
112impl EcmascriptChunkPlaceable for ManifestLoaderModule {
113    #[turbo_tasks::function]
114    fn get_exports(&self) -> Vc<EcmascriptExports> {
115        EcmascriptExports::Value.cell()
116    }
117
118    #[turbo_tasks::function]
119    async fn chunk_item_content(
120        self: Vc<Self>,
121        _chunking_context: Vc<Box<dyn ChunkingContext>>,
122        _module_graph: Vc<ModuleGraph>,
123        _async_module_info: Option<Vc<AsyncModuleInfo>>,
124        _estimated: bool,
125    ) -> Result<Vc<EcmascriptChunkItemContent>> {
126        let this = self.await?;
127        let mut code = Vec::new();
128
129        let manifest = this.manifest.await?;
130
131        // We need several items in order for a dynamic import to fully load. First, we
132        // need the chunk path of the manifest chunk, relative from the output root. The
133        // chunk is a servable file, which will contain the manifest chunk item, which
134        // will perform the actual chunk traversal and generate load statements.
135        let chunks_server_data = &*self.chunks_data().await?.iter().try_join().await?;
136
137        // We also need the manifest chunk item's id, which points to a CJS module that
138        // exports a promise for all of the necessary chunk loads.
139        let item_id = this
140            .manifest
141            .chunk_item_id(*manifest.chunking_context)
142            .await?;
143
144        // Finally, we need the id of the module that we're actually trying to
145        // dynamically import.
146        let placeable =
147            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(manifest.inner)
148                .ok_or_else(|| anyhow!("asset is not placeable in ecmascript chunk"))?;
149        let dynamic_id = placeable.chunk_item_id(*manifest.chunking_context).await?;
150
151        // This is the code that will be executed when the dynamic import is reached.
152        // It will load the manifest chunk, which will load all the chunks needed by
153        // the dynamic import, and finally we'll be able to import the module we're
154        // trying to dynamically import.
155        // This is similar to what happens when the first evaluated chunk is executed
156        // on first page load, but it's happening on-demand instead of eagerly.
157        writedoc!(
158            code,
159            r#"
160                {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
161                    return Promise.all({chunks_server_data}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{
162                        return {TURBOPACK_REQUIRE}({item_id});
163                    }}).then((chunks) => {{
164                        return Promise.all(chunks.map((chunk) => {TURBOPACK_LOAD}(chunk)));
165                    }}).then(() => {{
166                        return parentImport({dynamic_id});
167                    }});
168                }});
169            "#,
170            chunks_server_data = StringifyJs(
171                &chunks_server_data
172                    .iter()
173                    .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
174                    .collect::<Vec<_>>()
175            ),
176            item_id = StringifyModuleId(&item_id),
177            dynamic_id = StringifyModuleId(&dynamic_id),
178        )?;
179
180        Ok(EcmascriptChunkItemContent {
181            inner_code: code.into(),
182            ..Default::default()
183        }
184        .cell())
185    }
186
187    #[turbo_tasks::function]
188    fn chunk_item_content_ident(
189        self: Vc<Self>,
190        _chunking_context: Vc<Box<dyn ChunkingContext>>,
191        _module_graph: Vc<ModuleGraph>,
192    ) -> Vc<AssetIdent> {
193        self.content_ident()
194    }
195
196    #[turbo_tasks::function]
197    fn chunk_item_output_assets(
198        &self,
199        _chunking_context: Vc<Box<dyn ChunkingContext>>,
200        _module_graph: Vc<ModuleGraph>,
201    ) -> Vc<OutputAssetsWithReferenced> {
202        self.manifest.manifest_chunk_group()
203    }
204}
205
206#[turbo_tasks::value_impl]
207impl ManifestLoaderModule {
208    #[turbo_tasks::function]
209    pub fn content_ident(&self) -> Vc<AssetIdent> {
210        self.manifest.content_ident().with_modifier(modifier())
211    }
212}