Skip to main content

next_core/next_app/
app_page_entry.rs

1use std::io::Write;
2
3use anyhow::Result;
4use turbo_rcstr::RcStr;
5use turbo_tasks::{ResolvedVc, Vc, fxindexmap};
6use turbo_tasks_fs::{self, File, FileContent, FileSystemPath, rope::RopeBuilder};
7use turbopack::ModuleAssetContext;
8use turbopack_core::{
9    asset::{Asset, AssetContent},
10    context::AssetContext,
11    module::Module,
12    reference_type::ReferenceType,
13    source::Source,
14    virtual_source::VirtualSource,
15};
16use turbopack_ecmascript::runtime_functions::{TURBOPACK_LOAD, TURBOPACK_REQUIRE};
17
18use crate::{
19    app_page_loader_tree::AppPageLoaderTreeModule,
20    app_structure::AppPageLoaderTree,
21    next_app::{AppPage, AppPath, app_entry::AppEntry},
22    next_config::NextConfig,
23    next_edge::entry::wrap_edge_entry,
24    next_server_component::NextServerComponentTransition,
25    parse_segment_config_from_loader_tree,
26    util::{NextRuntime, app_function_name, file_content_rope, load_next_js_template},
27};
28
29/// Computes the entry for a Next.js app page.
30#[turbo_tasks::function]
31pub async fn get_app_page_entry(
32    nodejs_context: ResolvedVc<ModuleAssetContext>,
33    edge_context: ResolvedVc<ModuleAssetContext>,
34    loader_tree: Vc<AppPageLoaderTree>,
35    page: AppPage,
36    project_root: FileSystemPath,
37    next_config: Vc<NextConfig>,
38) -> Result<Vc<AppEntry>> {
39    let config = parse_segment_config_from_loader_tree(loader_tree);
40    let is_edge = matches!(config.await?.runtime, Some(NextRuntime::Edge));
41    let module_asset_context = if is_edge {
42        edge_context
43    } else {
44        nodejs_context
45    };
46
47    let server_component_transition =
48        ResolvedVc::upcast(NextServerComponentTransition::new().to_resolved().await?);
49
50    let base_path = next_config.base_path().owned().await?;
51    let loader_tree = AppPageLoaderTreeModule::build(
52        loader_tree,
53        module_asset_context,
54        server_component_transition,
55        base_path,
56    )
57    .await?;
58
59    let AppPageLoaderTreeModule {
60        inner_assets,
61        imports,
62        loader_tree_code,
63    } = loader_tree;
64
65    let mut result = RopeBuilder::default();
66
67    for import in imports {
68        writeln!(result, "{import}")?;
69    }
70
71    let original_name: RcStr = page.to_string().into();
72    let pathname: RcStr = AppPath::from(page.clone()).to_string().into();
73
74    // Load the file from the next.js codebase.
75    let source = load_next_js_template(
76        "app-page.js",
77        project_root.clone(),
78        [
79            ("VAR_DEFINITION_PAGE", &*page.to_string()),
80            ("VAR_DEFINITION_PATHNAME", &pathname),
81        ],
82        [
83            ("tree", &*loader_tree_code),
84            ("__next_app_require__", &TURBOPACK_REQUIRE.bound()),
85            ("__next_app_load_chunk__", &TURBOPACK_LOAD.bound()),
86        ],
87        [],
88    )
89    .await?;
90
91    let source_content = &*file_content_rope(source.content().file_content()).await?;
92
93    result.concat(source_content);
94
95    let query = qstring::QString::new(vec![("page", page.to_string())]);
96
97    let file = File::from(result.build());
98    let source = VirtualSource::new_with_ident(
99        source.ident().with_query(RcStr::from(format!("?{query}"))),
100        AssetContent::file(FileContent::Content(file).cell()),
101    );
102
103    let mut rsc_entry = module_asset_context
104        .process(
105            Vc::upcast(source),
106            ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
107        )
108        .module();
109
110    if is_edge {
111        rsc_entry = wrap_edge_page(
112            *ResolvedVc::upcast(module_asset_context),
113            project_root.clone(),
114            rsc_entry,
115            page,
116        );
117    };
118
119    Ok(AppEntry {
120        pathname,
121        original_name,
122        rsc_entry: rsc_entry.to_resolved().await?,
123        config: config.to_resolved().await?,
124    }
125    .cell())
126}
127
128#[turbo_tasks::function]
129async fn wrap_edge_page(
130    asset_context: Vc<Box<dyn AssetContext>>,
131    project_root: FileSystemPath,
132    entry: ResolvedVc<Box<dyn Module>>,
133    page: AppPage,
134) -> Result<Vc<Box<dyn Module>>> {
135    const INNER: &str = "INNER_PAGE_ENTRY";
136
137    let source = load_next_js_template(
138        "edge-ssr-app.js",
139        project_root.clone(),
140        [("VAR_USERLAND", INNER), ("VAR_PAGE", &page.to_string())],
141        [],
142        [("incrementalCacheHandler", None)],
143    )
144    .await?;
145
146    let inner_assets = fxindexmap! {
147        INNER.into() => entry
148    };
149
150    let wrapped = asset_context
151        .process(
152            source,
153            ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
154        )
155        .module();
156
157    Ok(wrap_edge_entry(
158        asset_context,
159        project_root,
160        wrapped,
161        app_function_name(&page).into(),
162    ))
163}