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