Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2210,13 +2210,13 @@ impl Endpoint for AppEndpoint {
async fn entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
let this = self.await?;
let app_entry = self.app_endpoint_entry().await?;
// The route's chunking heuristics from `experimental.turbopackChunkingHeuristics`. They are
// The route's chunking heuristics from `experimental.turbopackChunking`. They are
// attached to the route's entry chunk group.
let heuristics = this
.app_project
.project()
.next_config()
.chunking_heuristics()
.turbopack_chunking()
.await?
.entry_heuristics_for(&app_entry.pathname);
Ok(GraphEntries::from_chunk_groups(vec![
Expand Down
2 changes: 1 addition & 1 deletion crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1758,7 +1758,7 @@ impl Endpoint for PageEndpoint {
.pages_project
.project()
.next_config()
.chunking_heuristics()
.turbopack_chunking()
.await?
.entry_heuristics_for(&this.pathname);

Expand Down
12 changes: 8 additions & 4 deletions crates/next-api/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1598,7 +1598,7 @@ impl Project {
self: Vc<Self>,
) -> Result<Vc<Box<dyn ChunkingContext>>> {
let css_url_suffix = self.next_config().asset_suffix_path();
let chunking_heuristics = self.next_config().chunking_heuristics().await?;
let turbopack_chunking = self.next_config().turbopack_chunking().await?;
Ok(get_client_chunking_context(ClientChunkingContextOptions {
mode: self.next_mode(),
root_path: self.project_root_path().owned().await?,
Expand Down Expand Up @@ -1631,9 +1631,13 @@ impl Project {
cross_origin: self.next_config().cross_origin(),
chunk_loading_global: self.next_config().turbopack_chunk_loading_global(),
style_groups_algorithm: self.next_config().css_chunking().owned().await?,
chunking_first_page_load_priority: chunking_heuristics.first_page_load_priority,
chunking_priority_boost_percent: chunking_heuristics.priority_boost_percent,
chunking_request_cost: chunking_heuristics.request_cost,
chunking_first_page_load_priority: turbopack_chunking.first_page_load_priority,
chunking_priority_boost_percent: turbopack_chunking.priority_boost_percent,
chunking_request_cost: turbopack_chunking.request_cost,
chunking_min_chunk_size: turbopack_chunking.min_chunk_size,
chunking_max_chunk_count_per_group: turbopack_chunking.max_chunk_count_per_group,
chunking_max_merge_chunk_size: turbopack_chunking.max_merge_chunk_size,
chunking_min_component_chunk_size: turbopack_chunking.min_component_chunk_size,
generate_component_chunks: self.next_config().turbopack_generate_component_chunks(),
}))
}
Expand Down
16 changes: 12 additions & 4 deletions crates/next-core/src/next_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@ pub struct ClientChunkingContextOptions {
pub chunking_first_page_load_priority: Option<u32>,
pub chunking_priority_boost_percent: Option<u32>,
pub chunking_request_cost: Option<u64>,
pub chunking_min_chunk_size: Option<usize>,
pub chunking_max_chunk_count_per_group: Option<usize>,
pub chunking_max_merge_chunk_size: Option<usize>,
pub chunking_min_component_chunk_size: Option<usize>,
pub generate_component_chunks: Vc<bool>,
}

Expand Down Expand Up @@ -543,6 +547,10 @@ pub async fn get_client_chunking_context(
chunking_first_page_load_priority,
chunking_priority_boost_percent,
chunking_request_cost,
chunking_min_chunk_size,
chunking_max_chunk_count_per_group,
chunking_max_merge_chunk_size,
chunking_min_component_chunk_size,
generate_component_chunks,
} = options;

Expand Down Expand Up @@ -607,16 +615,16 @@ pub async fn get_client_chunking_context(
.chunking_config(
Vc::<EcmascriptChunkType>::default().to_resolved().await?,
ChunkingConfig {
min_chunk_size: 50_000,
max_chunk_count_per_group: 40,
max_merge_chunk_size: 200_000,
min_chunk_size: chunking_min_chunk_size.unwrap_or(50_000),
max_chunk_count_per_group: chunking_max_chunk_count_per_group.unwrap_or(40),
max_merge_chunk_size: chunking_max_merge_chunk_size.unwrap_or(200_000),
first_page_load_priority: chunking_first_page_load_priority,
priority_boost_percent: chunking_priority_boost_percent,
request_cost: chunking_request_cost,
// Generate component chunks alongside the merged chunk so that the browser
// runtime can fetch an already-cached one instead of the whole merged chunk.
generate_component_chunks: *generate_component_chunks.await?,
min_component_chunk_size: 20_000,
min_component_chunk_size: chunking_min_component_chunk_size.unwrap_or(20_000),
..Default::default()
},
)
Expand Down
59 changes: 45 additions & 14 deletions crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,7 @@ const DEFAULT_REQUEST_COST: f32 = 20_000.0;
/// Default `weightDistribution` for the graph algorithm.
const DEFAULT_WEIGHT_DISTRIBUTION: f32 = 0.1;

/// `experimental.chunkingHeuristics`: hints for Turbopack's production chunker.
/// `experimental.turbopackChunking`: hints for Turbopack's production chunker.
#[derive(
Clone,
Debug,
Expand All @@ -1149,7 +1149,7 @@ const DEFAULT_WEIGHT_DISTRIBUTION: f32 = 0.1;
Decode,
)]
#[serde(rename_all = "camelCase")]
pub struct ChunkingHeuristicsConfig {
pub struct TurbopackChunkingConfig {
/// A number between `0.0..=1.0`. Higher values weight the benefit of merging
/// chunks for a single page load more heavily. A site's bounce rate is a good
/// approximation if you don't have a better value.
Expand All @@ -1165,10 +1165,24 @@ pub struct ChunkingHeuristicsConfig {
/// bytes of code, default is 200 KB), used by the chunker to trade off request
/// count against preventing double-fetching.
request_cost: Option<u64>,
/// Avoid creating more than one chunk smaller than this size, in bytes (default `50000`).
/// Smaller chunks are merged into larger chunks.
min_chunk_size: Option<usize>,
/// Avoid creating more than this number of chunks per chunk group (default `40`).
max_chunk_count_per_group: Option<usize>,
/// Don't merge chunks bigger than this size, in bytes (default `200000`), with other
/// chunks. This keeps code in big chunks from being duplicated across multiple chunks.
max_merge_chunk_size: Option<usize>,
/// Emit each merged production chunk's constituent component chunks alongside it, so the
/// browser runtime can load only the chunks it doesn't already have (default `false`).
generate_component_chunks: Option<bool>,
/// Minimum size, in bytes (default `20000`), for a component chunk to be emitted on its
/// own when `generate_component_chunks` is enabled.
min_component_chunk_size: Option<usize>,
}

#[turbo_tasks::value]
pub struct ChunkingHeuristics {
pub struct TurbopackChunking {
/// First-page-load priority as an integer percentage (`0..=100`), or `None` if unset.
pub first_page_load_priority: Option<u32>,
/// Route-matching regexes for priority routes.
Expand All @@ -1178,9 +1192,19 @@ pub struct ChunkingHeuristics {
pub priority_boost_percent: Option<u32>,
/// Global estimated cost of an additional request, in bytes, or `None` if unset.
pub request_cost: Option<u64>,
}

impl ChunkingHeuristics {
/// Override for the client JS `min_chunk_size`, or `None` to use the default.
pub min_chunk_size: Option<usize>,
/// Override for the client JS `max_chunk_count_per_group`, or `None` to use the default.
pub max_chunk_count_per_group: Option<usize>,
/// Override for the client JS `max_merge_chunk_size`, or `None` to use the default.
pub max_merge_chunk_size: Option<usize>,
/// Override for the client JS `min_component_chunk_size`, or `None` to use the default.
pub min_component_chunk_size: Option<usize>,
/// Whether to emit component chunks alongside merged chunks (default `false`).
pub generate_component_chunks: bool,
}

impl TurbopackChunking {
/// Compute the [`EntryHeuristics`] for a route `pathname` by matching it against the configured
/// priority-route regexes.
pub fn entry_heuristics_for(&self, pathname: &str) -> EntryHeuristics {
Expand All @@ -1206,7 +1230,7 @@ fn parse_route_regexes(patterns: &[RegexComponents]) -> Result<Vec<EsRegex>> {
.cloned()
.map(|pattern| {
EsRegex::try_from(pattern)
.context("Invalid route pattern in `experimental.turbopackChunkingHeuristics`")
.context("Invalid route pattern in `experimental.turbopackChunking`")
})
.collect()
}
Expand Down Expand Up @@ -1300,8 +1324,7 @@ pub struct ExperimentalConfig {
/// CSS chunking strategy. See [`CssChunkingConfig`] for the accepted shapes.
css_chunking: Option<CssChunkingConfig>,

/// Traffic-shape hints for the production chunker. See [`ChunkingHeuristicsConfig`].
turbopack_chunking_heuristics: Option<ChunkingHeuristicsConfig>,
turbopack_chunking: Option<TurbopackChunkingConfig>,

// ---
// UNSUPPORTED
Expand Down Expand Up @@ -1370,7 +1393,6 @@ pub struct ExperimentalConfig {
turbopack_input_source_maps: Option<bool>,
turbopack_module_fragments: Option<bool>,
turbopack_scope_hoisting: Option<bool>,
turbopack_generate_component_chunks: Option<bool>,
turbopack_shared_runtime: Option<bool>,
/// Custom URL prefix for Web Worker URLs (the entrypoint and the module
/// chunks loaded inside the worker) produced by
Expand Down Expand Up @@ -2099,14 +2121,14 @@ impl NextConfig {
}

#[turbo_tasks::function]
pub fn chunking_heuristics(&self) -> Result<Vc<ChunkingHeuristics>> {
let config = self.experimental.turbopack_chunking_heuristics.as_ref();
pub fn turbopack_chunking(&self) -> Result<Vc<TurbopackChunking>> {
let config = self.experimental.turbopack_chunking.as_ref();
let priority_routes = parse_route_regexes(
config
.and_then(|c| c.priority_routes.as_deref())
.unwrap_or_default(),
)?;
Ok(ChunkingHeuristics {
Ok(TurbopackChunking {
first_page_load_priority: config
.and_then(|c| c.first_page_load_priority)
.map(|priority| (priority.clamp(0.0, 1.0) * 100.0).round() as u32),
Expand All @@ -2115,6 +2137,13 @@ impl NextConfig {
.and_then(|c| c.priority_boost)
.map(|boost| (boost.max(0.0) * 100.0).round() as u32),
request_cost: config.and_then(|c| c.request_cost),
min_chunk_size: config.and_then(|c| c.min_chunk_size),
max_chunk_count_per_group: config.and_then(|c| c.max_chunk_count_per_group),
max_merge_chunk_size: config.and_then(|c| c.max_merge_chunk_size),
min_component_chunk_size: config.and_then(|c| c.min_component_chunk_size),
generate_component_chunks: config
.and_then(|c| c.generate_component_chunks)
.unwrap_or(false),
}
.cell())
}
Expand Down Expand Up @@ -2521,7 +2550,9 @@ impl NextConfig {
pub fn turbopack_generate_component_chunks(&self) -> Vc<bool> {
Vc::cell(
self.experimental
.turbopack_generate_component_chunks
.turbopack_chunking
.as_ref()
.and_then(|c| c.generate_component_chunks)
.unwrap_or(false),
)
}
Expand Down
4 changes: 3 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -1460,5 +1460,7 @@
"1459": "[Server HMR] unreachable: unknown HMR instruction type %s",
"1460": "[Server HMR] unreachable: unexpected update instruction type %s",
"1461": "Expected segment cache to have data for stage '%s'",
"1462": "Unexpected chunk emitted in Before stage"
"1462": "Unexpected chunk emitted in Before stage",
"1463": "\\`experimental.turbopackGenerateComponentChunks\\` has been moved to \\`experimental.turbopackChunking.generateComponentChunks\\`. Please update your next.config.js file accordingly.",
"1464": "\\`experimental.turbopackChunkingHeuristics\\` has been renamed to \\`experimental.turbopackChunking\\`. Please update your next.config.js file accordingly."
}
15 changes: 7 additions & 8 deletions packages/next/src/build/swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1037,21 +1037,20 @@ function bindingToApi(
nextConfigSerializable.turbopack = turbopack
}

// Serialize `experimental.turbopackChunkingHeuristics` route patterns: convert each RegExp to
// Serialize `experimental.turbopackChunking` route patterns: convert each RegExp to
// {source, flags} since RegExp objects are not JSON-serializable.
const chunkingHeuristics =
nextConfigSerializable.experimental?.turbopackChunkingHeuristics
if (chunkingHeuristics) {
const chunkingConfig =
nextConfigSerializable.experimental?.turbopackChunking
if (chunkingConfig) {
const regexComponents = (regex: RegExp) => ({
source: regex.source,
flags: regex.flags,
})
nextConfigSerializable.experimental = {
...nextConfigSerializable.experimental,
turbopackChunkingHeuristics: {
...chunkingHeuristics,
priorityRoutes:
chunkingHeuristics.priorityRoutes?.map(regexComponents),
turbopackChunking: {
...chunkingConfig,
priorityRoutes: chunkingConfig.priorityRoutes?.map(regexComponents),
},
}
}
Expand Down
Loading
Loading