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, FileContent, 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_edge::entry::wrap_edge_entry,
20 pages_structure::{PagesStructure, PagesStructureItem},
21 util::{NextRuntime, file_content_rope, load_next_js_template, pages_function_name},
22};
23
24#[turbo_tasks::value]
25pub struct PageSsrEntryModule {
26 pub ssr_module: ResolvedVc<Box<dyn Module>>,
27 pub app_module: Option<ResolvedVc<Box<dyn Module>>>,
28 pub document_module: Option<ResolvedVc<Box<dyn Module>>>,
29}
30
31#[turbo_tasks::function]
32pub async fn create_page_ssr_entry_module(
33 pathname: RcStr,
34 reference_type: ReferenceType,
35 project_root: FileSystemPath,
36 ssr_module_context: Vc<Box<dyn AssetContext>>,
37 source: Vc<Box<dyn Source>>,
38 next_original_name: RcStr,
39 pages_structure: Vc<PagesStructure>,
40 runtime: NextRuntime,
41) -> Result<Vc<PageSsrEntryModule>> {
42 let definition_page = next_original_name;
43 let definition_pathname = pathname;
44
45 let ssr_module = ssr_module_context
46 .process(source, reference_type.clone())
47 .module()
48 .to_resolved()
49 .await?;
50
51 let template_file = match &reference_type {
52 ReferenceType::Entry(EntryReferenceSubType::Page)
53 | ReferenceType::Entry(EntryReferenceSubType::PageData) => {
54 match runtime {
56 NextRuntime::NodeJs => "pages.js",
57 NextRuntime::Edge => "edge-ssr.js",
58 }
59 }
60 ReferenceType::Entry(EntryReferenceSubType::PagesApi) => {
61 match runtime {
63 NextRuntime::NodeJs => "pages-api.js",
64 NextRuntime::Edge => "pages-edge-api.js",
65 }
66 }
67 _ => bail!("Invalid path type"),
68 };
69
70 let inner = rcstr!("INNER_PAGE");
71 let inner_document = rcstr!("INNER_DOCUMENT");
72 let inner_app = rcstr!("INNER_APP");
73 let inner_error = rcstr!("INNER_ERROR");
74 let inner_error_500 = rcstr!("INNER_500");
75
76 let mut replacements = vec![
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 && runtime == NextRuntime::Edge) {
87 replacements.push(("VAR_DEFINITION_PAGE", &*definition_page));
88 }
89 if is_page {
90 replacements.push(("VAR_MODULE_DOCUMENT", &*inner_document));
91 replacements.push(("VAR_MODULE_APP", &*inner_app));
92 if is_page && runtime == NextRuntime::Edge {
93 replacements.push(("VAR_MODULE_GLOBAL_ERROR", &*inner_error));
94 }
95 }
96
97 let pages_structure_ref = pages_structure.await?;
98
99 let (injections, imports) = if is_page && runtime == NextRuntime::Edge {
100 let injections = vec![
101 (
102 "pageRouteModuleOptions",
103 serde_json::to_string(&get_route_module_options(
104 definition_page.clone(),
105 definition_pathname.clone(),
106 ))?,
107 ),
108 (
109 "errorRouteModuleOptions",
110 serde_json::to_string(&get_route_module_options(
111 rcstr!("/_error"),
112 rcstr!("/_error"),
113 ))?,
114 ),
115 (
116 "user500RouteModuleOptions",
117 serde_json::to_string(&get_route_module_options(rcstr!("/500"), rcstr!("/500")))?,
118 ),
119 ];
120 let imports = vec![
121 ("incrementalCacheHandler", None),
123 (
124 "userland500Page",
125 pages_structure_ref.error_500.map(|_| &*inner_error_500),
126 ),
127 ];
128 (injections, imports)
129 } else {
130 (vec![], vec![])
131 };
132
133 let mut source = load_next_js_template(
135 template_file,
136 project_root.clone(),
137 replacements,
138 injections.iter().map(|(k, v)| (*k, &**v)),
139 imports,
140 )
141 .await?;
142
143 if is_page
147 && (definition_page == "/instrumentation" || definition_page == "/src/instrumentation")
148 {
149 let file = &*file_content_rope(source.content().file_content()).await?;
150
151 let mut result = RopeBuilder::default();
152 result += file;
153
154 writeln!(
155 result,
156 r#"export const register = hoist(userland, "register")"#
157 )?;
158
159 let file = File::from(result.build());
160
161 source = Vc::upcast(VirtualSource::new_with_ident(
162 source.ident(),
163 AssetContent::file(FileContent::Content(file).cell()),
164 ));
165 }
166
167 let mut inner_assets = fxindexmap! {
168 inner => ssr_module,
169 };
170
171 let source_query = source.ident().await?.query.clone();
175
176 let (app_module, document_module) = if is_page {
177 let document_module = process_global_item(
179 *pages_structure_ref.document,
180 reference_type.clone(),
181 source_query.clone(),
182 ssr_module_context,
183 )
184 .to_resolved()
185 .await?;
186 let app_module = process_global_item(
187 *pages_structure_ref.app,
188 reference_type.clone(),
189 source_query.clone(),
190 ssr_module_context,
191 )
192 .to_resolved()
193 .await?;
194 inner_assets.insert(inner_document, document_module);
195 inner_assets.insert(inner_app, app_module);
196
197 if is_page && runtime == NextRuntime::Edge {
198 inner_assets.insert(
199 inner_error,
200 process_global_item(
201 *pages_structure_ref.error,
202 reference_type.clone(),
203 source_query.clone(),
204 ssr_module_context,
205 )
206 .to_resolved()
207 .await?,
208 );
209
210 if let Some(error_500) = pages_structure_ref.error_500 {
211 inner_assets.insert(
212 inner_error_500,
213 process_global_item(
214 *error_500,
215 reference_type.clone(),
216 source_query.clone(),
217 ssr_module_context,
218 )
219 .to_resolved()
220 .await?,
221 );
222 }
223 }
224 (Some(app_module), Some(document_module))
225 } else {
226 (None, None)
227 };
228
229 let mut ssr_module = ssr_module_context
230 .process(
231 source,
232 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
233 )
234 .module();
235
236 if matches!(runtime, NextRuntime::Edge) {
237 ssr_module = wrap_edge_entry(
238 ssr_module_context,
239 project_root,
240 ssr_module,
241 pages_function_name(&definition_page).into(),
242 );
243 }
244
245 Ok(PageSsrEntryModule {
246 ssr_module: ssr_module.to_resolved().await?,
247 app_module,
248 document_module,
249 }
250 .cell())
251}
252
253#[turbo_tasks::function]
254async fn process_global_item(
255 item: Vc<PagesStructureItem>,
256 reference_type: ReferenceType,
257 source_query: RcStr,
258 module_context: Vc<Box<dyn AssetContext>>,
259) -> Result<Vc<Box<dyn Module>>> {
260 let source = Vc::upcast(FileSource::new_with_query(
261 item.file_path().owned().await?,
262 source_query,
263 ));
264 Ok(module_context.process(source, reference_type).module())
265}
266
267#[derive(Serialize)]
268struct PartialRouteModuleOptions {
269 definition: RouteDefinition,
270}
271
272#[derive(Serialize)]
273struct RouteDefinition {
274 kind: RcStr,
275 bundle_path: RcStr,
276 filename: RcStr,
277 page: RcStr,
281
282 pathname: RcStr,
284}
285
286fn get_route_module_options(page: RcStr, pathname: RcStr) -> PartialRouteModuleOptions {
287 PartialRouteModuleOptions {
288 definition: RouteDefinition {
289 kind: rcstr!("PAGES"),
290 page,
291 pathname,
292 bundle_path: rcstr!(""),
294 filename: rcstr!(""),
295 },
296 }
297}