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 file_source::FileSource,
12 module::Module,
13 reference_type::ReferenceType,
14 source::Source,
15 virtual_source::VirtualSource,
16};
17use turbopack_ecmascript::runtime_functions::{TURBOPACK_LOAD, TURBOPACK_REQUIRE};
18
19use crate::{
20 app_page_loader_tree::AppPageLoaderTreeModule,
21 app_structure::AppPageLoaderTree,
22 next_app::{AppPage, AppPath, app_entry::AppEntry},
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.base_path().owned().await?;
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 [
84 ("tree", &*loader_tree_code),
85 ("__next_app_require__", &TURBOPACK_REQUIRE.bound()),
86 ("__next_app_load_chunk__", &TURBOPACK_LOAD.bound()),
87 ],
88 [],
89 )
90 .await?;
91
92 let source_content = &*file_content_rope(source.content().file_content()).await?;
93
94 result.concat(source_content);
95
96 let query = qstring::QString::new(vec![("page", page.to_string())]);
97
98 let file = File::from(result.build());
99 let source = VirtualSource::new_with_ident(
100 source
101 .ident()
102 .owned()
103 .await?
104 .with_query(RcStr::from(format!("?{query}")))
105 .into_vc(),
106 AssetContent::file(FileContent::Content(file).cell()),
107 );
108
109 let mut rsc_entry = module_asset_context
110 .process(
111 Vc::upcast(source),
112 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
113 )
114 .module();
115
116 if is_edge {
117 rsc_entry = wrap_edge_page(
118 *ResolvedVc::upcast(module_asset_context),
119 project_root.clone(),
120 rsc_entry,
121 page,
122 next_config,
123 );
124 };
125
126 Ok(AppEntry {
127 pathname,
128 original_name,
129 rsc_entry: rsc_entry.to_resolved().await?,
130 config: config.to_resolved().await?,
131 }
132 .cell())
133}
134
135#[turbo_tasks::function]
136async fn wrap_edge_page(
137 asset_context: Vc<Box<dyn AssetContext>>,
138 project_root: FileSystemPath,
139 entry: ResolvedVc<Box<dyn Module>>,
140 page: AppPage,
141 next_config: Vc<NextConfig>,
142) -> Result<Vc<Box<dyn Module>>> {
143 const INNER: &str = "INNER_PAGE_ENTRY";
144 let mut cache_handler_imports = String::new();
145 let mut cache_handler_registration = String::new();
146 let mut incremental_cache_handler_import = None;
147 let mut cache_handler_inner_assets = fxindexmap! {};
148
149 let cache_handlers = next_config.cache_handlers_map().owned().await?;
150 for (index, (kind, handler_path)) in cache_handlers.iter().enumerate() {
151 let cache_handler_inner: RcStr = format!("INNER_CACHE_HANDLER_{index}").into();
152 let cache_handler_var = format!("cacheHandler{index}");
153 cache_handler_imports.push_str(&format!(
154 "import {cache_handler_var} from {};\n",
155 serde_json::to_string(&*cache_handler_inner)?
156 ));
157 cache_handler_registration.push_str(&format!(
158 " cacheHandlers.setCacheHandler({}, {cache_handler_var});\n",
159 serde_json::to_string(kind.as_str())?
160 ));
161
162 let cache_handler_module = asset_context
163 .process(
164 Vc::upcast(FileSource::new(project_root.join(handler_path)?)),
165 ReferenceType::Undefined,
166 )
167 .module()
168 .to_resolved()
169 .await?;
170 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
171 }
172
173 for cache_handler_path in next_config
174 .cache_handler(project_root.clone())
175 .await?
176 .into_iter()
177 {
178 let cache_handler_inner: RcStr = "INNER_INCREMENTAL_CACHE_HANDLER".into();
179 incremental_cache_handler_import = Some(cache_handler_inner.clone());
180 let cache_handler_module = asset_context
181 .process(
182 Vc::upcast(FileSource::new(cache_handler_path.clone())),
183 ReferenceType::Undefined,
184 )
185 .module()
186 .to_resolved()
187 .await?;
188 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
189 }
190
191 let source = load_next_js_template(
192 "edge-ssr-app.js",
193 project_root.clone(),
194 [("VAR_USERLAND", INNER), ("VAR_PAGE", &page.to_string())],
195 [
196 ("cacheHandlerImports", cache_handler_imports.as_str()),
197 (
198 "cacheHandlerRegistration",
199 cache_handler_registration.as_str(),
200 ),
201 ],
202 [(
203 "incrementalCacheHandler",
204 incremental_cache_handler_import.as_deref(),
205 )],
206 )
207 .await?;
208
209 let mut inner_assets = fxindexmap! {
210 INNER.into() => entry
211 };
212 inner_assets.extend(cache_handler_inner_assets);
213
214 let wrapped = asset_context
215 .process(
216 source,
217 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
218 )
219 .module();
220
221 Ok(wrap_edge_entry(
222 asset_context,
223 project_root,
224 wrapped,
225 app_function_name(&page).into(),
226 ))
227}