next_core/next_app/
app_route_entry.rs1use anyhow::Result;
2use turbo_rcstr::RcStr;
3use turbo_tasks::{ResolvedVc, Value, ValueToString, Vc, fxindexmap};
4use turbo_tasks_fs::FileSystemPath;
5use turbopack::ModuleAssetContext;
6use turbopack_core::{
7 context::AssetContext,
8 module::Module,
9 reference_type::{EntryReferenceSubType, ReferenceType},
10 source::Source,
11};
12
13use crate::{
14 app_segment_config::NextSegmentConfig,
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 util::{NextRuntime, load_next_js_template},
20};
21
22#[turbo_tasks::function]
30pub async fn get_app_route_entry(
31 nodejs_context: Vc<ModuleAssetContext>,
32 edge_context: Vc<ModuleAssetContext>,
33 source: Vc<Box<dyn Source>>,
34 page: AppPage,
35 project_root: Vc<FileSystemPath>,
36 original_segment_config: Option<Vc<NextSegmentConfig>>,
37 next_config: Vc<NextConfig>,
38) -> Result<Vc<AppEntry>> {
39 let segment_from_source = parse_segment_config_from_source(source);
40 let config = if let Some(original_segment_config) = original_segment_config {
41 let mut segment_config = (*segment_from_source.await?).clone();
42 segment_config.apply_parent_config(&*original_segment_config.await?);
43 segment_config.into()
44 } else {
45 segment_from_source
46 };
47
48 let is_edge = matches!(config.await?.runtime, Some(NextRuntime::Edge));
49 let module_asset_context = if is_edge {
50 edge_context
51 } else {
52 nodejs_context
53 };
54
55 let original_name: RcStr = page.to_string().into();
56 let pathname: RcStr = AppPath::from(page.clone()).to_string().into();
57
58 let path = source.ident().path();
59
60 const INNER: &str = "INNER_APP_ROUTE";
61
62 let output_type: RcStr = next_config
63 .await?
64 .output
65 .as_ref()
66 .map(|o| match o {
67 OutputType::Standalone => "\"standalone\"".to_string(),
68 OutputType::Export => "\"export\"".to_string(),
69 })
70 .map(RcStr::from)
71 .unwrap_or_else(|| "\"\"".into());
72
73 let virtual_source = load_next_js_template(
75 "app-route.js",
76 project_root,
77 fxindexmap! {
78 "VAR_DEFINITION_PAGE" => page.to_string().into(),
79 "VAR_DEFINITION_PATHNAME" => pathname.clone(),
80 "VAR_DEFINITION_FILENAME" => path.file_stem().await?.as_ref().unwrap().as_str().into(),
81 "VAR_DEFINITION_BUNDLE_PATH" => "".to_string().into(),
83 "VAR_RESOLVED_PAGE_PATH" => path.to_string().owned().await?,
84 "VAR_USERLAND" => INNER.into(),
85 },
86 fxindexmap! {
87 "nextConfigOutput" => output_type
88 },
89 fxindexmap! {},
90 )
91 .await?;
92
93 let userland_module = module_asset_context
94 .process(
95 source,
96 Value::new(ReferenceType::Entry(EntryReferenceSubType::AppRoute)),
97 )
98 .module()
99 .to_resolved()
100 .await?;
101
102 let inner_assets = fxindexmap! {
103 INNER.into() => userland_module
104 };
105
106 let mut rsc_entry = module_asset_context
107 .process(
108 Vc::upcast(virtual_source),
109 Value::new(ReferenceType::Internal(ResolvedVc::cell(inner_assets))),
110 )
111 .module();
112
113 if is_edge {
114 rsc_entry = wrap_edge_route(
115 Vc::upcast(module_asset_context),
116 project_root,
117 rsc_entry,
118 page,
119 next_config,
120 );
121 }
122
123 Ok(AppEntry {
124 pathname,
125 original_name,
126 rsc_entry: rsc_entry.to_resolved().await?,
127 config: config.to_resolved().await?,
128 }
129 .cell())
130}
131
132#[turbo_tasks::function]
133async fn wrap_edge_route(
134 asset_context: Vc<Box<dyn AssetContext>>,
135 project_root: Vc<FileSystemPath>,
136 entry: ResolvedVc<Box<dyn Module>>,
137 page: AppPage,
138 next_config: Vc<NextConfig>,
139) -> Result<Vc<Box<dyn Module>>> {
140 const INNER: &str = "INNER_ROUTE_ENTRY";
141
142 let next_config = &*next_config.await?;
143
144 let source = load_next_js_template(
145 "edge-app-route.js",
146 project_root,
147 fxindexmap! {
148 "VAR_USERLAND" => INNER.into(),
149 "VAR_PAGE" => page.to_string().into(),
150 },
151 fxindexmap! {
152 "nextConfig" => serde_json::to_string(next_config)?.into(),
153 },
154 fxindexmap! {},
155 )
156 .await?;
157
158 let inner_assets = fxindexmap! {
159 INNER.into() => entry
160 };
161
162 let wrapped = asset_context
163 .process(
164 Vc::upcast(source),
165 Value::new(ReferenceType::Internal(ResolvedVc::cell(inner_assets))),
166 )
167 .module();
168
169 Ok(wrap_edge_entry(
170 asset_context,
171 project_root,
172 wrapped,
173 AppPath::from(page).to_string().into(),
174 ))
175}