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(other: Vec<Vc<Self>>) -> Result<Vc<Self>> {
87        let mut assets: FxIndexSet<_> = FxIndexSet::default();
88        for other in other {
89            assets.extend(other.await?.iter().copied());
90        }
91        Ok(Vc::cell(assets.into_iter().collect()))
92    }
93}
94
95impl OutputAssets {
96    pub fn empty() -> Vc<Self> {
97        Vc::cell(vec![])
98    }
99
100    pub fn empty_resolved() -> ResolvedVc<Self> {
101        ResolvedVc::cell(vec![])
102    }
103}
104
105#[turbo_tasks::value(transparent)]
106pub struct ExpandedOutputAssets(Vec<ResolvedVc<Box<dyn OutputAsset>>>);
107
108/// A set of [OutputAsset]s
109#[turbo_tasks::value(transparent)]
110pub struct OutputAssetsSet(
111    #[bincode(with = "turbo_bincode::indexset")] FxIndexSet<ResolvedVc<Box<dyn OutputAsset>>>,
112);
113
114#[turbo_tasks::value(shared)]
115#[derive(Clone)]
116pub struct OutputAssetsWithReferenced {
117    /// Primary output assets. These are e. g. the chunks needed for a chunk group.
118    pub assets: ResolvedVc<OutputAssets>,
119    /// Secondary output assets that are referenced by the primary assets.
120    pub referenced_assets: ResolvedVc<OutputAssets>,
121    /// Secondary output assets that are referenced by the primary assets. These are unresolved
122    /// `OutputAssetsReference`s and need to be expanded to get the actual assets. These are e. g.
123    /// async loaders that reference other chunk groups.
124    pub references: ResolvedVc<OutputAssetsReferences>,
125}
126
127impl OutputAssetsWithReferenced {
128    async fn expand_assets(
129        &self,
130        inner_output_assets: bool,
131    ) -> Result<Vec<ResolvedVc<Box<dyn OutputAsset>>>> {
132        expand_output_assets(
133            self.assets
134                .await?
135                .into_iter()
136                .chain(self.referenced_assets.await?.into_iter())
137                .map(|&asset| ExpandOutputAssetsInput::Asset(asset))
138                .chain(
139                    self.references
140                        .await?
141                        .into_iter()
142                        .map(|&reference| ExpandOutputAssetsInput::Reference(reference)),
143                ),
144            inner_output_assets,
145        )
146        .await
147    }
148}
149
150#[turbo_tasks::value_impl]
151impl OutputAssetsWithReferenced {
152    #[turbo_tasks::function]
153    pub fn from_assets(assets: ResolvedVc<OutputAssets>) -> Vc<Self> {
154        OutputAssetsWithReferenced {
155            assets,
156            referenced_assets: OutputAssets::empty_resolved(),
157            references: OutputAssetsReferences::empty_resolved(),
158        }
159        .cell()
160    }
161
162    #[turbo_tasks::function]
163    pub async fn concatenate(&self, other: Vc<Self>) -> Result<Vc<Self>> {
164        Ok(Self {
165            assets: self
166                .assets
167                .concatenate(*other.await?.assets)
168                .to_resolved()
169                .await?,
170            referenced_assets: self
171                .referenced_assets
172                .concatenate(*other.await?.referenced_assets)
173                .to_resolved()
174                .await?,
175            references: self
176                .references
177                .concatenate(*other.await?.references)
178                .to_resolved()
179                .await?,
180        }
181        .cell())
182    }
183
184    /// Returns all assets, including referenced assets and nested assets.
185    #[turbo_tasks::function]
186    pub async fn expand_all_assets(&self) -> Result<Vc<ExpandedOutputAssets>> {
187        Ok(Vc::cell(self.expand_assets(true).await?))
188    }
189
190    /// Returns only direct referenced assets and does not include assets referenced indirectly by
191    /// them.
192    #[turbo_tasks::function]
193    pub async fn all_assets(&self) -> Result<Vc<OutputAssets>> {
194        Ok(Vc::cell(self.expand_assets(false).await?))
195    }
196
197    /// Returns only primary asset entries. Doesn't expand OutputAssets. Doesn't return referenced
198    /// assets.
199    #[turbo_tasks::function]
200    pub fn primary_assets(&self) -> Vc<OutputAssets> {
201        *self.assets
202    }
203
204    /// Returns only secondary referenced asset entries. Doesn't expand OutputAssets. Doesn't return
205    /// primary assets.
206    #[turbo_tasks::function]
207    pub async fn referenced_assets(&self) -> Result<Vc<OutputAssets>> {
208        Ok(Vc::cell(
209            expand_output_assets(
210                self.referenced_assets
211                    .await?
212                    .into_iter()
213                    .copied()
214                    .map(ExpandOutputAssetsInput::Asset)
215                    .chain(
216                        self.references
217                            .await?
218                            .into_iter()
219                            .copied()
220                            .map(ExpandOutputAssetsInput::Reference),
221                    ),
222                false,
223            )
224            .await?,
225        ))
226    }
227}
228
229/// Computes the list of all chunk children of a given chunk.
230async fn get_referenced_assets(
231    inner_output_assets: bool,
232    input: ExpandOutputAssetsInput,
233) -> Result<impl Iterator<Item = ExpandOutputAssetsInput>> {
234    let refs = match input {
235        ExpandOutputAssetsInput::Asset(output_asset) => {
236            if !inner_output_assets {
237                return Ok(Either::Left(std::iter::empty()));
238            }
239            output_asset.references().await?
240        }
241        ExpandOutputAssetsInput::Reference(reference) => reference.references().await?,
242    };
243    let assets = refs
244        .assets
245        .await?
246        .into_iter()
247        .chain(refs.referenced_assets.await?.into_iter())
248        .map(|&asset| ExpandOutputAssetsInput::Asset(asset))
249        .chain(
250            refs.references
251                .await?
252                .into_iter()
253                .map(|&reference| ExpandOutputAssetsInput::Reference(reference)),
254        );
255    Ok(Either::Right(assets))
256}
257
258#[derive(PartialEq, Eq, Hash, Clone, Copy)]
259pub enum ExpandOutputAssetsInput {
260    Asset(ResolvedVc<Box<dyn OutputAsset>>),
261    Reference(ResolvedVc<Box<dyn OutputAssetsReference>>),
262}
263
264pub async fn expand_output_assets(
265    inputs: impl Iterator<Item = ExpandOutputAssetsInput>,
266    inner_output_assets: bool,
267) -> Result<Vec<ResolvedVc<Box<dyn OutputAsset>>>> {
268    let edges = AdjacencyMap::new()
269        .visit(inputs, async |input| {
270            get_referenced_assets(inner_output_assets, input).await
271        })
272        .await
273        .completed()?
274        .into_postorder_topological();
275
276    let mut assets = Vec::new();
277    for input in edges {
278        match input {
279            ExpandOutputAssetsInput::Asset(asset) => {
280                assets.push(asset);
281            }
282            ExpandOutputAssetsInput::Reference(_) => {}
283        }
284    }
285
286    Ok(assets)
287}