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