Skip to main content

turbopack_browser/ecmascript/
chunk.rs

1use anyhow::{Context, Result};
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{FxIndexSet, ResolvedVc, ValueToString, Vc};
4use turbo_tasks_fs::{FileContent, FileSystemPath};
5use turbopack_core::{
6    asset::{Asset, AssetContent},
7    chunk::{Chunk, ChunkingContext, OutputChunk, OutputChunkRuntimeInfo},
8    ident::AssetIdent,
9    introspect::{Introspectable, IntrospectableChildren},
10    output::{OutputAsset, OutputAssetsReference, OutputAssetsWithReferenced},
11    source_map::{GenerateSourceMap, SourceMapAsset},
12    version::VersionedContent,
13};
14use turbopack_ecmascript::chunk::EcmascriptChunk;
15
16use crate::{BrowserChunkingContext, ecmascript::content::EcmascriptBrowserChunkContent};
17
18/// Development Ecmascript chunk.
19#[turbo_tasks::value(shared)]
20#[derive(ValueToString)]
21#[value_to_string("Ecmascript Dev Chunk")]
22pub struct EcmascriptBrowserChunk {
23    chunking_context: ResolvedVc<BrowserChunkingContext>,
24    chunk: ResolvedVc<EcmascriptChunk>,
25}
26
27#[turbo_tasks::value_impl]
28impl EcmascriptBrowserChunk {
29    /// Creates a new [`Vc<EcmascriptDevChunk>`].
30    #[turbo_tasks::function]
31    pub fn new(
32        chunking_context: ResolvedVc<BrowserChunkingContext>,
33        chunk: ResolvedVc<EcmascriptChunk>,
34    ) -> Vc<Self> {
35        EcmascriptBrowserChunk {
36            chunking_context,
37            chunk,
38        }
39        .cell()
40    }
41
42    #[turbo_tasks::function]
43    async fn source_map(self: Vc<Self>) -> Result<Vc<SourceMapAsset>> {
44        let this = self.await?;
45        Ok(SourceMapAsset::new(
46            Vc::upcast(*this.chunking_context),
47            this.ident_for_path().await?,
48            Vc::upcast(self),
49        ))
50    }
51}
52
53impl EcmascriptBrowserChunk {
54    async fn component_chunk_assets(&self) -> Result<Vec<ResolvedVc<Box<dyn OutputAsset>>>> {
55        let component_chunks = self.chunk.component_chunks().await?;
56        let mut assets = Vec::with_capacity(component_chunks.len());
57        for &component in component_chunks.iter() {
58            let component_chunk = ResolvedVc::try_downcast_type::<EcmascriptChunk>(component)
59                .context("merged chunk component_chunks must be ecmascript chunks")?;
60            assets.push(ResolvedVc::upcast(
61                EcmascriptBrowserChunk::new(*self.chunking_context, *component_chunk)
62                    .to_resolved()
63                    .await?,
64            ));
65        }
66        Ok(assets)
67    }
68
69    async fn ident_for_path(&self) -> Result<Vc<AssetIdent>> {
70        Ok(self
71            .chunk
72            .ident()
73            .owned()
74            .await?
75            .with_modifier(rcstr!("ecmascript dev chunk"))
76            .into_vc())
77    }
78}
79
80#[turbo_tasks::value_impl]
81impl OutputChunk for EcmascriptBrowserChunk {
82    #[turbo_tasks::function]
83    async fn runtime_info(&self) -> Result<Vc<OutputChunkRuntimeInfo>> {
84        let component_assets = self.component_chunk_assets().await?;
85        let module_chunks = if component_assets.is_empty() {
86            None
87        } else {
88            Some(ResolvedVc::cell(component_assets))
89        };
90        Ok(OutputChunkRuntimeInfo {
91            included_ids: Some(self.chunk.entry_ids().to_resolved().await?),
92            module_chunks,
93            ..Default::default()
94        }
95        .cell())
96    }
97}
98
99#[turbo_tasks::value_impl]
100impl EcmascriptBrowserChunk {
101    #[turbo_tasks::function]
102    pub(crate) async fn own_content(self: Vc<Self>) -> Result<Vc<EcmascriptBrowserChunkContent>> {
103        let this = self.await?;
104        Ok(EcmascriptBrowserChunkContent::new(
105            *this.chunking_context,
106            self,
107            this.chunk.chunk_content(),
108            self.source_map(),
109        ))
110    }
111
112    #[turbo_tasks::function]
113    pub fn chunk(&self) -> Result<Vc<Box<dyn Chunk>>> {
114        Ok(Vc::upcast(*self.chunk))
115    }
116}
117
118#[turbo_tasks::value_impl]
119impl OutputAssetsReference for EcmascriptBrowserChunk {
120    #[turbo_tasks::function]
121    async fn references(self: Vc<Self>) -> Result<Vc<OutputAssetsWithReferenced>> {
122        let this = self.await?;
123        let chunk_references = this.chunk.references().await?;
124        let include_source_map = *this
125            .chunking_context
126            .reference_chunk_source_maps(Vc::upcast(self))
127            .await?;
128        let ref_assets = chunk_references.assets.await?;
129        let mut assets =
130            Vec::with_capacity(ref_assets.len() + if include_source_map { 1 } else { 0 });
131
132        assets.extend(ref_assets.iter().copied());
133
134        if include_source_map {
135            assets.push(ResolvedVc::upcast(self.source_map().to_resolved().await?));
136        }
137
138        // Constituent component chunks of a merged chunk are emitted as referenced assets
139        // so the runtime can fetch an individual component when it's already cached, without
140        // them being eagerly loaded as primary chunks.
141        let component_assets = this.component_chunk_assets().await?;
142        let referenced_assets = if component_assets.is_empty() {
143            chunk_references.referenced_assets
144        } else {
145            let mut referenced: Vec<_> = chunk_references
146                .referenced_assets
147                .await?
148                .iter()
149                .copied()
150                .collect();
151            referenced.extend(component_assets);
152            ResolvedVc::cell(referenced)
153        };
154
155        Ok(OutputAssetsWithReferenced {
156            assets: ResolvedVc::cell(assets),
157            referenced_assets,
158            references: chunk_references.references,
159        }
160        .cell())
161    }
162}
163
164#[turbo_tasks::value_impl]
165impl OutputAsset for EcmascriptBrowserChunk {
166    #[turbo_tasks::function]
167    async fn path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
168        let this = self.await?;
169        let ident = this.ident_for_path().await?;
170        Ok(this
171            .chunking_context
172            .chunk_path(Some(Vc::upcast(self)), ident, None, rcstr!(".js")))
173    }
174}
175
176#[turbo_tasks::value_impl]
177impl Asset for EcmascriptBrowserChunk {
178    #[turbo_tasks::function]
179    fn content(self: Vc<Self>) -> Vc<AssetContent> {
180        self.own_content().content()
181    }
182
183    #[turbo_tasks::function]
184    fn versioned_content(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>> {
185        Vc::upcast(self.own_content())
186    }
187}
188
189#[turbo_tasks::value_impl]
190impl GenerateSourceMap for EcmascriptBrowserChunk {
191    #[turbo_tasks::function]
192    fn generate_source_map(self: Vc<Self>) -> Vc<FileContent> {
193        self.own_content().generate_source_map()
194    }
195
196    #[turbo_tasks::function]
197    fn by_section(self: Vc<Self>, section: RcStr) -> Vc<FileContent> {
198        self.own_content().by_section(section)
199    }
200}
201
202#[turbo_tasks::value_impl]
203impl Introspectable for EcmascriptBrowserChunk {
204    #[turbo_tasks::function]
205    fn ty(&self) -> Vc<RcStr> {
206        Vc::cell(rcstr!("dev ecmascript chunk"))
207    }
208
209    #[turbo_tasks::function]
210    fn title(self: Vc<Self>) -> Vc<RcStr> {
211        self.path().to_string()
212    }
213
214    #[turbo_tasks::function]
215    fn details(&self) -> Vc<RcStr> {
216        Vc::cell(rcstr!("generates a development ecmascript chunk"))
217    }
218
219    #[turbo_tasks::function]
220    fn children(&self) -> Result<Vc<IntrospectableChildren>> {
221        let mut children = FxIndexSet::default();
222        let chunk = ResolvedVc::upcast::<Box<dyn Introspectable>>(self.chunk);
223        children.insert((rcstr!("chunk"), chunk));
224        Ok(Vc::cell(children))
225    }
226}