turbopack_dev_server/source/
asset_graph.rs1use std::{collections::VecDeque, iter::once};
2
3use anyhow::Result;
4use rustc_hash::FxHashSet;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{
7 Completion, FxIndexMap, FxIndexSet, ResolvedVc, State, TryJoinIterExt, Vc, fxindexset,
8};
9use turbo_tasks_fs::FileSystemPath;
10use turbopack_core::{
11 asset::Asset,
12 introspect::{Introspectable, IntrospectableChildren, output_asset::IntrospectableOutputAsset},
13 output::{OutputAsset, OutputAssetsReference, OutputAssetsSet},
14};
15
16use crate::source::{
17 ContentSource, ContentSourceContent, ContentSourceData, ContentSourceSideEffect,
18 GetContentSourceContent,
19 route_tree::{BaseSegment, RouteTree, RouteTrees, RouteType},
20};
21
22#[turbo_tasks::value(transparent)]
23struct OutputAssetsMap(
24 #[bincode(with = "turbo_bincode::indexmap")]
25 FxIndexMap<RcStr, ResolvedVc<Box<dyn OutputAsset>>>,
26);
27
28type ExpandedState = State<FxHashSet<RcStr>>;
29
30#[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new")]
31pub struct AssetGraphContentSource {
32 root_path: FileSystemPath,
33 root_assets: ResolvedVc<OutputAssetsSet>,
34 expanded: Option<ExpandedState>,
35}
36
37#[turbo_tasks::value_impl]
38impl AssetGraphContentSource {
39 #[turbo_tasks::function]
41 pub fn new_eager(
42 root_path: FileSystemPath,
43 root_asset: ResolvedVc<Box<dyn OutputAsset>>,
44 ) -> Vc<Self> {
45 Self::cell(AssetGraphContentSource {
46 root_path,
47 root_assets: ResolvedVc::cell(fxindexset! { root_asset }),
48 expanded: None,
49 })
50 }
51
52 #[turbo_tasks::function]
55 pub fn new_lazy(
56 root_path: FileSystemPath,
57 root_asset: ResolvedVc<Box<dyn OutputAsset>>,
58 ) -> Vc<Self> {
59 Self::cell(AssetGraphContentSource {
60 root_path,
61 root_assets: ResolvedVc::cell(fxindexset! { root_asset }),
62 expanded: Some(State::new(FxHashSet::default())),
63 })
64 }
65
66 #[turbo_tasks::function]
67 async fn all_assets_map(&self) -> Result<Vc<OutputAssetsMap>> {
68 Ok(Vc::cell(
69 expand(
70 &*self.root_assets.await?,
71 &self.root_path,
72 self.expanded.as_ref(),
73 )
74 .await?,
75 ))
76 }
77}
78
79async fn expand(
80 root_assets: &FxIndexSet<ResolvedVc<Box<dyn OutputAsset>>>,
81 root_path: &FileSystemPath,
82 expanded: Option<&ExpandedState>,
83) -> Result<FxIndexMap<RcStr, ResolvedVc<Box<dyn OutputAsset>>>> {
84 let mut map = FxIndexMap::default();
85 let mut assets = Vec::new();
86 let mut queue: VecDeque<ResolvedVc<Box<dyn OutputAssetsReference>>> =
87 VecDeque::with_capacity(32);
88 let mut assets_set = FxHashSet::default();
89 let root_assets_with_path = root_assets
90 .iter()
91 .map(|&asset| async move {
92 let path = asset.path().await?;
93 Ok((path, asset))
94 })
95 .try_join()
96 .await?;
97
98 if let Some(expanded) = &expanded {
99 let expanded = expanded.get();
100 for (path, root_asset) in root_assets_with_path.into_iter() {
101 if let Some(sub_path) = root_path.get_path_to(&path) {
102 let (sub_paths_buffer, sub_paths) = get_sub_paths(sub_path);
103 let expanded = sub_paths_buffer
104 .iter()
105 .take(sub_paths)
106 .any(|sub_path| expanded.contains(sub_path));
107 for sub_path in sub_paths_buffer.into_iter().take(sub_paths) {
108 assets.push((sub_path, root_asset));
109 }
110 assets_set.insert(root_asset);
111 if expanded {
112 queue.push_back(ResolvedVc::upcast(root_asset));
113 }
114 }
115 }
116 } else {
117 for (path, root_asset) in root_assets_with_path.into_iter() {
118 if let Some(sub_path) = root_path.get_path_to(&path) {
119 let (sub_paths_buffer, sub_paths) = get_sub_paths(sub_path);
120 for sub_path in sub_paths_buffer.into_iter().take(sub_paths) {
121 assets.push((sub_path, root_asset));
122 }
123 queue.push_back(ResolvedVc::upcast(root_asset));
124 assets_set.insert(root_asset);
125 }
126 }
127 }
128
129 while let Some(asset) = queue.pop_front() {
130 let refs = asset.references().await?;
131 for &reference in refs.references.await?.iter() {
132 queue.push_back(reference);
133 }
134 let ref_assets = refs
135 .assets
136 .await?
137 .into_iter()
138 .chain(refs.referenced_assets.await?.into_iter());
139 for &asset in ref_assets {
140 if assets_set.insert(asset) {
141 let path = asset.path().await?;
142 if let Some(sub_path) = root_path.get_path_to(&path) {
143 let (sub_paths_buffer, sub_paths) = get_sub_paths(sub_path);
144 let expanded = if let Some(expanded) = &expanded {
145 let expanded = expanded.get();
146 sub_paths_buffer
147 .iter()
148 .take(sub_paths)
149 .any(|sub_path| expanded.contains(sub_path))
150 } else {
151 true
152 };
153 if expanded {
154 queue.push_back(ResolvedVc::upcast(asset));
155 }
156 for sub_path in sub_paths_buffer.into_iter().take(sub_paths) {
157 assets.push((sub_path, asset));
158 }
159 }
160 }
161 }
162 }
163 for (sub_path, asset) in assets {
164 if &*sub_path == "index.html" {
165 map.insert(rcstr!(""), asset);
166 } else if let Some(p) = sub_path.strip_suffix("/index.html") {
167 map.insert(p.into(), asset);
168 map.insert(format!("{p}/").into(), asset);
169 } else if let Some(p) = sub_path.strip_suffix(".html") {
170 map.insert(p.into(), asset);
171 }
172 map.insert(sub_path, asset);
173 }
174 Ok(map)
175}
176
177fn get_sub_paths(sub_path: &str) -> ([RcStr; 3], usize) {
178 let sub_paths_buffer: [RcStr; 3];
179 let n = if sub_path == "index.html" {
180 sub_paths_buffer = [rcstr!(""), sub_path.into(), Default::default()];
181 2
182 } else if let Some(p) = sub_path.strip_suffix("/index.html") {
183 sub_paths_buffer = [p.into(), format!("{p}/").into(), sub_path.into()];
184 3
185 } else if let Some(p) = sub_path.strip_suffix(".html") {
186 sub_paths_buffer = [p.into(), sub_path.into(), Default::default()];
187 2
188 } else {
189 sub_paths_buffer = [sub_path.into(), Default::default(), Default::default()];
190 1
191 };
192 (sub_paths_buffer, n)
193}
194
195#[turbo_tasks::function(operation)]
196fn all_assets_map_operation(source: ResolvedVc<AssetGraphContentSource>) -> Vc<OutputAssetsMap> {
197 source.all_assets_map()
198}
199
200#[turbo_tasks::value_impl]
201impl ContentSource for AssetGraphContentSource {
202 #[turbo_tasks::function]
203 async fn get_routes(self: ResolvedVc<Self>) -> Result<Vc<RouteTree>> {
204 let assets = all_assets_map_operation(self)
205 .read_strongly_consistent()
206 .await?;
207 let mut paths = Vec::new();
208 let routes = assets
209 .iter()
210 .map(|(path, asset)| {
211 paths.push(path.as_str());
212 RouteTree::new_route(
213 BaseSegment::from_static_pathname(path).collect(),
214 RouteType::Exact,
215 Vc::upcast(AssetGraphGetContentSourceContent::new(
216 *self,
217 path.clone(),
218 **asset,
219 )),
220 )
221 })
222 .map(|v| async move { v.to_resolved().await })
223 .try_join()
224 .await?;
225 Ok(Vc::<RouteTrees>::cell(routes).merge())
226 }
227}
228
229#[turbo_tasks::value]
230struct AssetGraphGetContentSourceContent {
231 source: ResolvedVc<AssetGraphContentSource>,
232 path: RcStr,
233 asset: ResolvedVc<Box<dyn OutputAsset>>,
234}
235
236#[turbo_tasks::value_impl]
237impl AssetGraphGetContentSourceContent {
238 #[turbo_tasks::function]
239 pub fn new(
240 source: ResolvedVc<AssetGraphContentSource>,
241 path: RcStr,
242 asset: ResolvedVc<Box<dyn OutputAsset>>,
243 ) -> Vc<Self> {
244 Self::cell(AssetGraphGetContentSourceContent {
245 source,
246 path,
247 asset,
248 })
249 }
250}
251
252#[turbo_tasks::value_impl]
253impl GetContentSourceContent for AssetGraphGetContentSourceContent {
254 #[turbo_tasks::function]
255 async fn get(
256 self: ResolvedVc<Self>,
257 _path: RcStr,
258 _data: ContentSourceData,
259 ) -> Result<Vc<ContentSourceContent>> {
260 let this = self.await?;
261 turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ContentSourceSideEffect>>(self));
262 Ok(ContentSourceContent::static_content(
263 this.asset.versioned_content(),
264 ))
265 }
266}
267
268#[turbo_tasks::value_impl]
269impl ContentSourceSideEffect for AssetGraphGetContentSourceContent {
270 #[turbo_tasks::function]
271 async fn apply(&self) -> Result<Vc<Completion>> {
272 let source = self.source.await?;
273
274 if let Some(expanded) = &source.expanded {
275 expanded.update_conditionally(|expanded| expanded.insert(self.path.clone()));
276 }
277 Ok(Completion::new())
278 }
279}
280
281#[turbo_tasks::value_impl]
282impl Introspectable for AssetGraphContentSource {
283 #[turbo_tasks::function]
284 fn ty(&self) -> Vc<RcStr> {
285 Vc::cell(rcstr!("asset graph content source"))
286 }
287
288 #[turbo_tasks::function]
289 fn title(&self) -> Vc<RcStr> {
290 self.root_path.value_to_string()
291 }
292
293 #[turbo_tasks::function]
294 fn details(&self) -> Vc<RcStr> {
295 Vc::cell(if let Some(expanded) = &self.expanded {
296 format!("{} assets expanded", expanded.get().len()).into()
297 } else {
298 rcstr!("eager")
299 })
300 }
301
302 #[turbo_tasks::function]
303 async fn children(self: Vc<Self>) -> Result<Vc<IntrospectableChildren>> {
304 let this = self.await?;
305
306 let root_assets = this.root_assets.await?;
307 let root_asset_children = root_assets
308 .iter()
309 .map(|&asset| async move {
310 Ok((
311 rcstr!("root"),
312 IntrospectableOutputAsset::new(*asset).to_resolved().await?,
313 ))
314 })
315 .try_join()
316 .await?;
317
318 let expanded_assets = self.all_assets_map().await?;
319 let expanded_asset_children = expanded_assets
320 .values()
321 .filter(|&a| !root_assets.contains(a))
322 .map(|&asset| async move {
323 Ok((
324 rcstr!("inner"),
325 IntrospectableOutputAsset::new(*asset).to_resolved().await?,
326 ))
327 })
328 .try_join()
329 .await?;
330
331 Ok(Vc::cell(
332 root_asset_children
333 .into_iter()
334 .chain(expanded_asset_children)
335 .chain(once((
336 rcstr!("expanded"),
337 ResolvedVc::upcast(FullyExpanded(self.to_resolved().await?).resolved_cell()),
338 )))
339 .collect(),
340 ))
341 }
342}
343
344#[turbo_tasks::value]
345struct FullyExpanded(ResolvedVc<AssetGraphContentSource>);
346
347#[turbo_tasks::value_impl]
348impl Introspectable for FullyExpanded {
349 #[turbo_tasks::function]
350 fn ty(&self) -> Vc<RcStr> {
351 Vc::cell(rcstr!("fully expanded asset graph content source"))
352 }
353
354 #[turbo_tasks::function]
355 async fn title(&self) -> Result<Vc<RcStr>> {
356 Ok(self.0.await?.root_path.value_to_string())
357 }
358
359 #[turbo_tasks::function]
360 async fn children(&self) -> Result<Vc<IntrospectableChildren>> {
361 let source = self.0.await?;
362
363 let expanded_assets = expand(&*source.root_assets.await?, &source.root_path, None).await?;
364 let children = expanded_assets
365 .iter()
366 .map(|(_k, &v)| async move {
367 Ok((
368 rcstr!("asset"),
369 IntrospectableOutputAsset::new(*v).to_resolved().await?,
370 ))
371 })
372 .try_join()
373 .await?
374 .into_iter()
375 .collect();
376
377 Ok(Vc::cell(children))
378 }
379}