next_core/
emit.rs

1use anyhow::Result;
2use tracing::{Instrument, Level, Span};
3use turbo_rcstr::RcStr;
4use turbo_tasks::{
5    FxIndexSet, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc,
6    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
7};
8use turbo_tasks_fs::{FileSystemPath, rebase};
9use turbopack_core::{
10    asset::Asset,
11    output::{OutputAsset, OutputAssets},
12};
13
14/// Emits all assets transitively reachable from the given chunks, that are
15/// inside the node root or the client root.
16///
17/// Assets inside the given client root are rebased to the given client output
18/// path.
19#[turbo_tasks::function]
20pub async fn emit_all_assets(
21    assets: Vc<OutputAssets>,
22    node_root: FileSystemPath,
23    client_relative_path: FileSystemPath,
24    client_output_path: FileSystemPath,
25) -> Result<()> {
26    emit_assets(
27        all_assets_from_entries(assets),
28        node_root,
29        client_relative_path,
30        client_output_path,
31    )
32    .as_side_effect()
33    .await?;
34    Ok(())
35}
36
37/// Emits all assets transitively reachable from the given chunks, that are
38/// inside the node root or the client root.
39///
40/// Assets inside the given client root are rebased to the given client output
41/// path.
42#[turbo_tasks::function]
43pub async fn emit_assets(
44    assets: Vc<OutputAssets>,
45    node_root: FileSystemPath,
46    client_relative_path: FileSystemPath,
47    client_output_path: FileSystemPath,
48) -> Result<()> {
49    let _: Vec<()> = assets
50        .await?
51        .iter()
52        .copied()
53        .map(|asset| {
54            let node_root = node_root.clone();
55            let client_relative_path = client_relative_path.clone();
56            let client_output_path = client_output_path.clone();
57
58            async move {
59                let path = asset.path().owned().await?;
60                let span = tracing::info_span!("emit asset", name = %path.value_to_string().await?);
61                async move {
62                    Ok(if path.is_inside_ref(&node_root) {
63                        Some(emit(*asset).as_side_effect().await?)
64                    } else if path.is_inside_ref(&client_relative_path) {
65                        // Client assets are emitted to the client output path, which is prefixed
66                        // with _next. We need to rebase them to remove that
67                        // prefix.
68                        Some(
69                            emit_rebase(*asset, client_relative_path, client_output_path)
70                                .as_side_effect()
71                                .await?,
72                        )
73                    } else {
74                        None
75                    })
76                }
77                .instrument(span)
78                .await
79            }
80        })
81        .try_flat_join()
82        .await?;
83    Ok(())
84}
85
86#[turbo_tasks::function]
87async fn emit(asset: Vc<Box<dyn OutputAsset>>) -> Result<()> {
88    asset
89        .content()
90        .resolve()
91        .await?
92        .write(asset.path().owned().await?)
93        .as_side_effect()
94        .await?;
95    Ok(())
96}
97
98#[turbo_tasks::function]
99async fn emit_rebase(
100    asset: Vc<Box<dyn OutputAsset>>,
101    from: FileSystemPath,
102    to: FileSystemPath,
103) -> Result<()> {
104    let path = rebase(asset.path().owned().await?, from, to)
105        .owned()
106        .await?;
107    let content = asset.content();
108    content
109        .resolve()
110        .await?
111        .write(path)
112        .as_side_effect()
113        .await?;
114    Ok(())
115}
116
117struct OutputAssetVisit {
118    emit_spans: bool,
119}
120impl Visit<(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>)> for OutputAssetVisit {
121    type Edge = (ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>);
122    type EdgesIntoIter = Vec<Self::Edge>;
123    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
124
125    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<Self::Edge> {
126        VisitControlFlow::Continue(edge)
127    }
128
129    fn edges(
130        &mut self,
131        node: &(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>),
132    ) -> Self::EdgesFuture {
133        get_referenced_assets(self.emit_spans, node.0)
134    }
135
136    fn span(
137        &mut self,
138        node: &(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>),
139    ) -> tracing::Span {
140        if let Some(ident) = &node.1 {
141            tracing::info_span!("asset", name = display(ident))
142        } else {
143            Span::current()
144        }
145    }
146}
147
148/// Walks the asset graph from multiple assets and collect all referenced
149/// assets.
150#[turbo_tasks::function]
151pub async fn all_assets_from_entries(entries: Vc<OutputAssets>) -> Result<Vc<OutputAssets>> {
152    let emit_spans = tracing::enabled!(Level::INFO);
153    Ok(Vc::cell(
154        AdjacencyMap::new()
155            .skip_duplicates()
156            .visit(
157                entries
158                    .await?
159                    .iter()
160                    .map(async |asset| {
161                        Ok((
162                            ResolvedVc::upcast(*asset),
163                            if emit_spans {
164                                Some(asset.path().to_string().await?)
165                            } else {
166                                None
167                            },
168                        ))
169                    })
170                    .try_join()
171                    .await?,
172                OutputAssetVisit { emit_spans },
173            )
174            .await
175            .completed()?
176            .into_inner()
177            .into_postorder_topological()
178            .map(|(asset, _)| asset)
179            .collect::<FxIndexSet<_>>()
180            .into_iter()
181            .collect(),
182    ))
183}
184
185/// Computes the list of all chunk children of a given chunk.
186async fn get_referenced_assets(
187    emit_spans: bool,
188    asset: ResolvedVc<Box<dyn OutputAsset>>,
189) -> Result<Vec<(ResolvedVc<Box<dyn OutputAsset>>, Option<ReadRef<RcStr>>)>> {
190    asset
191        .references()
192        .await?
193        .iter()
194        .map(async |asset| {
195            Ok((
196                *asset,
197                if emit_spans {
198                    Some(asset.path().to_string().await?)
199                } else {
200                    None
201                },
202            ))
203        })
204        .try_join()
205        .await
206}