Skip to main content

next_api/
paths.rs

1use anyhow::{Context, Result};
2use next_core::next_manifests::AssetBinding;
3use tracing::Instrument;
4use turbo_rcstr::RcStr;
5use turbo_tasks::{ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc};
6use turbo_tasks_fs::FileSystemPath;
7use turbo_tasks_hash::HashAlgorithm;
8use turbopack_core::{
9    asset::{Asset, no_hash_salt},
10    output::{OutputAsset, OutputAssets},
11    reference::all_assets_from_entries,
12};
13use turbopack_wasm::wasm_edge_var_name;
14
15/// A reference to an output asset with content hash for change detection
16#[turbo_tasks::value]
17#[derive(Debug, Clone)]
18pub struct AssetPath {
19    /// Relative to the root_path
20    pub path: RcStr,
21    pub content_hash: RcStr,
22}
23
24/// A list of asset paths
25#[turbo_tasks::value(transparent)]
26pub struct AssetPaths(Vec<AssetPath>);
27
28#[turbo_tasks::value(transparent)]
29pub struct OptionAssetPath(Option<AssetPath>);
30
31#[turbo_tasks::function]
32async fn asset_path(
33    asset: Vc<Box<dyn OutputAsset>>,
34    node_root: FileSystemPath,
35    should_content_hash: Option<HashAlgorithm>,
36) -> Result<Vc<OptionAssetPath>> {
37    Ok(Vc::cell(
38        if let Some(path) = node_root.get_path_to(&*asset.path().await?) {
39            let hash = if let Some(algorithm) = should_content_hash {
40                asset
41                    .content()
42                    .content_hash(no_hash_salt(), algorithm)
43                    .owned()
44                    .await?
45                    .context("asset content not found")?
46            } else {
47                asset
48                    .content()
49                    .hash(HashAlgorithm::Xxh3Hash128Hex)
50                    .owned()
51                    .await?
52            };
53            Some(AssetPath {
54                path: RcStr::from(path),
55                content_hash: hash,
56            })
57        } else {
58            None
59        },
60    ))
61}
62
63/// Return a list of all asset paths with filename and hash for all output
64/// assets references from the `assets` list. Only paths inside `node_root` are included.
65#[turbo_tasks::function]
66pub async fn all_asset_paths(
67    assets: Vc<OutputAssets>,
68    node_root: FileSystemPath,
69    should_content_hash: Option<HashAlgorithm>,
70) -> Result<Vc<AssetPaths>> {
71    let span = tracing::info_span!(
72        "collect all asset paths",
73        assets_count = tracing::field::Empty,
74        asset_paths_count = tracing::field::Empty
75    );
76    let span_clone = span.clone();
77    async move {
78        let all_assets = all_assets_from_entries(assets).await?;
79        span.record("assets_count", all_assets.len());
80        let asset_paths = all_assets
81            .iter()
82            .map(|&asset| asset_path(*asset, node_root.clone(), should_content_hash).owned())
83            .try_flat_join()
84            .await?;
85        span.record("asset_paths_count", asset_paths.len());
86        Ok(Vc::cell(asset_paths))
87    }
88    .instrument(span_clone)
89    .await
90}
91
92/// Return a list of relative paths to `root` for all output assets references
93/// from the `assets` list which are located inside the root path.
94#[turbo_tasks::function]
95pub async fn all_paths_in_root(
96    assets: Vc<OutputAssets>,
97    root: FileSystemPath,
98) -> Result<Vc<Vec<RcStr>>> {
99    let all_assets = all_assets_from_entries(assets).await?;
100
101    Ok(Vc::cell(
102        get_paths_from_root(&root, all_assets, |_| true).await?,
103    ))
104}
105
106pub(crate) async fn get_paths_from_root(
107    root: &FileSystemPath,
108    output_assets: impl IntoIterator<Item = ResolvedVc<Box<dyn OutputAsset>>>,
109    filter: impl FnOnce(&str) -> bool + Copy,
110) -> Result<Vec<RcStr>> {
111    output_assets
112        .into_iter()
113        .map(move |file| async move {
114            let path = &*file.path().await?;
115            let Some(relative) = root.get_path_to(path) else {
116                return Ok(None);
117            };
118
119            Ok(if filter(relative) {
120                Some(relative.into())
121            } else {
122                None
123            })
124        })
125        .try_flat_join()
126        .await
127}
128
129pub(crate) async fn get_js_paths_from_root(
130    root: &FileSystemPath,
131    output_assets: impl IntoIterator<Item = ResolvedVc<Box<dyn OutputAsset>>>,
132) -> Result<Vec<RcStr>> {
133    get_paths_from_root(root, output_assets, |path| path.ends_with(".js")).await
134}
135
136pub(crate) async fn get_wasm_paths_from_root(
137    root: &FileSystemPath,
138    output_assets: impl IntoIterator<Item = ResolvedVc<Box<dyn OutputAsset>>>,
139) -> Result<Vec<(RcStr, ResolvedVc<Box<dyn OutputAsset>>)>> {
140    output_assets
141        .into_iter()
142        .map(move |file| async move {
143            let path = &*file.path().await?;
144            let Some(relative) = root.get_path_to(path) else {
145                return Ok(None);
146            };
147
148            Ok(if relative.ends_with(".wasm") {
149                Some((relative.into(), file))
150            } else {
151                None
152            })
153        })
154        .try_flat_join()
155        .await
156}
157
158pub(crate) async fn get_asset_paths_from_root(
159    root: &FileSystemPath,
160    output_assets: impl IntoIterator<Item = ResolvedVc<Box<dyn OutputAsset>>>,
161) -> Result<Vec<RcStr>> {
162    get_paths_from_root(root, output_assets, |path| {
163        !path.ends_with(".js") && !path.ends_with(".map") && !path.ends_with(".wasm")
164    })
165    .await
166}
167
168pub(crate) async fn get_font_paths_from_root(
169    root: &FileSystemPath,
170    output_assets: impl IntoIterator<Item = ResolvedVc<Box<dyn OutputAsset>>>,
171) -> Result<Vec<RcStr>> {
172    get_paths_from_root(root, output_assets, |path| {
173        path.ends_with(".woff")
174            || path.ends_with(".woff2")
175            || path.ends_with(".eot")
176            || path.ends_with(".ttf")
177            || path.ends_with(".otf")
178    })
179    .await
180}
181
182pub(crate) async fn wasm_paths_to_bindings(
183    paths: impl IntoIterator<Item = (RcStr, ResolvedVc<Box<dyn OutputAsset>>)>,
184) -> Result<Vec<AssetBinding>> {
185    paths
186        .into_iter()
187        .map(async |(path, asset)| {
188            Ok(AssetBinding {
189                name: wasm_edge_var_name(Vc::upcast(*asset)).owned().await?,
190                file_path: path,
191            })
192        })
193        .try_join()
194        .await
195}
196
197pub(crate) fn paths_to_bindings(paths: Vec<RcStr>) -> Vec<AssetBinding> {
198    paths
199        .into_iter()
200        .map(|path| AssetBinding {
201            name: path.clone(),
202            file_path: path,
203        })
204        .collect()
205}