1use std::io::Write;
2
3use anyhow::{Result, bail};
4use serde::Serialize;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{ResolvedVc, Vc, fxindexmap};
7use turbo_tasks_fs::{File, FileSystemPath, rope::RopeBuilder};
8use turbopack_core::{
9 asset::{Asset, AssetContent},
10 context::AssetContext,
11 file_source::FileSource,
12 module::Module,
13 reference_type::{EntryReferenceSubType, ReferenceType},
14 source::Source,
15 virtual_source::VirtualSource,
16};
17
18use crate::{
19 next_config::NextConfig,
20 next_edge::entry::wrap_edge_entry,
21 pages_structure::{PagesStructure, PagesStructureItem},
22 util::{NextRuntime, file_content_rope, load_next_js_template, pages_function_name},
23};
24
25#[turbo_tasks::value]
26pub struct PageSsrEntryModule {
27 pub ssr_module: ResolvedVc<Box<dyn Module>>,
28 pub app_module: Option<ResolvedVc<Box<dyn Module>>>,
29 pub document_module: Option<ResolvedVc<Box<dyn Module>>>,
30}
31
32#[turbo_tasks::function]
33pub async fn create_page_ssr_entry_module(
34 pathname: RcStr,
35 reference_type: ReferenceType,
36 project_root: FileSystemPath,
37 ssr_module_context: Vc<Box<dyn AssetContext>>,
38 source: Vc<Box<dyn Source>>,
39 next_original_name: RcStr,
40 pages_structure: Vc<PagesStructure>,
41 runtime: NextRuntime,
42 next_config: Vc<NextConfig>,
43) -> Result<Vc<PageSsrEntryModule>> {
44 let definition_page = next_original_name;
45 let definition_pathname = pathname;
46
47 let ssr_module = ssr_module_context
48 .process(source, reference_type.clone())
49 .module()
50 .to_resolved()
51 .await?;
52
53 let template_file = match (&reference_type, runtime) {
54 (ReferenceType::Entry(EntryReferenceSubType::Page), _)
55 | (ReferenceType::Entry(EntryReferenceSubType::PageData), _) => {
56 "pages.js"
58 }
59 (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::NodeJs) => {
60 "pages-api.js"
62 }
63 (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::Edge) => {
64 "pages-edge-api.js"
66 }
67 _ => bail!("Invalid path type"),
68 };
69
70 let inner = rcstr!("INNER_PAGE");
71
72 let inner_document = rcstr!("INNER_DOCUMENT");
73 let inner_app = rcstr!("INNER_APP");
74
75 let mut replacements = vec![
76 ("VAR_DEFINITION_PAGE", &*definition_page),
77 ("VAR_DEFINITION_PATHNAME", &definition_pathname),
78 ("VAR_USERLAND", &inner),
79 ];
80
81 let is_page = matches!(
82 reference_type,
83 ReferenceType::Entry(EntryReferenceSubType::Page)
84 | ReferenceType::Entry(EntryReferenceSubType::PageData)
85 );
86 if is_page {
87 replacements.push(("VAR_MODULE_DOCUMENT", &inner_document));
88 replacements.push(("VAR_MODULE_APP", &inner_app));
89 }
90 let source_ident = source.ident().await?;
91 let source_query = source_ident.query.clone();
95
96 let mut source =
98 load_next_js_template(template_file, project_root.clone(), &replacements, &[], &[]).await?;
99
100 if is_page
104 && (definition_page == "/instrumentation" || definition_page == "/src/instrumentation")
105 {
106 let file = &*file_content_rope(source.content().file_content()).await?;
107
108 let mut result = RopeBuilder::default();
109 result += file;
110
111 writeln!(
112 result,
113 r#"export const register = hoist(userland, "register")"#
114 )?;
115
116 let file = File::from(result.build());
117
118 source = Vc::upcast(VirtualSource::new_with_ident(
119 source.ident(),
120 AssetContent::file(file.into()),
121 ));
122 }
123
124 let mut inner_assets = fxindexmap! {
125 inner => ssr_module,
126 };
127
128 let pages_structure_ref = pages_structure.await?;
129
130 let (app_module, document_module) = if is_page {
131 let document_module = process_global_item(
133 *pages_structure_ref.document,
134 reference_type.clone(),
135 source_query.clone(),
136 ssr_module_context,
137 )
138 .to_resolved()
139 .await?;
140 let app_module = process_global_item(
141 *pages_structure_ref.app,
142 reference_type.clone(),
143 source_query.clone(),
144 ssr_module_context,
145 )
146 .to_resolved()
147 .await?;
148 inner_assets.insert(inner_document, document_module);
149 inner_assets.insert(inner_app, app_module);
150 (Some(app_module), Some(document_module))
151 } else {
152 (None, None)
153 };
154
155 let mut ssr_module = ssr_module_context
156 .process(
157 source,
158 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
159 )
160 .module();
161
162 if matches!(runtime, NextRuntime::Edge) {
163 if is_page {
164 ssr_module = wrap_edge_page(
165 ssr_module_context,
166 project_root,
167 ssr_module,
168 definition_page.clone(),
169 definition_pathname.clone(),
170 reference_type,
171 pages_structure,
172 next_config,
173 source_query.clone(),
174 );
175 } else {
176 ssr_module = wrap_edge_entry(
177 ssr_module_context,
178 project_root,
179 ssr_module,
180 pages_function_name(&definition_page).into(),
181 );
182 }
183 }
184
185 Ok(PageSsrEntryModule {
186 ssr_module: ssr_module.to_resolved().await?,
187 app_module,
188 document_module,
189 }
190 .cell())
191}
192
193#[turbo_tasks::function]
194async fn process_global_item(
195 item: Vc<PagesStructureItem>,
196 reference_type: ReferenceType,
197 source_query: RcStr,
198 module_context: Vc<Box<dyn AssetContext>>,
199) -> Result<Vc<Box<dyn Module>>> {
200 let source = Vc::upcast(FileSource::new_with_query(
201 item.file_path().owned().await?,
202 source_query,
203 ));
204 Ok(module_context.process(source, reference_type).module())
205}
206
207#[turbo_tasks::function]
208async fn wrap_edge_page(
209 asset_context: Vc<Box<dyn AssetContext>>,
210 project_root: FileSystemPath,
211 entry: ResolvedVc<Box<dyn Module>>,
212 page: RcStr,
213 pathname: RcStr,
214 reference_type: ReferenceType,
215 pages_structure: Vc<PagesStructure>,
216 next_config: Vc<NextConfig>,
217 source_query: RcStr,
218) -> Result<Vc<Box<dyn Module>>> {
219 const INNER: &str = "INNER_PAGE_ENTRY";
220
221 const INNER_DOCUMENT: &str = "INNER_DOCUMENT";
222 const INNER_APP: &str = "INNER_APP";
223 const INNER_ERROR: &str = "INNER_ERROR";
224 const INNER_ERROR_500: &str = "INNER_500";
225
226 let next_config_val = &*next_config.await?;
227
228 let source = load_next_js_template(
229 "edge-ssr.js",
230 project_root.clone(),
231 &[
232 ("VAR_USERLAND", INNER),
233 ("VAR_PAGE", &pathname),
234 ("VAR_MODULE_DOCUMENT", INNER_DOCUMENT),
235 ("VAR_MODULE_APP", INNER_APP),
236 ("VAR_MODULE_GLOBAL_ERROR", INNER_ERROR),
237 ],
238 &[
239 ("nextConfig", &*serde_json::to_string(next_config_val)?),
242 (
243 "pageRouteModuleOptions",
244 &serde_json::to_string(&get_route_module_options(page.clone(), pathname.clone()))?,
245 ),
246 (
247 "errorRouteModuleOptions",
248 &serde_json::to_string(&get_route_module_options(
249 rcstr!("/_error"),
250 rcstr!("/_error"),
251 ))?,
252 ),
253 (
254 "user500RouteModuleOptions",
255 &serde_json::to_string(&get_route_module_options(rcstr!("/500"), rcstr!("/500")))?,
256 ),
257 ],
258 &[
259 ("incrementalCacheHandler", None),
261 (
262 "userland500Page",
263 pages_structure.await?.error_500.map(|_| INNER_ERROR_500),
264 ),
265 ],
266 )
267 .await?;
268
269 let pages_structure_ref = pages_structure.await?;
270
271 let mut inner_assets = fxindexmap! {
272 INNER.into() => entry,
273 INNER_DOCUMENT.into() => process_global_item(
274 *pages_structure_ref.document,
275 reference_type.clone(),
276 source_query.clone(),
277 asset_context,
278 ).to_resolved().await?,
279 INNER_APP.into() => process_global_item(
280 *pages_structure_ref.app,
281 reference_type.clone(),
282 source_query.clone(),
283 asset_context,
284 ).to_resolved().await?,
285 INNER_ERROR.into() => process_global_item(
286 *pages_structure_ref.error,
287 reference_type.clone(),
288 source_query.clone(),
289 asset_context,
290 ).to_resolved().await?,
291 };
292
293 if let Some(error_500) = pages_structure_ref.error_500 {
294 inner_assets.insert(
295 INNER_ERROR_500.into(),
296 process_global_item(
297 *error_500,
298 reference_type.clone(),
299 source_query.clone(),
300 asset_context,
301 )
302 .to_resolved()
303 .await?,
304 );
305 }
306
307 let wrapped = asset_context
308 .process(
309 source,
310 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
311 )
312 .module();
313
314 Ok(wrap_edge_entry(
315 asset_context,
316 project_root,
317 wrapped,
318 pages_function_name(&page).into(),
319 ))
320}
321
322#[derive(Serialize)]
323struct PartialRouteModuleOptions {
324 definition: RouteDefinition,
325}
326
327#[derive(Serialize)]
328struct RouteDefinition {
329 kind: RcStr,
330 bundle_path: RcStr,
331 filename: RcStr,
332 page: RcStr,
336
337 pathname: RcStr,
339}
340
341fn get_route_module_options(page: RcStr, pathname: RcStr) -> PartialRouteModuleOptions {
342 PartialRouteModuleOptions {
343 definition: RouteDefinition {
344 kind: rcstr!("PAGES"),
345 page,
346 pathname,
347 bundle_path: rcstr!(""),
349 filename: rcstr!(""),
350 },
351 }
352}