turbopack_dev_server/source/
asset_graph.rs

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