Skip to main content

turbopack_core/
output.rs

1use anyhow::Result;
2use either::Either;
3use turbo_rcstr::RcStr;
4use turbo_tasks::{
5    FxIndexSet, ResolvedVc, ValueToString, Vc,
6    graph::{AdjacencyMap, GraphTraversal},
7};
8use turbo_tasks_fs::FileSystemPath;
9
10use crate::asset::Asset;
11
12#[turbo_tasks::value(transparent)]
13pub struct OptionOutputAsset(Option<ResolvedVc<Box<dyn OutputAsset>>>);
14
15#[turbo_tasks::value_trait]
16pub trait OutputAssetsReference {
17    /// References to other [`OutputAsset`]s from this [`OutputAssetsReference`].
18    #[turbo_tasks::function]
19    fn references(self: Vc<Self>) -> Vc<OutputAssetsWithReferenced> {
20        OutputAssetsWithReferenced {
21            assets: OutputAssets::empty_resolved(),
22            referenced_assets: OutputAssets::empty_resolved(),
23            references: OutputAssetsReferences::empty_resolved(),
24        }
25        .cell()
26    }
27}
28
29/// An asset that should be outputted, e. g. written to disk or served from a server.
30///
31/// For documentation about where this is used and how it fits into the rest of Turbopack, see
32/// [`crate::_layers`].
33#[turbo_tasks::value_trait]
34pub trait OutputAsset: Asset + OutputAssetsReference {
35    /// The identifier of the [OutputAsset]. It's expected to be unique and
36    /// capture all properties of the [OutputAsset].
37    #[turbo_tasks::function]
38    fn path(&self) -> Vc<FileSystemPath>;
39
40    /// The identifier of the [OutputAsset] as string. It's expected to be unique and
41    /// capture all properties of the [OutputAsset].
42    #[turbo_tasks::function]
43    fn path_string(self: Vc<Self>) -> Vc<RcStr> {
44        self.path().to_string()
45    }
46
47    #[turbo_tasks::function]
48    fn size_bytes(self: Vc<Self>) -> Vc<Option<u64>> {
49        Vc::cell(None)
50    }
51}
52
53#[turbo_tasks::value(transparent)]
54pub struct OutputAssetsReferences(Vec<ResolvedVc<Box<dyn OutputAssetsReference>>>);
55
56#[turbo_tasks::value_impl]
57impl OutputAssetsReferences {
58    #[turbo_tasks::function]
59    pub async fn concatenate(&self, other: Vc<Self>) -> Result<Vc<Self>> {
60        let mut references: FxIndexSet<_> = self.0.iter().copied().collect();
61        references.extend(other.await?.iter().copied());
62        Ok(Vc::cell(references.into_iter().collect()))
63    }
64}
65impl OutputAssetsReferences {
66    pub fn empty() -> Vc<Self> {
67        Vc::cell(vec![])
68    }
69
70    pub fn empty_resolved() -> ResolvedVc<Self> {
71        ResolvedVc::cell(vec![])
72    }
73}
74
75#[turbo_tasks::value(transparent)]
76pub struct OutputAssets(Vec<ResolvedVc<Box<dyn OutputAsset>>>);
77
78#[turbo_tasks::value_impl]
79impl OutputAssets {
80    #[turbo_tasks::function]
81    pub async fn concatenate(&self, other: Vc<Self>) -> Result<Vc<Self>> {
82        let mut assets: FxIndexSet<_> = self.0.iter().copied().collect();
83        assets.extend(other.await?.iter().copied());
84        Ok(Vc::cell(assets.into_iter().collect()))
85    }
86
87    #[turbo_tasks::function]
88    pub async fn concat_asset(&self, asset: ResolvedVc<Box<dyn OutputAsset>>) -> Result<Vc<Self>> {
89        let mut assets: FxIndexSet<_> = self.0.iter().copied().collect();
90        assets.extend([asset]);
91        Ok(Vc::cell(assets.into_iter().collect()))
92    }
93
94    #[turbo_tasks::function]
95    pub async fn concat(other: Vec<Vc<Self>>) -> Result<Vc<Self>> {
96        let mut assets: FxIndexSet<_> = FxIndexSet::default();
97        for other in other {
98            assets.extend(other.await?.iter().copied());
99        }
100        Ok(Vc::cell(assets.into_iter().collect()))
101    }
102}
103
104impl OutputAssets {
105    pub fn empty() -> Vc<Self> {
106        Vc::cell(vec![])
107    }
108
109    pub fn empty_resolved() -> ResolvedVc<Self> {
110        ResolvedVc::cell(vec![])
111    }
112}
113
114#[turbo_tasks::value(transparent)]
115pub struct ExpandedOutputAssets(Vec<ResolvedVc<Box<dyn OutputAsset>>>);
116
117/// A set of [OutputAsset]s
118#[turbo_tasks::value(transparent)]
119pub struct OutputAssetsSet(
120    #[bincode(with = "turbo_bincode::indexset")] FxIndexSet<ResolvedVc<Box<dyn OutputAsset>>>,
121);
122
123#[turbo_tasks::value(shared)]
124#[derive(Clone)]
125pub struct OutputAssetsWithReferenced {
126    /// Primary output assets. These are e. g. the chunks needed for a chunk group.
127    pub assets: ResolvedVc<OutputAssets>,
128    /// Secondary output assets that are referenced by the primary assets.
129    pub referenced_assets: ResolvedVc<OutputAssets>,
130    /// Secondary output assets that are referenced by the primary assets. These are unresolved
131    /// `OutputAssetsReference`s and need to be expanded to get the actual assets. These are e. g.
132    /// async loaders that reference other chunk groups.
133    pub references: ResolvedVc<OutputAssetsReferences>,
134}
135
136impl OutputAssetsWithReferenced {
137    async fn expand_assets(
138        &self,
139        inner_output_assets: bool,
140    ) -> Result<Vec<ResolvedVc<Box<dyn OutputAsset>>>> {
141        expand_output_assets(
142            self.assets
143                .await?
144                .into_iter()
145                .chain(self.referenced_assets.await?)
146                .map(|&asset| ExpandOutputAssetsInput::Asset(asset))
147                .chain(
148                    self.references
149                        .await?
150                        .into_iter()
151                        .map(|&reference| ExpandOutputAssetsInput::Reference(reference)),
152                ),
153            inner_output_assets,
154        )
155        .await
156    }
157}
158
159#[turbo_tasks::value_impl]
160impl OutputAssetsWithReferenced {
161    #[turbo_tasks::function]
162    pub fn from_assets(assets: ResolvedVc<OutputAssets>) -> Vc<Self> {
163        OutputAssetsWithReferenced {
164            assets,
165            referenced_assets: OutputAssets::empty_resolved(),
166            references: OutputAssetsReferences::empty_resolved(),
167        }
168        .cell()
169    }
170
171    #[turbo_tasks::function]
172    pub async fn concatenate(&self, other: Vc<Self>) -> Result<Vc<Self>> {
173        let other = other.await?;
174        Ok(Self {
175            assets: self.assets.concatenate(*other.assets).to_resolved().await?,
176            referenced_assets: self
177                .referenced_assets
178                .concatenate(*other.referenced_assets)
179                .to_resolved()
180                .await?,
181            references: self
182                .references
183                .concatenate(*other.references)
184                .to_resolved()
185                .await?,
186        }
187        .cell())
188    }
189
190    #[turbo_tasks::function]
191    pub async fn concatenate_asset(
192        &self,
193        asset: ResolvedVc<Box<dyn OutputAsset>>,
194    ) -> Result<Vc<Self>> {
195        Ok(Self {
196            assets: self.assets.concat_asset(*asset).to_resolved().await?,
197            referenced_assets: self.referenced_assets,
198            references: self.references,
199        }
200        .cell())
201    }
202
203    /// Returns all assets, including referenced assets and nested assets.
204    #[turbo_tasks::function]
205    pub async fn expand_all_assets(&self) -> Result<Vc<ExpandedOutputAssets>> {
206        Ok(Vc::cell(self.expand_assets(true).await?))
207    }
208
209    /// Returns only direct referenced assets and does not include assets referenced indirectly by
210    /// them.
211    #[turbo_tasks::function]
212    pub async fn all_assets(&self) -> Result<Vc<OutputAssets>> {
213        Ok(Vc::cell(self.expand_assets(false).await?))
214    }
215
216    /// Returns only primary asset entries. Doesn't expand OutputAssets. Doesn't return referenced
217    /// assets.
218    #[turbo_tasks::function]
219    pub fn primary_assets(&self) -> Vc<OutputAssets> {
220        *self.assets
221    }
222
223    /// Returns only secondary referenced asset entries. Doesn't expand OutputAssets. Doesn't return
224    /// primary assets.
225    #[turbo_tasks::function]
226    pub async fn referenced_assets(&self) -> Result<Vc<OutputAssets>> {
227        Ok(Vc::cell(
228            expand_output_assets(
229                self.referenced_assets
230                    .await?
231                    .into_iter()
232                    .copied()
233                    .map(ExpandOutputAssetsInput::Asset)
234                    .chain(
235                        self.references
236                            .await?
237                            .into_iter()
238                            .copied()
239                            .map(ExpandOutputAssetsInput::Reference),
240                    ),
241                false,
242            )
243            .await?,
244        ))
245    }
246}
247
248/// Computes the list of all chunk children of a given chunk.
249async fn get_referenced_assets(
250    inner_output_assets: bool,
251    input: ExpandOutputAssetsInput,
252) -> Result<impl Iterator<Item = ExpandOutputAssetsInput>> {
253    let refs = match input {
254        ExpandOutputAssetsInput::Asset(output_asset) => {
255            if !inner_output_assets {
256                return Ok(Either::Left(std::iter::empty()));
257            }
258            output_asset.references().await?
259        }
260        ExpandOutputAssetsInput::Reference(reference) => reference.references().await?,
261    };
262    let assets = refs
263        .assets
264        .await?
265        .into_iter()
266        .chain(refs.referenced_assets.await?)
267        .map(|&asset| ExpandOutputAssetsInput::Asset(asset))
268        .chain(
269            refs.references
270                .await?
271                .into_iter()
272                .map(|&reference| ExpandOutputAssetsInput::Reference(reference)),
273        );
274    Ok(Either::Right(assets))
275}
276
277#[derive(PartialEq, Eq, Hash, Clone, Copy)]
278pub enum ExpandOutputAssetsInput {
279    Asset(ResolvedVc<Box<dyn OutputAsset>>),
280    Reference(ResolvedVc<Box<dyn OutputAssetsReference>>),
281}
282
283pub async fn expand_output_assets(
284    inputs: impl Iterator<Item = ExpandOutputAssetsInput>,
285    inner_output_assets: bool,
286) -> Result<Vec<ResolvedVc<Box<dyn OutputAsset>>>> {
287    let edges = AdjacencyMap::new()
288        .visit(inputs, async |input| {
289            get_referenced_assets(inner_output_assets, input).await
290        })
291        .await
292        .completed()?
293        .into_postorder_topological();
294
295    let mut assets = Vec::new();
296    for input in edges {
297        match input {
298            ExpandOutputAssetsInput::Asset(asset) => {
299                assets.push(asset);
300            }
301            ExpandOutputAssetsInput::Reference(_) => {}
302        }
303    }
304
305    Ok(assets)
306}