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 "pages.js"
57 }
58 (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::NodeJs) => {
59 "pages-api.js"
61 }
62 (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::Edge) => {
63 "pages-edge-api.js"
65 }
66 _ => bail!("Invalid path type"),
67 };
68
69 let inner = rcstr!("INNER_PAGE");
70
71 let inner_document = rcstr!("INNER_DOCUMENT");
72 let inner_app = rcstr!("INNER_APP");
73
74 let mut replacements = vec![
75 ("VAR_DEFINITION_PAGE", &*definition_page),
76 ("VAR_DEFINITION_PATHNAME", &definition_pathname),
77 ("VAR_USERLAND", &inner),
78 ];
79
80 if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page) {
81 replacements.push(("VAR_MODULE_DOCUMENT", &inner_document));
82 replacements.push(("VAR_MODULE_APP", &inner_app));
83 }
84
85 let mut source =
87 load_next_js_template(template_file, project_root.clone(), &replacements, &[], &[]).await?;
88
89 if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page)
93 && (definition_page == "/instrumentation" || definition_page == "/src/instrumentation")
94 {
95 let file = &*file_content_rope(source.content().file_content()).await?;
96
97 let mut result = RopeBuilder::default();
98 result += file;
99
100 writeln!(
101 result,
102 r#"export const register = hoist(userland, "register")"#
103 )?;
104
105 let file = File::from(result.build());
106
107 source = Vc::upcast(VirtualSource::new(
108 source.ident().path().owned().await?,
109 AssetContent::file(file.into()),
110 ));
111 }
112
113 let mut inner_assets = fxindexmap! {
114 inner => ssr_module,
115 };
116
117 let pages_structure_ref = pages_structure.await?;
118
119 let (app_module, document_module) =
120 if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page) {
121 let document_module = process_global_item(
122 *pages_structure_ref.document,
123 reference_type.clone(),
124 ssr_module_context,
125 )
126 .to_resolved()
127 .await?;
128 let app_module = process_global_item(
129 *pages_structure_ref.app,
130 reference_type.clone(),
131 ssr_module_context,
132 )
133 .to_resolved()
134 .await?;
135 inner_assets.insert(inner_document, document_module);
136 inner_assets.insert(inner_app, app_module);
137 (Some(app_module), Some(document_module))
138 } else {
139 (None, None)
140 };
141
142 let mut ssr_module = ssr_module_context
143 .process(
144 source,
145 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
146 )
147 .module();
148
149 if matches!(runtime, NextRuntime::Edge) {
150 if reference_type == ReferenceType::Entry(EntryReferenceSubType::Page) {
151 ssr_module = wrap_edge_page(
152 ssr_module_context,
153 project_root,
154 ssr_module,
155 definition_page.clone(),
156 definition_pathname.clone(),
157 reference_type,
158 pages_structure,
159 next_config,
160 );
161 } else {
162 ssr_module = wrap_edge_entry(
163 ssr_module_context,
164 project_root,
165 ssr_module,
166 pages_function_name(&definition_page).into(),
167 );
168 }
169 }
170
171 Ok(PageSsrEntryModule {
172 ssr_module: ssr_module.to_resolved().await?,
173 app_module,
174 document_module,
175 }
176 .cell())
177}
178
179#[turbo_tasks::function]
180async fn process_global_item(
181 item: Vc<PagesStructureItem>,
182 reference_type: ReferenceType,
183 module_context: Vc<Box<dyn AssetContext>>,
184) -> Result<Vc<Box<dyn Module>>> {
185 let source = Vc::upcast(FileSource::new(item.file_path().owned().await?));
186 Ok(module_context.process(source, reference_type).module())
187}
188
189#[turbo_tasks::function]
190async fn wrap_edge_page(
191 asset_context: Vc<Box<dyn AssetContext>>,
192 project_root: FileSystemPath,
193 entry: ResolvedVc<Box<dyn Module>>,
194 page: RcStr,
195 pathname: RcStr,
196 reference_type: ReferenceType,
197 pages_structure: Vc<PagesStructure>,
198 next_config: Vc<NextConfig>,
199) -> Result<Vc<Box<dyn Module>>> {
200 const INNER: &str = "INNER_PAGE_ENTRY";
201
202 const INNER_DOCUMENT: &str = "INNER_DOCUMENT";
203 const INNER_APP: &str = "INNER_APP";
204 const INNER_ERROR: &str = "INNER_ERROR";
205 const INNER_ERROR_500: &str = "INNER_500";
206
207 let next_config_val = &*next_config.await?;
208
209 let source = load_next_js_template(
210 "edge-ssr.js",
211 project_root.clone(),
212 &[
213 ("VAR_USERLAND", INNER),
214 ("VAR_PAGE", &pathname),
215 ("VAR_MODULE_DOCUMENT", INNER_DOCUMENT),
216 ("VAR_MODULE_APP", INNER_APP),
217 ("VAR_MODULE_GLOBAL_ERROR", INNER_ERROR),
218 ],
219 &[
220 ("nextConfig", &*serde_json::to_string(next_config_val)?),
223 (
224 "pageRouteModuleOptions",
225 &serde_json::to_string(&get_route_module_options(page.clone(), pathname.clone()))?,
226 ),
227 (
228 "errorRouteModuleOptions",
229 &serde_json::to_string(&get_route_module_options(
230 rcstr!("/_error"),
231 rcstr!("/_error"),
232 ))?,
233 ),
234 (
235 "user500RouteModuleOptions",
236 &serde_json::to_string(&get_route_module_options(rcstr!("/500"), rcstr!("/500")))?,
237 ),
238 ],
239 &[
240 ("incrementalCacheHandler", None),
242 (
243 "userland500Page",
244 pages_structure.await?.error_500.map(|_| INNER_ERROR_500),
245 ),
246 ],
247 )
248 .await?;
249
250 let pages_structure_ref = pages_structure.await?;
251
252 let mut inner_assets = fxindexmap! {
253 INNER.into() => entry,
254 INNER_DOCUMENT.into() => process_global_item(
255 *pages_structure_ref.document,
256 reference_type.clone(),
257 asset_context,
258 ).to_resolved().await?,
259 INNER_APP.into() => process_global_item(
260 *pages_structure_ref.app,
261 reference_type.clone(),
262 asset_context,
263 ).to_resolved().await?,
264 INNER_ERROR.into() => process_global_item(
265 *pages_structure_ref.error,
266 reference_type.clone(),
267 asset_context,
268 ).to_resolved().await?,
269 };
270
271 if let Some(error_500) = pages_structure_ref.error_500 {
272 inner_assets.insert(
273 INNER_ERROR_500.into(),
274 process_global_item(*error_500, reference_type.clone(), asset_context)
275 .to_resolved()
276 .await?,
277 );
278 }
279
280 let wrapped = asset_context
281 .process(
282 Vc::upcast(source),
283 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
284 )
285 .module();
286
287 Ok(wrap_edge_entry(
288 asset_context,
289 project_root,
290 wrapped,
291 pages_function_name(&page).into(),
292 ))
293}
294
295#[derive(Serialize)]
296struct PartialRouteModuleOptions {
297 definition: RouteDefinition,
298}
299
300#[derive(Serialize)]
301struct RouteDefinition {
302 kind: RcStr,
303 bundle_path: RcStr,
304 filename: RcStr,
305 page: RcStr,
309
310 pathname: RcStr,
312}
313
314fn get_route_module_options(page: RcStr, pathname: RcStr) -> PartialRouteModuleOptions {
315 PartialRouteModuleOptions {
316 definition: RouteDefinition {
317 kind: rcstr!("PAGES"),
318 page,
319 pathname,
320 bundle_path: rcstr!(""),
322 filename: rcstr!(""),
323 },
324 }
325}