next_core/next_app/
app_route_entry.rs

1use anyhow::Result;
2use turbo_rcstr::{RcStr, rcstr};
3use turbo_tasks::{ResolvedVc, 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, app_function_name, load_next_js_template},
20};
21
22/// Computes the entry for a Next.js app route.
23/// # Arguments
24///
25/// * `original_segment_config` - A next segment config to be specified explicitly for the given
26///   source.
27/// For some cases `source` may not be the original but the handler (dynamic
28/// metadata) which will lose segment config.
29#[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: 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.owned().await?;
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().owned().await?;
59
60    let inner = rcstr!("INNER_APP_ROUTE");
61
62    let output_type: &str = next_config
63        .await?
64        .output
65        .as_ref()
66        .map(|o| match o {
67            OutputType::Standalone => "\"standalone\"",
68            OutputType::Export => "\"export\"",
69        })
70        .unwrap_or("\"\"");
71
72    // Load the file from the next.js codebase.
73    let virtual_source = load_next_js_template(
74        "app-route.js",
75        project_root.clone(),
76        &[
77            ("VAR_DEFINITION_PAGE", &*page.to_string()),
78            ("VAR_DEFINITION_PATHNAME", &pathname),
79            ("VAR_DEFINITION_FILENAME", path.file_stem().unwrap()),
80            // TODO(alexkirsz) Is this necessary?
81            ("VAR_DEFINITION_BUNDLE_PATH", ""),
82            ("VAR_RESOLVED_PAGE_PATH", &path.value_to_string().await?),
83            ("VAR_USERLAND", &inner),
84        ],
85        &[("nextConfigOutput", output_type)],
86        &[],
87    )
88    .await?;
89
90    let userland_module = module_asset_context
91        .process(
92            source,
93            ReferenceType::Entry(EntryReferenceSubType::AppRoute),
94        )
95        .module()
96        .to_resolved()
97        .await?;
98
99    let inner_assets = fxindexmap! {
100        inner => userland_module
101    };
102
103    let mut rsc_entry = module_asset_context
104        .process(
105            Vc::upcast(virtual_source),
106            ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
107        )
108        .module();
109
110    if is_edge {
111        rsc_entry = wrap_edge_route(
112            Vc::upcast(module_asset_context),
113            project_root,
114            rsc_entry,
115            page,
116            next_config,
117        );
118    }
119
120    Ok(AppEntry {
121        pathname,
122        original_name,
123        rsc_entry: rsc_entry.to_resolved().await?,
124        config: config.to_resolved().await?,
125    }
126    .cell())
127}
128
129#[turbo_tasks::function]
130async fn wrap_edge_route(
131    asset_context: Vc<Box<dyn AssetContext>>,
132    project_root: FileSystemPath,
133    entry: ResolvedVc<Box<dyn Module>>,
134    page: AppPage,
135    next_config: Vc<NextConfig>,
136) -> Result<Vc<Box<dyn Module>>> {
137    let inner = rcstr!("INNER_ROUTE_ENTRY");
138
139    let next_config = &*next_config.await?;
140
141    let source = load_next_js_template(
142        "edge-app-route.js",
143        project_root.clone(),
144        &[("VAR_USERLAND", &*inner), ("VAR_PAGE", &page.to_string())],
145        &[("nextConfig", &*serde_json::to_string(next_config)?)],
146        &[],
147    )
148    .await?;
149
150    let inner_assets = fxindexmap! {
151        inner => entry
152    };
153
154    let wrapped = asset_context
155        .process(
156            Vc::upcast(source),
157            ReferenceType::Internal(ResolvedVc::cell(inner_assets)),
158        )
159        .module();
160
161    Ok(wrap_edge_entry(
162        asset_context,
163        project_root.clone(),
164        wrapped,
165        app_function_name(&page).into(),
166    ))
167}