turbopack_dev_server/source/
conditional.rs

1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{Completion, ResolvedVc, State, Vc};
4use turbopack_core::introspect::{Introspectable, IntrospectableChildren};
5
6use super::{
7    ContentSource, ContentSourceData, ContentSourceDataVary, ContentSourceSideEffect,
8    GetContentSourceContent,
9    route_tree::{MapGetContentSourceContent, RouteTree, RouteTrees},
10};
11use crate::source::{ContentSourceContent, ContentSources};
12
13/// Combines two [ContentSource]s like the [CombinedContentSource], but only
14/// allows to serve from the second source when the first source has
15/// successfully served something once.
16///
17/// This is a laziness optimization when the content of the second source can
18/// only be reached via references from the first source.
19///
20/// For example, we use that in the content source that handles SSR rendering of
21/// pages. Here HTML and "other assets" are in different content sources. So we
22/// use this source to only serve (and process) "other assets" when the HTML was
23/// served once.
24#[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new")]
25pub struct ConditionalContentSource {
26    activator: ResolvedVc<Box<dyn ContentSource>>,
27    action: ResolvedVc<Box<dyn ContentSource>>,
28    activated: State<bool>,
29}
30
31#[turbo_tasks::value_impl]
32impl ConditionalContentSource {
33    #[turbo_tasks::function]
34    pub fn new(
35        activator: ResolvedVc<Box<dyn ContentSource>>,
36        action: ResolvedVc<Box<dyn ContentSource>>,
37    ) -> Vc<Self> {
38        ConditionalContentSource {
39            activator,
40            action,
41            activated: State::new(false),
42        }
43        .cell()
44    }
45}
46
47#[turbo_tasks::value_impl]
48impl ContentSource for ConditionalContentSource {
49    #[turbo_tasks::function]
50    async fn get_routes(self: ResolvedVc<Self>) -> Result<Vc<RouteTree>> {
51        let this = self.await?;
52        Ok(if !*this.activated.get() {
53            this.activator.get_routes().map_routes(Vc::upcast(
54                ConditionalContentSourceMapper { source: self }.cell(),
55            ))
56        } else {
57            Vc::<RouteTrees>::cell(vec![
58                this.activator.get_routes().to_resolved().await?,
59                this.action.get_routes().to_resolved().await?,
60            ])
61            .merge()
62        })
63    }
64
65    #[turbo_tasks::function]
66    fn get_children(&self) -> Vc<ContentSources> {
67        Vc::cell(vec![self.activator, self.action])
68    }
69}
70
71#[turbo_tasks::value]
72struct ConditionalContentSourceMapper {
73    source: ResolvedVc<ConditionalContentSource>,
74}
75
76#[turbo_tasks::value_impl]
77impl MapGetContentSourceContent for ConditionalContentSourceMapper {
78    #[turbo_tasks::function]
79    fn map_get_content(
80        &self,
81        get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
82    ) -> Vc<Box<dyn GetContentSourceContent>> {
83        Vc::upcast(
84            ActivateOnGetContentSource {
85                source: self.source,
86                get_content,
87            }
88            .cell(),
89        )
90    }
91}
92
93#[turbo_tasks::value_impl]
94impl Introspectable for ConditionalContentSource {
95    #[turbo_tasks::function]
96    fn ty(&self) -> Vc<RcStr> {
97        Vc::cell(rcstr!("conditional content source"))
98    }
99
100    #[turbo_tasks::function]
101    fn details(&self) -> Vc<RcStr> {
102        Vc::cell(if *self.activated.get() {
103            rcstr!("activated")
104        } else {
105            rcstr!("not activated")
106        })
107    }
108
109    #[turbo_tasks::function]
110    fn title(&self) -> Result<Vc<RcStr>> {
111        if let Some(activator) = ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(self.activator)
112        {
113            Ok(activator.title())
114        } else {
115            Ok(Vc::<RcStr>::default())
116        }
117    }
118
119    #[turbo_tasks::function]
120    fn children(&self) -> Result<Vc<IntrospectableChildren>> {
121        Ok(Vc::cell(
122            [
123                ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(self.activator)
124                    .map(|i| (rcstr!("activator"), i)),
125                ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(self.action)
126                    .map(|i| (rcstr!("action"), i)),
127            ]
128            .into_iter()
129            .flatten()
130            .collect(),
131        ))
132    }
133}
134
135#[turbo_tasks::value(serialization = "none", eq = "manual", cell = "new")]
136struct ActivateOnGetContentSource {
137    source: ResolvedVc<ConditionalContentSource>,
138    get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
139}
140
141#[turbo_tasks::value_impl]
142impl GetContentSourceContent for ActivateOnGetContentSource {
143    #[turbo_tasks::function]
144    fn vary(&self) -> Vc<ContentSourceDataVary> {
145        self.get_content.vary()
146    }
147
148    #[turbo_tasks::function]
149    async fn get(
150        self: ResolvedVc<Self>,
151        path: RcStr,
152        data: ContentSourceData,
153    ) -> Result<Vc<ContentSourceContent>> {
154        turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn ContentSourceSideEffect>>(self));
155        Ok(self.await?.get_content.get(path, data))
156    }
157}
158
159#[turbo_tasks::value_impl]
160impl ContentSourceSideEffect for ActivateOnGetContentSource {
161    #[turbo_tasks::function]
162    async fn apply(&self) -> Result<Vc<Completion>> {
163        self.source.await?.activated.set(true);
164        Ok(Completion::new())
165    }
166}