1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, ValueToStringRef, Vc, fxindexmap};
4use turbo_tasks_fs::FileSystemPath;
5use turbopack::ModuleAssetContext;
6use turbopack_core::{
7 context::AssetContext,
8 file_source::FileSource,
9 module::Module,
10 reference_type::{EntryReferenceSubType, ReferenceType},
11 source::Source,
12};
13
14use crate::{
15 next_app::{AppEntry, AppPage, AppPath},
16 next_config::{NextConfig, OutputType},
17 next_edge::entry::wrap_edge_entry,
18 parse_segment_config_from_source,
19 segment_config::{NextSegmentConfig, ParseSegmentMode},
20 util::{NextRuntime, app_function_name, load_next_js_template},
21};
22
23#[turbo_tasks::function]
31pub async fn get_app_route_entry(
32 nodejs_context: Vc<ModuleAssetContext>,
33 edge_context: Vc<ModuleAssetContext>,
34 source: Vc<Box<dyn Source>>,
35 page: AppPage,
36 project_root: FileSystemPath,
37 original_segment_config: Option<Vc<NextSegmentConfig>>,
38 next_config: Vc<NextConfig>,
39) -> Result<Vc<AppEntry>> {
40 let segment_from_source = parse_segment_config_from_source(source, ParseSegmentMode::App);
41 let config = if let Some(original_segment_config) = original_segment_config {
42 let mut segment_config = segment_from_source.owned().await?;
43 segment_config.apply_parent_config(&*original_segment_config.await?);
44 segment_config.cell()
45 } else {
46 segment_from_source
47 };
48
49 let is_edge = matches!(config.await?.runtime, Some(NextRuntime::Edge));
50 let module_asset_context = if is_edge {
51 edge_context
52 } else {
53 nodejs_context
54 };
55
56 let original_name: RcStr = page.to_string().into();
57 let pathname: RcStr = AppPath::from(page.clone()).to_string().into();
58
59 let ident = source.ident().await?;
60 let path = &ident.path;
61
62 let inner = rcstr!("INNER_APP_ROUTE");
63
64 let output_type: &str = next_config
65 .output()
66 .await?
67 .as_ref()
68 .map(|o| match o {
69 OutputType::Standalone => "\"standalone\"",
70 OutputType::Export => "\"export\"",
71 })
72 .unwrap_or("\"\"");
73
74 let virtual_source = load_next_js_template(
76 "app-route.js",
77 project_root.clone(),
78 [
79 ("VAR_DEFINITION_PAGE", &*page.to_string()),
80 ("VAR_DEFINITION_PATHNAME", &pathname),
81 ("VAR_DEFINITION_FILENAME", path.file_stem().unwrap()),
82 ("VAR_DEFINITION_BUNDLE_PATH", ""),
84 ("VAR_RESOLVED_PAGE_PATH", &path.to_string_ref().await?),
85 ("VAR_USERLAND", &inner),
86 ],
87 [("nextConfigOutput", output_type)],
88 [],
89 )
90 .await?;
91
92 let userland_module = module_asset_context
93 .process(
94 source,
95 ReferenceType::Entry(EntryReferenceSubType::AppRoute),
96 )
97 .module()
98 .to_resolved()
99 .await?;
100
101 let inner_assets = fxindexmap! {
102 inner => userland_module
103 };
104
105 let mut rsc_entry = module_asset_context
106 .process(
107 virtual_source,
108 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
109 )
110 .module();
111
112 if is_edge {
113 rsc_entry = wrap_edge_route(
114 Vc::upcast(module_asset_context),
115 project_root,
116 rsc_entry,
117 page,
118 next_config,
119 );
120 }
121
122 Ok(AppEntry {
123 pathname,
124 original_name,
125 rsc_entry: rsc_entry.to_resolved().await?,
126 config: config.to_resolved().await?,
127 }
128 .cell())
129}
130
131#[turbo_tasks::function]
132async fn wrap_edge_route(
133 asset_context: Vc<Box<dyn AssetContext>>,
134 project_root: FileSystemPath,
135 entry: ResolvedVc<Box<dyn Module>>,
136 page: AppPage,
137 next_config: Vc<NextConfig>,
138) -> Result<Vc<Box<dyn Module>>> {
139 let inner = rcstr!("INNER_ROUTE_ENTRY");
140 let mut cache_handler_imports = String::new();
141 let mut cache_handler_map_entries = String::new();
142 let mut incremental_cache_handler_import = None;
143 let mut cache_handler_inner_assets = fxindexmap! {};
144
145 let cache_handlers = next_config.cache_handlers_map().owned().await?;
146 for (index, (kind, handler_path)) in cache_handlers.iter().enumerate() {
147 let cache_handler_inner: RcStr = format!("INNER_CACHE_HANDLER_{index}").into();
148 let cache_handler_var = format!("cacheHandler{index}");
149 cache_handler_imports.push_str(&format!(
150 "import {cache_handler_var} from {};\n",
151 serde_json::to_string(&*cache_handler_inner)?
152 ));
153 cache_handler_map_entries.push_str(&format!(
154 " {}: {cache_handler_var},\n",
155 serde_json::to_string(kind.as_str())?
156 ));
157
158 let cache_handler_module = asset_context
159 .process(
160 Vc::upcast(FileSource::new(project_root.join(handler_path)?)),
161 ReferenceType::Undefined,
162 )
163 .module()
164 .to_resolved()
165 .await?;
166 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
167 }
168
169 for cache_handler_path in next_config
170 .cache_handler(project_root.clone())
171 .await?
172 .into_iter()
173 {
174 let cache_handler_inner: RcStr = "INNER_INCREMENTAL_CACHE_HANDLER".into();
175 incremental_cache_handler_import = Some(cache_handler_inner.clone());
176 let cache_handler_module = asset_context
177 .process(
178 Vc::upcast(FileSource::new(cache_handler_path.clone())),
179 ReferenceType::Undefined,
180 )
181 .module()
182 .to_resolved()
183 .await?;
184 cache_handler_inner_assets.insert(cache_handler_inner, cache_handler_module);
185 }
186
187 let source = load_next_js_template(
188 "edge-app-route.js",
189 project_root.clone(),
190 [("VAR_USERLAND", &*inner), ("VAR_PAGE", &page.to_string())],
191 [
192 ("cacheHandlerImports", cache_handler_imports.as_str()),
193 (
194 "edgeCacheHandlersRegistration",
195 cache_handler_map_entries.as_str(),
196 ),
197 ],
198 [(
199 "incrementalCacheHandler",
200 incremental_cache_handler_import.as_deref(),
201 )],
202 )
203 .await?;
204
205 let mut inner_assets = fxindexmap! {
206 inner => entry
207 };
208 inner_assets.extend(cache_handler_inner_assets);
209
210 let wrapped = asset_context
211 .process(
212 source,
213 ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
214 )
215 .module();
216
217 Ok(wrap_edge_entry(
218 asset_context,
219 project_root.clone(),
220 wrapped,
221 app_function_name(&page).into(),
222 ))
223}