Skip to main content

next_core/next_app/
app_route_entry.rs

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/// Computes the entry for a Next.js app route.
24/// # Arguments
25///
26/// * `original_segment_config` - A next segment config to be specified explicitly for the given
27///   source.
28/// For some cases `source` may not be the original but the handler (dynamic
29/// metadata) which will lose segment config.
30#[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    // Load the file from the next.js codebase.
75    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            // TODO(alexkirsz) Is this necessary?
83            ("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}