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