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