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