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#[turbo_tasks::value]
17#[derive(Debug, Clone)]
18pub struct AssetPath {
19 pub path: RcStr,
21 pub content_hash: RcStr,
22}
23
24#[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#[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#[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}