turbopack_ecmascript/async_chunk/
chunk_item.rs

1use anyhow::Result;
2use indoc::formatdoc;
3use tracing::Instrument;
4use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
5use turbopack_core::{
6    chunk::{
7        AsyncModuleInfo, ChunkData, ChunkItem, ChunkType, ChunkingContext, ChunkingContextExt,
8        ChunksData, ModuleChunkItemIdExt,
9    },
10    ident::AssetIdent,
11    module::Module,
12    module_graph::{
13        ModuleGraph, chunk_group_info::ChunkGroup, module_batch::ChunkableModuleOrBatch,
14    },
15    output::{OutputAssetsReference, OutputAssetsWithReferenced},
16};
17
18use crate::{
19    async_chunk::module::AsyncLoaderModule,
20    chunk::{
21        EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkPlaceable,
22        EcmascriptChunkType, data::EcmascriptChunkData,
23    },
24    runtime_functions::{TURBOPACK_EXPORT_VALUE, TURBOPACK_LOAD},
25    utils::{StringifyJs, StringifyModuleId},
26};
27
28#[turbo_tasks::value(shared)]
29pub struct AsyncLoaderChunkItem {
30    pub module: ResolvedVc<AsyncLoaderModule>,
31    pub module_graph: ResolvedVc<ModuleGraph>,
32    pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
33}
34
35#[turbo_tasks::value_impl]
36impl AsyncLoaderChunkItem {
37    #[turbo_tasks::function]
38    pub(super) async fn chunk_group(&self) -> Result<Vc<OutputAssetsWithReferenced>> {
39        let module = self.module.await?;
40        if let Some(chunk_items) = module.availability_info.available_modules() {
41            let inner_module = ResolvedVc::upcast(module.inner);
42            let batches = self
43                .module_graph
44                .module_batches(self.chunking_context.batching_config())
45                .await?;
46            let module_or_batch = batches.get_entry(inner_module).await?;
47            if let Some(chunkable_module_or_batch) =
48                ChunkableModuleOrBatch::from_module_or_batch(module_or_batch)
49                && *chunk_items.get(chunkable_module_or_batch.into()).await?
50            {
51                return Ok(OutputAssetsWithReferenced {
52                    assets: ResolvedVc::cell(vec![]),
53                    referenced_assets: ResolvedVc::cell(vec![]),
54                    references: ResolvedVc::cell(vec![]),
55                }
56                .cell());
57            }
58        }
59        Ok(self.chunking_context.chunk_group_assets(
60            module.inner.ident(),
61            ChunkGroup::Async(ResolvedVc::upcast(module.inner)),
62            *self.module_graph,
63            module.availability_info,
64        ))
65    }
66
67    #[turbo_tasks::function]
68    async fn chunks_data(self: Vc<Self>) -> Result<Vc<ChunksData>> {
69        let this = self.await?;
70        let span = tracing::info_span!(
71            "compute async chunks",
72            name = this.module.ident().to_string().await?.as_str()
73        );
74        async move {
75            Ok(ChunkData::from_assets(
76                this.chunking_context.output_root().owned().await?,
77                *self.chunk_group().await?.assets,
78            ))
79        }
80        .instrument(span)
81        .await
82    }
83}
84
85#[turbo_tasks::value_impl]
86impl EcmascriptChunkItem for AsyncLoaderChunkItem {
87    #[turbo_tasks::function]
88    async fn content(self: Vc<Self>) -> Result<Vc<EcmascriptChunkItemContent>> {
89        let this = self.await?;
90        let module = this.module.await?;
91
92        let id = if let Some(placeable) =
93            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(module.inner)
94        {
95            Some(placeable.chunk_item_id(*this.chunking_context).await?)
96        } else {
97            None
98        };
99        let id = id.as_deref();
100
101        let chunks_data = self.chunks_data().await?;
102        let chunks_data = chunks_data.iter().try_join().await?;
103        let chunks_data: Vec<_> = chunks_data
104            .iter()
105            .map(|chunk_data| EcmascriptChunkData::new(chunk_data))
106            .collect();
107
108        let code = match (id, chunks_data.is_empty()) {
109            (Some(id), true) => {
110                formatdoc! {
111                    r#"
112                        {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
113                            return Promise.resolve().then(() => {{
114                                return parentImport({id});
115                            }});
116                        }});
117                    "#,
118                    id = StringifyModuleId(id),
119                }
120            }
121            (Some(id), false) => {
122                formatdoc! {
123                    r#"
124                        {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
125                            return Promise.all({chunks:#}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{
126                                return parentImport({id});
127                            }});
128                        }});
129                    "#,
130                    chunks = StringifyJs(&chunks_data),
131                    id = StringifyModuleId(id),
132                }
133            }
134            (None, true) => {
135                formatdoc! {
136                    r#"
137                        {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
138                            return Promise.resolve();
139                        }});
140                    "#,
141                }
142            }
143            (None, false) => {
144                formatdoc! {
145                    r#"
146                        {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
147                            return Promise.all({chunks:#}.map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{}});
148                        }});
149                    "#,
150                    chunks = StringifyJs(&chunks_data),
151                }
152            }
153        };
154
155        Ok(EcmascriptChunkItemContent {
156            inner_code: code.into(),
157            ..Default::default()
158        }
159        .cell())
160    }
161
162    #[turbo_tasks::function]
163    fn content_with_async_module_info(
164        self: Vc<Self>,
165        _async_module_info: Option<Vc<AsyncModuleInfo>>,
166        estimated: bool,
167    ) -> Vc<EcmascriptChunkItemContent> {
168        if estimated {
169            let code = formatdoc! {
170                r#"
171                    {TURBOPACK_EXPORT_VALUE}((parentImport) => {{
172                        return Promise.all([].map((chunk) => {TURBOPACK_LOAD}(chunk))).then(() => {{}});
173                    }});
174                "#,
175            };
176            EcmascriptChunkItemContent {
177                inner_code: code.into(),
178                ..Default::default()
179            }
180            .cell()
181        } else {
182            self.content()
183        }
184    }
185}
186
187#[turbo_tasks::value_impl]
188impl OutputAssetsReference for AsyncLoaderChunkItem {
189    #[turbo_tasks::function]
190    fn references(self: Vc<Self>) -> Vc<OutputAssetsWithReferenced> {
191        self.chunk_group()
192    }
193}
194
195#[turbo_tasks::value_impl]
196impl ChunkItem for AsyncLoaderChunkItem {
197    #[turbo_tasks::function]
198    fn asset_ident(&self) -> Vc<AssetIdent> {
199        self.module.ident()
200    }
201
202    #[turbo_tasks::function]
203    async fn content_ident(self: Vc<Self>) -> Result<Vc<AssetIdent>> {
204        let mut ident = self.module().ident();
205
206        let this = self.await?;
207
208        let nested_async_availability = this
209            .chunking_context
210            .is_nested_async_availability_enabled()
211            .await?;
212
213        let availability_ident = if *nested_async_availability {
214            Some(self.chunks_data().hash().await?.to_string().into())
215        } else {
216            this.module.await?.availability_info.ident().await?
217        };
218
219        if let Some(availability_ident) = availability_ident {
220            ident = ident.with_modifier(availability_ident)
221        }
222
223        Ok(ident)
224    }
225
226    #[turbo_tasks::function]
227    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
228        *self.chunking_context
229    }
230
231    #[turbo_tasks::function]
232    async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
233        Ok(Vc::upcast(
234            Vc::<EcmascriptChunkType>::default().resolve().await?,
235        ))
236    }
237
238    #[turbo_tasks::function]
239    fn module(&self) -> Vc<Box<dyn Module>> {
240        *ResolvedVc::upcast(self.module)
241    }
242}