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