Skip to main content

next_core/next_app/
app_page_entry.rs

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