Skip to main content

next_api/
route.rs

1use std::fmt::Display;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use itertools::Itertools;
6use next_core::app_structure::FileSystemPathVec;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{
9    Completion, FxIndexMap, FxIndexSet, JoinIterExt, NonLocalValue, OperationVc, ResolvedVc,
10    TryJoinIterExt, Vc, debug::ValueDebugFormat,
11};
12use turbopack_core::{
13    module::Module,
14    module_graph::{GraphEntries, ModuleGraph},
15    output::OutputAssets,
16};
17
18use crate::{operation::OptionEndpoint, paths::AssetPath, project::Project};
19
20#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
21pub struct AppPageRoute {
22    pub original_name: RcStr,
23    pub html_endpoint: ResolvedVc<Box<dyn Endpoint>>,
24    pub rsc_hmr_endpoint: ResolvedVc<Box<dyn Endpoint>>,
25}
26
27#[turbo_tasks::value(shared)]
28#[derive(Clone, Debug)]
29pub enum Route {
30    Page {
31        html_endpoint: ResolvedVc<Box<dyn Endpoint>>,
32        data_endpoint: Option<ResolvedVc<Box<dyn Endpoint>>>,
33    },
34    PageApi {
35        endpoint: ResolvedVc<Box<dyn Endpoint>>,
36    },
37    AppPage(Vec<AppPageRoute>),
38    AppRoute {
39        original_name: RcStr,
40        endpoint: ResolvedVc<Box<dyn Endpoint>>,
41        has_action_manifest: bool,
42    },
43    Conflict,
44}
45
46#[turbo_tasks::value(transparent)]
47pub struct ModuleGraphs(Vec<ResolvedVc<ModuleGraph>>);
48
49/// Client-side modules associated with an endpoint.
50#[turbo_tasks::value(shared)]
51#[derive(Clone, Debug, Default)]
52pub struct AnalyzeClientEntries {
53    /// Server entry modules established by this endpoint's build graph.
54    pub server_modules: Vec<ResolvedVc<Box<dyn Module>>>,
55    /// Client entry modules used to bootstrap this endpoint, not observed requests.
56    pub bootstrap_modules: Vec<ResolvedVc<Box<dyn Module>>>,
57    /// Client references discovered from the endpoint's server graph.
58    pub references: Vec<AnalyzeClientReference>,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode)]
62pub struct AnalyzeClientReference {
63    /// The referenced client module, with its bundler identity and variants intact.
64    pub module: ResolvedVc<Box<dyn Module>>,
65    /// Producer reference category (for example ECMAScript or CSS), not a load trigger.
66    pub kind: RcStr,
67}
68
69/// Direct emitted assets of a build-time chunk group. Groups may overlap: an
70/// App layout group can include chunks shared with other client references.
71#[derive(Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode)]
72pub struct AnalyzeChunkGroup {
73    pub kind: RcStr,
74    pub trigger: Option<ResolvedVc<Box<dyn Module>>>,
75    pub assets: ResolvedVc<OutputAssets>,
76    /// Only Pages HTML endpoints inherit the shared _app client group.
77    pub pages_html: bool,
78}
79
80#[turbo_tasks::value(transparent)]
81pub struct AnalyzeChunkGroups(Vec<AnalyzeChunkGroup>);
82
83#[turbo_tasks::value_trait]
84pub trait Endpoint {
85    #[turbo_tasks::function]
86    fn output(self: Vc<Self>) -> Vc<EndpointOutput>;
87    // fn write_to_disk(self: Vc<Self>) -> Vc<EndpointOutputPaths>;
88    #[turbo_tasks::function]
89    fn server_changed(self: Vc<Self>) -> Vc<Completion>;
90    #[turbo_tasks::function]
91    fn client_changed(self: Vc<Self>) -> Vc<Completion>;
92    /// The entry modules for the modules graph.
93    #[turbo_tasks::function]
94    fn entries(self: Vc<Self>) -> Vc<GraphEntries>;
95    /// Build-time client bootstrap and client-reference modules for analysis.
96    #[turbo_tasks::function]
97    fn analyze_client_entries(self: Vc<Self>) -> Vc<AnalyzeClientEntries> {
98        AnalyzeClientEntries::default().cell()
99    }
100    /// Build-time client chunk groups, not observed browser requests.
101    #[turbo_tasks::function]
102    fn analyze_chunk_groups(self: Vc<Self>) -> Vc<AnalyzeChunkGroups> {
103        Vc::cell(vec![])
104    }
105    /// Additional entry modules for the module graph.
106    /// This may read the module graph and return additional modules.
107    #[turbo_tasks::function]
108    fn additional_entries(self: Vc<Self>, _graph: Vc<ModuleGraph>) -> Vc<GraphEntries> {
109        GraphEntries::empty()
110    }
111    #[turbo_tasks::function]
112    fn module_graphs(self: Vc<Self>) -> Vc<ModuleGraphs>;
113    /// The project this endpoint belongs to.
114    #[turbo_tasks::function]
115    fn project(self: Vc<Self>) -> Vc<Project>;
116
117    /// The traced files included by this endpoint. This is only used for analysis purposes.
118    /// Usually, `output()` includes the NFT file and everything else is handled outside of
119    /// Turbopack.
120    #[turbo_tasks::function]
121    fn traced_files(self: Vc<Self>) -> Vc<FileSystemPathVec>;
122}
123
124#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
125pub enum EndpointGroupKey {
126    Instrumentation,
127    InstrumentationEdge,
128    Middleware,
129    PagesError,
130    PagesApp,
131    PagesDocument,
132    Route(RcStr),
133}
134
135impl EndpointGroupKey {
136    pub fn as_str(&self) -> &str {
137        match self {
138            EndpointGroupKey::Instrumentation => "instrumentation",
139            EndpointGroupKey::InstrumentationEdge => "instrumentation-edge",
140            EndpointGroupKey::Middleware => "middleware",
141            EndpointGroupKey::PagesError => "_error",
142            EndpointGroupKey::PagesApp => "_app",
143            EndpointGroupKey::PagesDocument => "_document",
144            EndpointGroupKey::Route(route) => route,
145        }
146    }
147}
148
149impl Display for EndpointGroupKey {
150    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151        match self {
152            EndpointGroupKey::Instrumentation => write!(f, "instrumentation"),
153            EndpointGroupKey::InstrumentationEdge => write!(f, "instrumentation-edge"),
154            EndpointGroupKey::Middleware => write!(f, "middleware"),
155            EndpointGroupKey::PagesError => write!(f, "_error"),
156            EndpointGroupKey::PagesApp => write!(f, "_app"),
157            EndpointGroupKey::PagesDocument => write!(f, "_document"),
158            EndpointGroupKey::Route(route) => write!(f, "{}", route),
159        }
160    }
161}
162
163#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
164pub struct EndpointGroupEntry {
165    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
166    pub sub_name: Option<RcStr>,
167}
168
169#[derive(PartialEq, Eq, ValueDebugFormat, Clone, Debug, NonLocalValue, Encode, Decode)]
170pub struct EndpointGroup {
171    pub primary: Vec<EndpointGroupEntry>,
172    pub additional: Vec<EndpointGroupEntry>,
173}
174
175impl EndpointGroup {
176    pub fn from(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Self {
177        Self {
178            primary: vec![EndpointGroupEntry {
179                endpoint,
180                sub_name: None,
181            }],
182            additional: vec![],
183        }
184    }
185
186    pub fn output_assets(&self) -> Vc<OutputAssets> {
187        output_of_endpoints(
188            self.primary
189                .iter()
190                .map(|endpoint| *endpoint.endpoint)
191                .collect(),
192        )
193    }
194
195    pub fn module_graphs(&self) -> Vc<ModuleGraphs> {
196        module_graphs_of_endpoints(
197            self.primary
198                .iter()
199                .map(|endpoint| *endpoint.endpoint)
200                .collect(),
201        )
202    }
203
204    pub fn traced_files(&self) -> Vc<FileSystemPathVec> {
205        traced_files_of_endpoints(
206            self.primary
207                .iter()
208                .map(|endpoint| *endpoint.endpoint)
209                .collect(),
210        )
211    }
212}
213
214#[turbo_tasks::function]
215async fn output_of_endpoints(endpoints: Vec<Vc<Box<dyn Endpoint>>>) -> Result<Vc<OutputAssets>> {
216    let assets = endpoints
217        .iter()
218        .map(async |endpoint| Ok(*endpoint.output().await?.output_assets))
219        .try_join()
220        .await?;
221    Ok(OutputAssets::concat(assets))
222}
223
224#[turbo_tasks::function]
225async fn module_graphs_of_endpoints(
226    endpoints: Vec<Vc<Box<dyn Endpoint>>>,
227) -> Result<Vc<ModuleGraphs>> {
228    // Deduplicate while preserving first-seen order, then hand back a `Vec`.
229    let module_graphs = endpoints
230        .iter()
231        .map(async |endpoint| anyhow::Ok(endpoint.module_graphs().await?.into_iter()))
232        .join()
233        .await
234        .into_iter()
235        .flatten_ok()
236        .collect::<Result<FxIndexSet<_>>>()?
237        .into_iter()
238        .collect::<Vec<_>>();
239    Ok(Vc::cell(module_graphs))
240}
241
242#[turbo_tasks::function]
243async fn traced_files_of_endpoints(
244    endpoints: Vec<Vc<Box<dyn Endpoint>>>,
245) -> Result<Vc<FileSystemPathVec>> {
246    let mut modules: FxIndexSet<_> = FxIndexSet::default();
247    for endpoint in endpoints {
248        modules.extend(endpoint.traced_files().await?.iter().cloned());
249    }
250    Ok(Vc::cell(modules.into_iter().collect()))
251}
252
253#[turbo_tasks::value(transparent)]
254pub struct EndpointGroups(Vec<(EndpointGroupKey, EndpointGroup)>);
255
256#[turbo_tasks::value(transparent)]
257pub struct Endpoints(Vec<ResolvedVc<Box<dyn Endpoint>>>);
258
259#[turbo_tasks::function]
260pub async fn endpoint_write_to_disk(
261    endpoint: ResolvedVc<Box<dyn Endpoint>>,
262) -> Result<Vc<EndpointOutputPaths>> {
263    let output_op = output_assets_operation(endpoint);
264    let EndpointOutput {
265        project,
266        output_paths,
267        ..
268    } = *output_op.connect().await?;
269
270    project
271        .emit_all_output_assets(endpoint_output_assets_operation(output_op))
272        .as_side_effect()
273        .await?;
274
275    Ok(*output_paths)
276}
277
278#[turbo_tasks::function(operation)]
279fn output_assets_operation(endpoint: ResolvedVc<Box<dyn Endpoint>>) -> Vc<EndpointOutput> {
280    endpoint.output()
281}
282
283#[turbo_tasks::function(operation)]
284async fn endpoint_output_assets_operation(
285    output: OperationVc<EndpointOutput>,
286) -> Result<Vc<OutputAssets>> {
287    Ok(*output.connect().await?.output_assets)
288}
289
290#[turbo_tasks::function(operation, root)]
291pub async fn endpoint_write_to_disk_operation(
292    endpoint: OperationVc<OptionEndpoint>,
293) -> Result<Vc<EndpointOutputPaths>> {
294    Ok(if let Some(endpoint) = *endpoint.connect().await? {
295        endpoint_write_to_disk(*endpoint)
296    } else {
297        EndpointOutputPaths::NotFound.cell()
298    })
299}
300
301#[turbo_tasks::function(operation, root)]
302pub async fn endpoint_server_changed_operation(
303    endpoint: OperationVc<OptionEndpoint>,
304) -> Result<Vc<Completion>> {
305    Ok(if let Some(endpoint) = *endpoint.connect().await? {
306        endpoint.server_changed()
307    } else {
308        Completion::new()
309    })
310}
311
312#[turbo_tasks::function(operation, root)]
313pub async fn endpoint_client_changed_operation(
314    endpoint: OperationVc<OptionEndpoint>,
315) -> Result<Vc<Completion>> {
316    Ok(if let Some(endpoint) = *endpoint.connect().await? {
317        endpoint.client_changed()
318    } else {
319        Completion::new()
320    })
321}
322
323#[turbo_tasks::value(shared)]
324#[derive(Debug, Clone)]
325pub struct EndpointOutput {
326    pub output_assets: ResolvedVc<OutputAssets>,
327    pub output_paths: ResolvedVc<EndpointOutputPaths>,
328    pub project: ResolvedVc<Project>,
329}
330
331#[turbo_tasks::value(shared)]
332#[derive(Debug, Clone)]
333pub enum EndpointOutputPaths {
334    NodeJs {
335        /// Relative to the root_path
336        server_entry_path: RcStr,
337        server_hmr_entry_paths: Vec<RcStr>,
338        server_paths: Vec<AssetPath>,
339        client_paths: Vec<RcStr>,
340    },
341    Edge {
342        server_paths: Vec<AssetPath>,
343        client_paths: Vec<RcStr>,
344    },
345    NotFound,
346}
347
348/// The routes as map from pathname to route. (pathname includes the leading
349/// slash)
350#[turbo_tasks::value(transparent)]
351pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);