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