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            && *self.own_content().has_source_map().await?;
129        let ref_assets = chunk_references.assets.await?;
130        let mut assets =
131            Vec::with_capacity(ref_assets.len() + if include_source_map { 1 } else { 0 });
132
133        assets.extend(ref_assets.iter().copied());
134
135        if include_source_map {
136            assets.push(ResolvedVc::upcast(self.source_map().to_resolved().await?));
137        }
138
139        // Constituent component chunks of a merged chunk are emitted as referenced assets
140        // so the runtime can fetch an individual component when it's already cached, without
141        // them being eagerly loaded as primary chunks.
142        let component_assets = this.component_chunk_assets().await?;
143        let referenced_assets = if component_assets.is_empty() {
144            chunk_references.referenced_assets
145        } else {
146            let mut referenced: Vec<_> = chunk_references
147                .referenced_assets
148                .await?
149                .iter()
150                .copied()
151                .collect();
152            referenced.extend(component_assets);
153            ResolvedVc::cell(referenced)
154        };
155
156        Ok(OutputAssetsWithReferenced {
157            assets: ResolvedVc::cell(assets),
158            referenced_assets,
159            references: chunk_references.references,
160        }
161        .cell())
162    }
163}
164
165#[turbo_tasks::value_impl]
166impl OutputAsset for EcmascriptBrowserChunk {
167    #[turbo_tasks::function]
168    async fn path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
169        let this = self.await?;
170        let ident = this.ident_for_path().await?;
171        Ok(this
172            .chunking_context
173            .chunk_path(Some(Vc::upcast(self)), ident, None, rcstr!(".js")))
174    }
175}
176
177#[turbo_tasks::value_impl]
178impl Asset for EcmascriptBrowserChunk {
179    #[turbo_tasks::function]
180    fn content(self: Vc<Self>) -> Vc<AssetContent> {
181        self.own_content().content()
182    }
183
184    #[turbo_tasks::function]
185    fn versioned_content(self: Vc<Self>) -> Vc<Box<dyn VersionedContent>> {
186        Vc::upcast(self.own_content())
187    }
188}
189
190#[turbo_tasks::value_impl]
191impl GenerateSourceMap for EcmascriptBrowserChunk {
192    #[turbo_tasks::function]
193    fn generate_source_map(self: Vc<Self>) -> Vc<FileContent> {
194        self.own_content().generate_source_map()
195    }
196
197    #[turbo_tasks::function]
198    fn by_section(self: Vc<Self>, section: RcStr) -> Vc<FileContent> {
199        self.own_content().by_section(section)
200    }
201}
202
203#[turbo_tasks::value_impl]
204impl Introspectable for EcmascriptBrowserChunk {
205    #[turbo_tasks::function]
206    fn ty(&self) -> Vc<RcStr> {
207        Vc::cell(rcstr!("dev ecmascript chunk"))
208    }
209
210    #[turbo_tasks::function]
211    fn title(self: Vc<Self>) -> Vc<RcStr> {
212        self.path().to_string()
213    }
214
215    #[turbo_tasks::function]
216    fn details(&self) -> Vc<RcStr> {
217        Vc::cell(rcstr!("generates a development ecmascript chunk"))
218    }
219
220    #[turbo_tasks::function]
221    fn children(&self) -> Result<Vc<IntrospectableChildren>> {
222        let mut children = FxIndexSet::default();
223        let chunk = ResolvedVc::upcast::<Box<dyn Introspectable>>(self.chunk);
224        children.insert((rcstr!("chunk"), chunk));
225        Ok(Vc::cell(children))
226    }
227}