Skip to content

Commit 618263d

Browse files
authored
Turbopack: layout segment optimization for Pages (#74815)
Closes PACK-3715 This also required some changes to the dev loader, as since bc05ce9, app and document are used from the page bundle itself, and not from the app and document. ~~`_app.js` and `_document.js` are chunked first before the page (this result is then cached and reused across pages).~~ ~~Here, `_document.js` which includes lodash is only chunked once as part of `page3.js` and the other two reuse that.~~ ![Bildschirmfoto 2025-01-13 um 12 03 16](https://github.com/user-attachments/assets/a1149bb3-053a-44dd-a233-47fa4f456a83)
1 parent d832662 commit 618263d

File tree

5 files changed

+221
-82
lines changed

5 files changed

+221
-82
lines changed

crates/next-api/src/pages.rs

+144-45
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use tracing::Instrument;
3131
use turbo_rcstr::RcStr;
3232
use turbo_tasks::{
3333
fxindexmap, trace::TraceRawVcs, Completion, FxIndexMap, NonLocalValue, ResolvedVc, TaskInput,
34-
Value, Vc,
34+
Value, ValueToString, Vc,
3535
};
3636
use turbo_tasks_fs::{
3737
self, File, FileContent, FileSystem, FileSystemPath, FileSystemPathOption, VirtualFileSystem,
@@ -46,15 +46,15 @@ use turbopack_core::{
4646
asset::AssetContent,
4747
chunk::{
4848
availability_info::AvailabilityInfo, ChunkGroupResult, ChunkingContext, ChunkingContextExt,
49-
EntryChunkGroupResult, EvaluatableAsset, EvaluatableAssets,
49+
EvaluatableAsset, EvaluatableAssets,
5050
},
5151
context::AssetContext,
5252
file_source::FileSource,
5353
ident::AssetIdent,
5454
module::Module,
5555
module_graph::{
5656
chunk_group_info::{ChunkGroup, ChunkGroupEntry},
57-
GraphEntries, ModuleGraph,
57+
GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
5858
},
5959
output::{OptionOutputAsset, OutputAsset, OutputAssets},
6060
reference_type::{EcmaScriptModulesReferenceSubType, EntryReferenceSubType, ReferenceType},
@@ -772,7 +772,45 @@ impl PageEndpoint {
772772
let this = self.await?;
773773
let project = this.pages_project.project();
774774
let evaluatable_assets = self.client_evaluatable_assets();
775-
Ok(project.module_graph_for_entries(evaluatable_assets))
775+
Ok(project.module_graph_for_modules(evaluatable_assets))
776+
}
777+
778+
#[turbo_tasks::function]
779+
async fn ssr_module_graph(self: Vc<Self>) -> Result<Vc<ModuleGraph>> {
780+
let this = self.await?;
781+
let project = this.pages_project.project();
782+
783+
if *project.per_page_module_graph().await? {
784+
let ssr_chunk_module = self.internal_ssr_chunk_module().await?;
785+
// Implements layout segment optimization to compute a graph "chain" for document, app,
786+
// page
787+
let mut graphs = vec![];
788+
let mut visited_modules = VisitedModules::empty();
789+
for module in [
790+
ssr_chunk_module.document_module,
791+
ssr_chunk_module.app_module,
792+
]
793+
.into_iter()
794+
.flatten()
795+
{
796+
let graph = SingleModuleGraph::new_with_entries_visited_intern(
797+
vec![ChunkGroupEntry::Shared(module)],
798+
visited_modules,
799+
);
800+
graphs.push(graph);
801+
visited_modules = visited_modules.concatenate(graph);
802+
}
803+
804+
let graph = SingleModuleGraph::new_with_entries_visited_intern(
805+
vec![ChunkGroupEntry::Entry(vec![ssr_chunk_module.ssr_module])],
806+
visited_modules,
807+
);
808+
graphs.push(graph);
809+
810+
Ok(ModuleGraph::from_graphs(graphs))
811+
} else {
812+
Ok(*project.whole_app_module_graphs().await?.full)
813+
}
776814
}
777815

778816
#[turbo_tasks::function]
@@ -852,10 +890,8 @@ impl PageEndpoint {
852890
.module();
853891

854892
let config = parse_config_from_source(ssr_module, NextRuntime::default()).await?;
855-
let is_edge = matches!(config.runtime, NextRuntime::Edge);
856-
857-
let ssr_module = if is_edge {
858-
create_page_ssr_entry_module(
893+
Ok(if config.runtime == NextRuntime::Edge {
894+
let modules = create_page_ssr_entry_module(
859895
*this.pathname,
860896
reference_type,
861897
project_root,
@@ -866,15 +902,28 @@ impl PageEndpoint {
866902
config.runtime,
867903
this.pages_project.project().next_config(),
868904
)
905+
.await?;
906+
907+
InternalSsrChunkModule {
908+
ssr_module: modules.ssr_module,
909+
app_module: modules.app_module,
910+
document_module: modules.document_module,
911+
runtime: config.runtime,
912+
}
869913
} else {
870914
let pathname = &**this.pathname.await?;
871915

872916
// `/_app` and `/_document` never get rendered directly so they don't need to be
873917
// wrapped in the route module.
874918
if pathname == "/_app" || pathname == "/_document" {
875-
ssr_module
919+
InternalSsrChunkModule {
920+
ssr_module: ssr_module.to_resolved().await?,
921+
app_module: None,
922+
document_module: None,
923+
runtime: config.runtime,
924+
}
876925
} else {
877-
create_page_ssr_entry_module(
926+
let modules = create_page_ssr_entry_module(
878927
*this.pathname,
879928
reference_type,
880929
project_root,
@@ -885,12 +934,14 @@ impl PageEndpoint {
885934
config.runtime,
886935
this.pages_project.project().next_config(),
887936
)
937+
.await?;
938+
InternalSsrChunkModule {
939+
ssr_module: modules.ssr_module,
940+
app_module: modules.app_module,
941+
document_module: modules.document_module,
942+
runtime: config.runtime,
943+
}
888944
}
889-
};
890-
891-
Ok(InternalSsrChunkModule {
892-
ssr_module: ssr_module.to_resolved().await?,
893-
runtime: config.runtime,
894945
}
895946
.cell())
896947
}
@@ -900,7 +951,7 @@ impl PageEndpoint {
900951
self: Vc<Self>,
901952
ty: SsrChunkType,
902953
node_path: Vc<FileSystemPath>,
903-
chunking_context: Vc<NodeJsChunkingContext>,
954+
node_chunking_context: Vc<NodeJsChunkingContext>,
904955
edge_chunking_context: Vc<Box<dyn ChunkingContext>>,
905956
runtime_entries: Vc<EvaluatableAssets>,
906957
edge_runtime_entries: Vc<EvaluatableAssets>,
@@ -910,14 +961,16 @@ impl PageEndpoint {
910961

911962
let InternalSsrChunkModule {
912963
ssr_module,
964+
app_module,
965+
document_module,
913966
runtime,
914967
} = *self.internal_ssr_chunk_module().await?;
915968

916969
let project = this.pages_project.project();
917970
// The SSR and Client Graphs are not connected in Pages Router.
918971
// We are only interested in get_next_dynamic_imports_for_endpoint at the
919972
// moment, which only needs the client graph anyway.
920-
let module_graph = project.module_graph(*ssr_module);
973+
let ssr_module_graph = self.ssr_module_graph();
921974

922975
let next_dynamic_imports = if let PageEndpointType::Html = this.ty {
923976
let client_availability_info = self.client_chunks().await?.availability_info;
@@ -952,6 +1005,40 @@ impl PageEndpoint {
9521005
DynamicImportedChunks::default().resolved_cell()
9531006
};
9541007

1008+
let chunking_context: Vc<Box<dyn ChunkingContext>> = match runtime {
1009+
NextRuntime::NodeJs => Vc::upcast(node_chunking_context),
1010+
NextRuntime::Edge => Vc::upcast(edge_chunking_context),
1011+
};
1012+
1013+
let mut current_chunks = OutputAssets::empty();
1014+
let mut current_availability_info = AvailabilityInfo::Root;
1015+
for layout in [document_module, app_module].iter().flatten().copied() {
1016+
let span = tracing::trace_span!(
1017+
"layout segment",
1018+
name = display(layout.ident().to_string().await?)
1019+
);
1020+
async {
1021+
let chunk_group = chunking_context
1022+
.chunk_group(
1023+
layout.ident(),
1024+
ChunkGroup::Shared(layout),
1025+
ssr_module_graph,
1026+
Value::new(current_availability_info),
1027+
)
1028+
.await?;
1029+
1030+
current_chunks = current_chunks
1031+
.concatenate(*chunk_group.assets)
1032+
.resolve()
1033+
.await?;
1034+
current_availability_info = chunk_group.availability_info;
1035+
1036+
anyhow::Ok(())
1037+
}
1038+
.instrument(span)
1039+
.await?;
1040+
}
1041+
9551042
let ssr_module_evaluatable = ResolvedVc::try_sidecast(ssr_module)
9561043
.context("could not process page loader entry module")?;
9571044
let is_edge = matches!(runtime, NextRuntime::Edge);
@@ -962,18 +1049,15 @@ impl PageEndpoint {
9621049
.map(|m| ResolvedVc::upcast(*m))
9631050
.chain(std::iter::once(ResolvedVc::upcast(ssr_module_evaluatable)));
9641051

965-
let edge_files = edge_chunking_context
966-
.evaluated_chunk_group_assets(
967-
ssr_module.ident(),
968-
ChunkGroup::Entry(evaluatable_assets.collect()),
969-
module_graph,
970-
Value::new(AvailabilityInfo::Root),
971-
)
972-
.to_resolved()
973-
.await?;
1052+
let edge_files = edge_chunking_context.evaluated_chunk_group_assets(
1053+
ssr_module.ident(),
1054+
ChunkGroup::Entry(evaluatable_assets.collect()),
1055+
ssr_module_graph,
1056+
Value::new(current_availability_info),
1057+
);
9741058

9751059
Ok(SsrChunk::Edge {
976-
files: edge_files,
1060+
files: current_chunks.concatenate(edge_files).to_resolved().await?,
9771061
dynamic_import_entries,
9781062
}
9791063
.cell())
@@ -984,17 +1068,15 @@ impl PageEndpoint {
9841068

9851069
let ssr_entry_chunk_path_string: RcStr = format!("pages{asset_path}").into();
9861070
let ssr_entry_chunk_path = node_path.join(ssr_entry_chunk_path_string);
987-
let EntryChunkGroupResult {
988-
asset: ssr_entry_chunk,
989-
..
990-
} = *chunking_context
991-
.entry_chunk_group(
1071+
let ssr_entry_chunk = node_chunking_context
1072+
.entry_chunk_group_asset(
9921073
ssr_entry_chunk_path,
9931074
runtime_entries.with_entry(*ssr_module_evaluatable),
994-
module_graph,
995-
OutputAssets::empty(),
996-
Value::new(AvailabilityInfo::Root),
1075+
ssr_module_graph,
1076+
current_chunks,
1077+
Value::new(current_availability_info),
9971078
)
1079+
.to_resolved()
9981080
.await?;
9991081

10001082
let server_asset_trace_file = if this
@@ -1364,6 +1446,8 @@ impl PageEndpoint {
13641446
#[turbo_tasks::value]
13651447
pub struct InternalSsrChunkModule {
13661448
pub ssr_module: ResolvedVc<Box<dyn Module>>,
1449+
pub app_module: Option<ResolvedVc<Box<dyn Module>>>,
1450+
pub document_module: Option<ResolvedVc<Box<dyn Module>>>,
13671451
pub runtime: NextRuntime,
13681452
}
13691453

@@ -1467,17 +1551,32 @@ impl Endpoint for PageEndpoint {
14671551
let this = self.await?;
14681552

14691553
let ssr_chunk_module = self.internal_ssr_chunk_module().await?;
1470-
let mut modules = vec![ChunkGroupEntry::Entry(vec![ssr_chunk_module.ssr_module])];
14711554

1472-
if let PageEndpointType::Html = this.ty {
1473-
modules.push(ChunkGroupEntry::Entry(
1474-
self.client_evaluatable_assets()
1475-
.await?
1476-
.iter()
1477-
.map(|m| ResolvedVc::upcast(*m))
1478-
.collect(),
1479-
));
1480-
}
1555+
let shared_entries = [
1556+
ssr_chunk_module.document_module,
1557+
ssr_chunk_module.app_module,
1558+
];
1559+
1560+
let modules = shared_entries
1561+
.into_iter()
1562+
.flatten()
1563+
.map(ChunkGroupEntry::Shared)
1564+
.chain(std::iter::once(ChunkGroupEntry::Entry(vec![
1565+
ssr_chunk_module.ssr_module,
1566+
])))
1567+
.chain(if this.ty == PageEndpointType::Html {
1568+
Some(ChunkGroupEntry::Entry(
1569+
self.client_evaluatable_assets()
1570+
.await?
1571+
.iter()
1572+
.map(|m| ResolvedVc::upcast(*m))
1573+
.collect(),
1574+
))
1575+
.into_iter()
1576+
} else {
1577+
None.into_iter()
1578+
})
1579+
.collect::<Vec<_>>();
14811580

14821581
Ok(Vc::cell(modules))
14831582
}

crates/next-api/src/project.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ impl Project {
895895
}
896896

897897
#[turbo_tasks::function]
898-
pub async fn module_graph_for_entries(
898+
pub async fn module_graph_for_modules(
899899
self: Vc<Self>,
900900
evaluatable_assets: Vc<EvaluatableAssets>,
901901
) -> Result<Vc<ModuleGraph>> {
@@ -912,6 +912,18 @@ impl Project {
912912
})
913913
}
914914

915+
#[turbo_tasks::function]
916+
pub async fn module_graph_for_entries(
917+
self: Vc<Self>,
918+
entries: Vc<GraphEntries>,
919+
) -> Result<Vc<ModuleGraph>> {
920+
Ok(if *self.per_page_module_graph().await? {
921+
ModuleGraph::from_modules(entries)
922+
} else {
923+
*self.whole_app_module_graphs().await?.full
924+
})
925+
}
926+
915927
#[turbo_tasks::function]
916928
pub async fn whole_app_module_graphs(self: ResolvedVc<Self>) -> Result<Vc<ModuleGraphs>> {
917929
async move {

0 commit comments

Comments
 (0)