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