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_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 next_config: Vc<NextConfig>,
42 runtime: NextRuntime,
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 {
54 ReferenceType::Entry(EntryReferenceSubType::Page)
55 | ReferenceType::Entry(EntryReferenceSubType::PageData) => {
56 match runtime {
58 NextRuntime::NodeJs => "pages.js",
59 NextRuntime::Edge => "edge-ssr.js",
60 }
61 }
62 ReferenceType::Entry(EntryReferenceSubType::PagesApi) => {
63 match runtime {
65 NextRuntime::NodeJs => "pages-api.js",
66 NextRuntime::Edge => "pages-edge-api.js",
67 }
68 }
69 _ => bail!("Invalid path type"),
70 };
71
72 let inner = rcstr!("INNER_PAGE");
73 let inner_document = rcstr!("INNER_DOCUMENT");
74 let inner_app = rcstr!("INNER_APP");
75 let inner_error = rcstr!("INNER_ERROR");
76 let inner_error_500 = rcstr!("INNER_500");
77
78 let mut replacements = vec![
79 ("VAR_DEFINITION_PATHNAME", &*definition_pathname),
80 ("VAR_USERLAND", &*inner),
81 ];
82
83 let is_page = matches!(
84 reference_type,
85 ReferenceType::Entry(EntryReferenceSubType::Page)
86 | ReferenceType::Entry(EntryReferenceSubType::PageData)
87 );
88 if !(is_page && runtime == NextRuntime::Edge) {
89 replacements.push(("VAR_DEFINITION_PAGE", &*definition_page));
90 }
91 if is_page {
92 replacements.push(("VAR_MODULE_DOCUMENT", &*inner_document));
93 replacements.push(("VAR_MODULE_APP", &*inner_app));
94 if is_page && runtime == NextRuntime::Edge {
95 replacements.push(("VAR_MODULE_GLOBAL_ERROR", &*inner_error));
96 }
97 }
98
99 let pages_structure_ref = pages_structure.await?;
100 let mut cache_handler_inner_assets = fxindexmap! {};
101 let mut cache_handler_imports = String::new();
102 let mut cache_handler_registration = String::new();
103 let mut incremental_cache_handler_import = None;
104
105 if runtime == NextRuntime::Edge {
106 if is_page {
107 let cache_handlers = next_config.cache_handlers_map().owned().await?;
108 for (index, (kind, handler_path)) in cache_handlers.iter().enumerate() {
109 let cache_handler_inner: RcStr = format!("INNER_CACHE_HANDLER_{index}").into();
110 let cache_handler_var = format!("cacheHandler{index}");
111 cache_handler_imports.push_str(&format!(
112 "import {cache_handler_var} from {};\n",
113 serde_json::to_string(&*cache_handler_inner)?
114 ));
115 cache_handler_registration.push_str(&format!(
116 " cacheHandlers.setCacheHandler({}, {cache_handler_var});\n",
117 serde_json::to_string(kind.as_str())?
118 ));
119
120 let cache_handler_module = ssr_module_context
121 .process(
122 Vc::upcast(FileSource::new(project_root.join(handler_path)?)),
123 ReferenceType::Undefined,
124 )
125 .module()
126 .to_resolved()
127 .await?;
128 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
129 }
130 }
131
132 for cache_handler_path in next_config
133 .cache_handler(project_root.clone())
134 .await?
135 .into_iter()
136 {
137 let cache_handler_inner = rcstr!("INNER_INCREMENTAL_CACHE_HANDLER");
138 incremental_cache_handler_import = Some(cache_handler_inner.clone());
139 let cache_handler_module = ssr_module_context
140 .process(
141 Vc::upcast(FileSource::new(cache_handler_path.clone())),
142 ReferenceType::Undefined,
143 )
144 .module()
145 .to_resolved()
146 .await?;
147 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
148 }
149 }
150
151 let (injections, imports) = if is_page && runtime == NextRuntime::Edge {
152 let injections = vec![
153 (
154 "pageRouteModuleOptions",
155 serde_json::to_string(&get_route_module_options(
156 definition_page.clone(),
157 definition_pathname.clone(),
158 ))?,
159 ),
160 (
161 "errorRouteModuleOptions",
162 serde_json::to_string(&get_route_module_options(
163 rcstr!("/_error"),
164 rcstr!("/_error"),
165 ))?,
166 ),
167 (
168 "user500RouteModuleOptions",
169 serde_json::to_string(&get_route_module_options(rcstr!("/500"), rcstr!("/500")))?,
170 ),
171 ("cacheHandlerImports", cache_handler_imports),
172 ("cacheHandlerRegistration", cache_handler_registration),
173 ];
174 let imports = vec![
175 ("incrementalCacheHandler", incremental_cache_handler_import),
176 (
177 "userland500Page",
178 pages_structure_ref
179 .error_500
180 .map(|_| inner_error_500.clone()),
181 ),
182 ];
183 (injections, imports)
184 } else if runtime == NextRuntime::Edge {
185 (
186 vec![],
187 vec![("incrementalCacheHandler", incremental_cache_handler_import)],
188 )
189 } else {
190 (vec![], vec![])
191 };
192
193 let mut source = load_next_js_template(
195 template_file,
196 project_root.clone(),
197 replacements,
198 injections.iter().map(|(k, v)| (*k, &**v)),
199 imports
200 .iter()
201 .map(|(k, v)| (*k, v.as_ref().map(|value| value.as_str()))),
202 )
203 .await?;
204
205 if is_page
209 && (definition_page == "/instrumentation" || definition_page == "/src/instrumentation")
210 {
211 let file = &*file_content_rope(source.content().file_content()).await?;
212
213 let mut result = RopeBuilder::default();
214 result += file;
215
216 writeln!(
217 result,
218 r#"export const register = hoist(userland, "register")"#
219 )?;
220
221 let file = File::from(result.build());
222
223 source = Vc::upcast(VirtualSource::new_with_ident(
224 source.ident(),
225 AssetContent::file(FileContent::Content(file).cell()),
226 ));
227 }
228
229 let mut inner_assets = fxindexmap! {
230 inner => ssr_module,
231 };
232 inner_assets.extend(cache_handler_inner_assets);
233
234 let source_query = source.ident().await?.query.clone();
238
239 let (app_module, document_module) = if is_page {
240 let document_module = process_global_item(
242 *pages_structure_ref.document,
243 reference_type.clone(),
244 source_query.clone(),
245 ssr_module_context,
246 )
247 .to_resolved()
248 .await?;
249 let app_module = process_global_item(
250 *pages_structure_ref.app,
251 reference_type.clone(),
252 source_query.clone(),
253 ssr_module_context,
254 )
255 .to_resolved()
256 .await?;
257 inner_assets.insert(inner_document, document_module);
258 inner_assets.insert(inner_app, app_module);
259
260 if is_page && runtime == NextRuntime::Edge {
261 inner_assets.insert(
262 inner_error,
263 process_global_item(
264 *pages_structure_ref.error,
265 reference_type.clone(),
266 source_query.clone(),
267 ssr_module_context,
268 )
269 .to_resolved()
270 .await?,
271 );
272
273 if let Some(error_500) = pages_structure_ref.error_500 {
274 inner_assets.insert(
275 inner_error_500,
276 process_global_item(
277 *error_500,
278 reference_type.clone(),
279 source_query.clone(),
280 ssr_module_context,
281 )
282 .to_resolved()
283 .await?,
284 );
285 }
286 }
287 (Some(app_module), Some(document_module))
288 } else {
289 (None, None)
290 };
291
292 let mut ssr_module = ssr_module_context
293 .process(
294 source,
295 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
296 )
297 .module();
298
299 if matches!(runtime, NextRuntime::Edge) {
300 ssr_module = wrap_edge_entry(
301 ssr_module_context,
302 project_root,
303 ssr_module,
304 pages_function_name(&definition_page).into(),
305 );
306 }
307
308 Ok(PageSsrEntryModule {
309 ssr_module: ssr_module.to_resolved().await?,
310 app_module,
311 document_module,
312 }
313 .cell())
314}
315
316#[turbo_tasks::function]
317async fn process_global_item(
318 item: Vc<PagesStructureItem>,
319 reference_type: ReferenceType,
320 source_query: RcStr,
321 module_context: Vc<Box<dyn AssetContext>>,
322) -> Result<Vc<Box<dyn Module>>> {
323 let source = Vc::upcast(FileSource::new_with_query(
324 item.file_path().owned().await?,
325 source_query,
326 ));
327 Ok(module_context.process(source, reference_type).module())
328}
329
330#[derive(Serialize)]
331struct PartialRouteModuleOptions {
332 definition: RouteDefinition,
333}
334
335#[derive(Serialize)]
336struct RouteDefinition {
337 kind: RcStr,
338 bundle_path: RcStr,
339 filename: RcStr,
340 page: RcStr,
344
345 pathname: RcStr,
347}
348
349fn get_route_module_options(page: RcStr, pathname: RcStr) -> PartialRouteModuleOptions {
350 PartialRouteModuleOptions {
351 definition: RouteDefinition {
352 kind: rcstr!("PAGES"),
353 page,
354 pathname,
355 bundle_path: rcstr!(""),
357 filename: rcstr!(""),
358 },
359 }
360}