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