next_core/next_pages/
page_entry.rs

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            // Load the Page entry file.
56            "pages.js"
57        }
58        (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::NodeJs) => {
59            // Load the Pages API entry file.
60            "pages-api.js"
61        }
62        (ReferenceType::Entry(EntryReferenceSubType::PagesApi), NextRuntime::Edge) => {
63            // Load the Pages API entry file.
64            "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    // Load the file from the next.js codebase.
86    let mut source =
87        load_next_js_template(template_file, project_root.clone(), &replacements, &[], &[]).await?;
88
89    // When we're building the instrumentation page (only when the
90    // instrumentation file conflicts with a page also labeled
91    // /instrumentation) hoist the `register` method.
92    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            // TODO do we really need to pass the entire next config here?
221            // This is bad for invalidation as any config change will invalidate this
222            ("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            // TODO
241            ("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    /// Describes the pathname including all internal modifiers such as
306    /// intercepting routes, parallel routes and route/page suffixes that are
307    /// not part of the pathname.
308    page: RcStr,
309
310    /// The pathname (including dynamic placeholders) for a route to resolve.
311    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            // The following aren't used in production.
321            bundle_path: rcstr!(""),
322            filename: rcstr!(""),
323        },
324    }
325}