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