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