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