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.ident().with_query(RcStr::from(format!("?{query}"))),
101 AssetContent::file(FileContent::Content(file).cell()),
102 );
103
104 let mut rsc_entry = module_asset_context
105 .process(
106 Vc::upcast(source),
107 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
108 )
109 .module();
110
111 if is_edge {
112 rsc_entry = wrap_edge_page(
113 *ResolvedVc::upcast(module_asset_context),
114 project_root.clone(),
115 rsc_entry,
116 page,
117 next_config,
118 );
119 };
120
121 Ok(AppEntry {
122 pathname,
123 original_name,
124 rsc_entry: rsc_entry.to_resolved().await?,
125 config: config.to_resolved().await?,
126 }
127 .cell())
128}
129
130#[turbo_tasks::function]
131async fn wrap_edge_page(
132 asset_context: Vc<Box<dyn AssetContext>>,
133 project_root: FileSystemPath,
134 entry: ResolvedVc<Box<dyn Module>>,
135 page: AppPage,
136 next_config: Vc<NextConfig>,
137) -> Result<Vc<Box<dyn Module>>> {
138 const INNER: &str = "INNER_PAGE_ENTRY";
139 let mut cache_handler_imports = String::new();
140 let mut cache_handler_registration = String::new();
141 let mut incremental_cache_handler_import = None;
142 let mut cache_handler_inner_assets = fxindexmap! {};
143
144 let cache_handlers = next_config.cache_handlers_map().owned().await?;
145 for (index, (kind, handler_path)) in cache_handlers.iter().enumerate() {
146 let cache_handler_inner: RcStr = format!("INNER_CACHE_HANDLER_{index}").into();
147 let cache_handler_var = format!("cacheHandler{index}");
148 cache_handler_imports.push_str(&format!(
149 "import {cache_handler_var} from {};\n",
150 serde_json::to_string(&*cache_handler_inner)?
151 ));
152 cache_handler_registration.push_str(&format!(
153 " cacheHandlers.setCacheHandler({}, {cache_handler_var});\n",
154 serde_json::to_string(kind.as_str())?
155 ));
156
157 let cache_handler_module = asset_context
158 .process(
159 Vc::upcast(FileSource::new(project_root.join(handler_path)?)),
160 ReferenceType::Undefined,
161 )
162 .module()
163 .to_resolved()
164 .await?;
165 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
166 }
167
168 for cache_handler_path in next_config
169 .cache_handler(project_root.clone())
170 .await?
171 .into_iter()
172 {
173 let cache_handler_inner: RcStr = "INNER_INCREMENTAL_CACHE_HANDLER".into();
174 incremental_cache_handler_import = Some(cache_handler_inner.clone());
175 let cache_handler_module = asset_context
176 .process(
177 Vc::upcast(FileSource::new(cache_handler_path.clone())),
178 ReferenceType::Undefined,
179 )
180 .module()
181 .to_resolved()
182 .await?;
183 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
184 }
185
186 let source = load_next_js_template(
187 "edge-ssr-app.js",
188 project_root.clone(),
189 [("VAR_USERLAND", INNER), ("VAR_PAGE", &page.to_string())],
190 [
191 ("cacheHandlerImports", cache_handler_imports.as_str()),
192 (
193 "cacheHandlerRegistration",
194 cache_handler_registration.as_str(),
195 ),
196 ],
197 [(
198 "incrementalCacheHandler",
199 incremental_cache_handler_import.as_deref(),
200 )],
201 )
202 .await?;
203
204 let mut inner_assets = fxindexmap! {
205 INNER.into() => entry
206 };
207 inner_assets.extend(cache_handler_inner_assets);
208
209 let wrapped = asset_context
210 .process(
211 source,
212 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
213 )
214 .module();
215
216 Ok(wrap_edge_entry(
217 asset_context,
218 project_root,
219 wrapped,
220 app_function_name(&page).into(),
221 ))
222}