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 id of the module that we're actually trying to
149        // dynamically import.
150        let placeable =
151            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(manifest.inner)
152                .ok_or_else(|| anyhow!("asset is not placeable in ecmascript chunk"))?;
153        let dynamic_id = placeable.chunk_item_id(*manifest.chunking_context).await?;
154
155        if chunks_server_data.is_empty() {
156            // No chunks need to be loaded and thus the module is already available,
157            // so we can just require the module.
158            writedoc!(
159                code,
160                r#"
161                    {TURBOPACK_EXPORT_VALUE}(() => {{
162                        return Promise.resolve().then(() => {TURBOPACK_REQUIRE}({dynamic_id}));
163                    }});
164                "#,
165                dynamic_id = StringifyModuleId(&dynamic_id),
166            )?;
167
168            return Ok(EcmascriptChunkItemContent {
169                inner_code: code.into(),
170                ..Default::default()
171            }
172            .cell());
173        }
174
175        // The manifest chunk item's id points to a CJS module that exports the
176        // chunks required by the dynamic import.
177        let item_id = this
178            .manifest
179            .chunk_item_id(*manifest.chunking_context)
180            .await?;
181
182        // This code will be executed when the dynamic import is reached.
183        // It will load the manifest chunk, which will load all the chunks needed by
184        // the dynamic import, and finally we'll be able to import the module we're
185        // trying to dynamically import.
186        // This is similar to what happens when the first evaluated chunk is executed
187        // on first page load, but it's happening on-demand instead of eagerly.
188        writedoc!(
189            code,
190            r#"
191                {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
192                    return Promise.all({chunks_server_data}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{
193                        return {TURBOPACK_REQUIRE}({item_id});
194                    }}).then((chunks) => {{
195                        return Promise.all(chunks.map((chunk) => {TURBOPACK_LOAD}(chunk)));
196                    }}).then(() => {{
197                        return parentImport({dynamic_id});
198                    }});
199                }});
200            "#,
201            chunks_server_data = StringifyJs(
202                &chunks_server_data
203                    .iter()
204                    .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
205                    .collect::<Vec<_>>()
206            ),
207            item_id = StringifyModuleId(&item_id),
208            dynamic_id = StringifyModuleId(&dynamic_id),
209        )?;
210
211        Ok(EcmascriptChunkItemContent {
212            inner_code: code.into(),
213            ..Default::default()
214        }
215        .cell())
216    }
217
218    #[turbo_tasks::function]
219    fn chunk_item_content_ident(
220        self: Vc<Self>,
221        _chunking_context: Vc<Box<dyn ChunkingContext>>,
222        _module_graph: Vc<ModuleGraph>,
223    ) -> Vc<AssetIdent> {
224        self.content_ident()
225    }
226
227    #[turbo_tasks::function]
228    fn chunk_item_output_assets(
229        &self,
230        _chunking_context: Vc<Box<dyn ChunkingContext>>,
231        _module_graph: Vc<ModuleGraph>,
232    ) -> Vc<OutputAssetsWithReferenced> {
233        self.manifest.manifest_chunk_group()
234    }
235}
236
237#[turbo_tasks::value_impl]
238impl ManifestLoaderModule {
239    #[turbo_tasks::function]
240    pub async fn content_ident(&self) -> Result<Vc<AssetIdent>> {
241        Ok(self
242            .manifest
243            .content_ident()
244            .owned()
245            .await?
246            .with_modifier(modifier())
247            .into_vc())
248    }
249}