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 #[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 #[turbo_tasks::function]
61 fn entries(self: Vc<Self>) -> Vc<GraphEntries>;
62 #[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 #[turbo_tasks::function]
72 fn project(self: Vc<Self>) -> Vc<Project>;
73
74 #[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 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 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#[turbo_tasks::value(transparent)]
314pub struct Routes(#[bincode(with = "turbo_bincode::indexmap")] FxIndexMap<RcStr, Route>);