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
1 change: 1 addition & 0 deletions .rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
edition = "2024"
max_width = 100

comment_width = 100
Expand Down
4 changes: 4 additions & 0 deletions crates/next-api/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,8 @@ impl AppEndpoint {

if emit_manifests == EmitManifests::Full {
let dynamic_import_entries = collect_next_dynamic_chunks(
*module_graphs.full,
*client_chunking_context,
next_dynamic_imports,
NextDynamicChunkAvailability::ClientReferences(
&*(client_references_chunks.await?),
Expand Down Expand Up @@ -1680,6 +1682,8 @@ impl AppEndpoint {
let loadable_manifest_output = if emit_manifests == EmitManifests::Full {
// create react-loadable-manifest for next/dynamic
let dynamic_import_entries = collect_next_dynamic_chunks(
*module_graphs.full,
*client_chunking_context,
next_dynamic_imports,
NextDynamicChunkAvailability::ClientReferences(
&*(client_references_chunks.await?),
Expand Down
57 changes: 22 additions & 35 deletions crates/next-api/src/dynamic_imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,24 @@ use turbo_tasks::{
debug::ValueDebugFormat, trace::TraceRawVcs,
};
use turbopack_core::{
chunk::{ChunkGroupResult, ChunkableModule},
chunk::{ChunkableModule, ChunkingContext, availability_info::AvailabilityInfo},
module::Module,
module_graph::ModuleGraphLayer,
module_graph::{ModuleGraph, ModuleGraphLayer},
output::{OutputAssetsReference, OutputAssetsWithReferenced},
};

use crate::module_graph::DynamicImportEntriesWithImporter;

pub(crate) enum NextDynamicChunkAvailability<'a> {
/// In App Router, the client references chunks contain the async loaders
/// In App Router, the client references
ClientReferences(&'a ClientReferencesChunks),
/// In Pages Router, the base page chunk group result
PageChunkGroup(&'a ChunkGroupResult),
/// In Pages Router, the base page chunk group
AvailabilityInfo(AvailabilityInfo),
}

/// Collects the chunk outputs for next/dynamic imports by looking up pre-computed
/// async loaders from the chunk group results.
///
/// This function no longer recomputes chunks - instead it looks up the async loader
/// outputs that were already computed by `make_chunk_group` when the parent chunk
/// groups were created. This ensures consistency between the manifest and the actual
/// chunks served at runtime.
pub(crate) async fn collect_next_dynamic_chunks(
module_graph: Vc<ModuleGraph>,
chunking_context: Vc<Box<dyn ChunkingContext>>,
dynamic_import_entries: ReadRef<DynamicImportEntriesWithImporter>,
chunking_availability: NextDynamicChunkAvailability<'_>,
) -> Result<ResolvedVc<DynamicImportedChunks>> {
Expand All @@ -62,36 +57,28 @@ pub(crate) async fn collect_next_dynamic_chunks(
.map(|(dynamic_entry, parent_client_reference)| async move {
let module = ResolvedVc::upcast::<Box<dyn ChunkableModule>>(*dynamic_entry);

// Look up the pre-computed async loader from the parent chunk group
let async_loader = match chunking_availability {
// This is the availability info for the parent chunk group, i.e. the client reference
// containing the next/dynamic imports
let availability_info = match chunking_availability {
NextDynamicChunkAvailability::ClientReferences(client_reference_chunks) => {
// For App Router: look up the chunk group for the parent client reference,
// then find the async loader for this dynamic entry
let parent_ref = parent_client_reference
.context("Parent client reference not found for next/dynamic import")?;
let chunk_group = client_reference_chunks
client_reference_chunks
.client_component_client_chunks
.get(&parent_ref)
.get(
&parent_client_reference.context(
"Parent client reference not found for next/dynamic import",
)?,
)
.context("Client reference chunk group not found for next/dynamic import")?
.await?;
// Copy the ResolvedVc out of the map to avoid lifetime issues
*chunk_group.async_loaders_by_module.get(&module).context(
"Dynamic entry not found in async loaders - this may indicate the dynamic \
import is not reachable from the client reference",
)?
.await?
.availability_info
}
NextDynamicChunkAvailability::PageChunkGroup(chunk_group) => {
// For Pages Router: look up directly in the page's chunk group
// Copy the ResolvedVc out of the map to avoid lifetime issues
*chunk_group.async_loaders_by_module.get(&module).context(
"Dynamic entry not found in async loaders - this may indicate the dynamic \
import is not reachable from the page entry",
)?
NextDynamicChunkAvailability::AvailabilityInfo(availability_info) => {
*availability_info
}
};

// Get the output assets from the async loader reference
// Upcast to OutputAssetsReference to call references()
let async_loader =
chunking_context.async_loader_chunk_item(*module, module_graph, availability_info);
let async_chunk_group = async_loader.references().to_resolved().await?;

Ok((*dynamic_entry, (*dynamic_entry, async_chunk_group)))
Expand Down
8 changes: 2 additions & 6 deletions crates/next-api/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ use turbo_tasks::{Completion, ResolvedVc, Vc};
use turbo_tasks_fs::{self, File, FileContent, FileSystemPath};
use turbopack_core::{
asset::AssetContent,
chunk::{
ChunkingContext, ChunkingContextExt, EntryChunkGroupResult,
availability_info::AvailabilityInfo,
},
chunk::{ChunkingContextExt, EntryChunkGroupResult, availability_info::AvailabilityInfo},
context::AssetContext,
module::Module,
module_graph::{
Expand Down Expand Up @@ -138,7 +135,7 @@ impl MiddlewareEndpoint {
};

let EntryChunkGroupResult { asset: chunk, .. } = *chunking_context
.entry_chunk_group(
.root_entry_chunk_group(
this.project
.node_root()
.await?
Expand All @@ -147,7 +144,6 @@ impl MiddlewareEndpoint {
module_graph,
OutputAssets::empty(),
OutputAssets::empty(),
AvailabilityInfo::root(),
)
.await?;
Ok(*chunk)
Expand Down
20 changes: 13 additions & 7 deletions crates/next-api/src/nft_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ impl OutputAsset for NftJsonAsset {
}
}

#[turbo_tasks::value(transparent)]
pub struct OutputSpecifier(Option<RcStr>);

fn get_output_specifier(
path_ref: &FileSystemPath,
ident_folder: &FileSystemPath,
Expand All @@ -97,9 +94,8 @@ fn get_output_specifier(
.get_relative_path_to(path_ref)
.unwrap());
}

// This should effectively be unreachable
bail!("NftJsonAsset: cannot handle filepath {path_ref}");
bail!("NftJsonAsset: cannot handle filepath '{path_ref}'");
}

/// Apply outputFileTracingIncludes patterns to find additional files
Expand Down Expand Up @@ -285,13 +281,23 @@ impl Asset for NftJsonAsset {
}
}

let specifier = get_output_specifier(
let specifier = match get_output_specifier(
&referenced_chunk_path,
&ident_folder,
&ident_folder_in_project_fs,
&output_root_ref,
&project_root_ref,
)?;
) {
Ok(specifier) => specifier,
Err(err) => {
return Err(err.context(format!(
"NftJsonAsset: cannot handle filepath '{chunk_path}' for \
{referenced_chunk:?} it is not under the output_root: \
'{output_root_ref}' or the project_root: '{project_root_ref}'",
chunk_path = referenced_chunk_path.value_to_string().await?
)));
}
};

result.insert(specifier);
}
Expand Down
29 changes: 17 additions & 12 deletions crates/next-api/src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ impl PageEndpoint {
let ssr_module_graph = self.ssr_module_graph();

let next_dynamic_imports = if let PageEndpointType::Html = this.ty {
let client_chunk_group = self.client_chunk_group().await?;
let client_availability_info = self.client_chunk_group().await?.availability_info;

let client_module_graph = self.client_module_graph();
let per_page_module_graph = *project.per_page_module_graph().await?;
Expand Down Expand Up @@ -966,21 +966,26 @@ impl PageEndpoint {
NextDynamicGraphs::new(client_module_graph, per_page_module_graph)
.get_next_dynamic_imports_for_endpoint(self.client_module())
.await?;
Some((next_dynamic_imports, client_chunk_group))
Some((next_dynamic_imports, client_availability_info))
} else {
None
};

let dynamic_import_entries =
if let Some((next_dynamic_imports, client_chunk_group)) = next_dynamic_imports {
collect_next_dynamic_chunks(
next_dynamic_imports,
NextDynamicChunkAvailability::PageChunkGroup(&client_chunk_group),
)
.await?
} else {
DynamicImportedChunks::default().resolved_cell()
};
let dynamic_import_entries = if let Some((
next_dynamic_imports,
client_availability_info,
)) = next_dynamic_imports
{
collect_next_dynamic_chunks(
self.client_module_graph(),
project.client_chunking_context(),
next_dynamic_imports,
NextDynamicChunkAvailability::AvailabilityInfo(client_availability_info),
)
.await?
} else {
DynamicImportedChunks::default().resolved_cell()
};

let chunking_context: Vc<Box<dyn ChunkingContext>> = match runtime {
NextRuntime::NodeJs => Vc::upcast(node_chunking_context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ pub async fn get_app_client_references_chunks(
let mut current_client_chunk_group = ChunkGroupResult {
assets: ResolvedVc::cell(vec![]),
referenced_assets: ResolvedVc::cell(vec![]),
references: ResolvedVc::cell(vec![]),
availability_info: client_availability_info,
async_loaders_by_module: FxIndexMap::default(),
}
.resolved_cell();
let mut current_ssr_chunk_group = ChunkGroupResult::empty_resolved();
Expand Down
4 changes: 2 additions & 2 deletions examples/reproduction-template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
},
"dependencies": {
"next": "canary",
"react": "19.2.3",
"react-dom": "19.2.3"
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@types/node": "^22",
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"registry": "https://registry.npmjs.org/"
}
},
"version": "16.2.0-canary.8"
"version": "16.2.0-canary.11"
}
32 changes: 16 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
"eslint-plugin-jsdoc": "48.0.4",
"eslint-plugin-mdx": "3.1.5",
"eslint-plugin-react": "7.37.0",
"eslint-plugin-react-hooks": "0.0.0-experimental-24d8716e-20260123",
"eslint-plugin-react-hooks": "0.0.0-experimental-10680271-20260126",
"event-stream": "4.0.1",
"execa": "2.0.3",
"expect": "29.7.0",
Expand Down Expand Up @@ -258,16 +258,16 @@
"pretty-ms": "7.0.0",
"random-seed": "0.3.0",
"react": "19.0.0",
"react-builtin": "npm:react@19.3.0-canary-24d8716e-20260123",
"react-builtin": "npm:react@19.3.0-canary-10680271-20260126",
"react-dom": "19.0.0",
"react-dom-builtin": "npm:react-dom@19.3.0-canary-24d8716e-20260123",
"react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-24d8716e-20260123",
"react-experimental-builtin": "npm:react@0.0.0-experimental-24d8716e-20260123",
"react-is-builtin": "npm:react-is@19.3.0-canary-24d8716e-20260123",
"react-server-dom-turbopack": "19.3.0-canary-24d8716e-20260123",
"react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-24d8716e-20260123",
"react-server-dom-webpack": "19.3.0-canary-24d8716e-20260123",
"react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-24d8716e-20260123",
"react-dom-builtin": "npm:react-dom@19.3.0-canary-10680271-20260126",
"react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-10680271-20260126",
"react-experimental-builtin": "npm:react@0.0.0-experimental-10680271-20260126",
"react-is-builtin": "npm:react-is@19.3.0-canary-10680271-20260126",
"react-server-dom-turbopack": "19.3.0-canary-10680271-20260126",
"react-server-dom-turbopack-experimental": "npm:react-server-dom-turbopack@0.0.0-experimental-10680271-20260126",
"react-server-dom-webpack": "19.3.0-canary-10680271-20260126",
"react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-10680271-20260126",
"react-ssr-prepass": "1.0.8",
"react-virtualized": "9.22.3",
"relay-compiler": "13.0.2",
Expand All @@ -277,8 +277,8 @@
"resolve-from": "5.0.0",
"sass": "1.54.0",
"satori": "0.15.2",
"scheduler-builtin": "npm:scheduler@0.28.0-canary-24d8716e-20260123",
"scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-24d8716e-20260123",
"scheduler-builtin": "npm:scheduler@0.28.0-canary-10680271-20260126",
"scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-10680271-20260126",
"seedrandom": "3.0.5",
"semver": "7.3.7",
"serve-handler": "6.1.6",
Expand Down Expand Up @@ -323,10 +323,10 @@
"@types/react-dom": "19.2.1",
"@types/retry": "0.12.0",
"jest-snapshot": "30.0.0-alpha.6",
"react": "19.3.0-canary-24d8716e-20260123",
"react-dom": "19.3.0-canary-24d8716e-20260123",
"react-is": "19.3.0-canary-24d8716e-20260123",
"scheduler": "0.28.0-canary-24d8716e-20260123"
"react": "19.3.0-canary-10680271-20260126",
"react-dom": "19.3.0-canary-10680271-20260126",
"react-is": "19.3.0-canary-10680271-20260126",
"scheduler": "0.28.0-canary-10680271-20260126"
},
"packageExtensions": {
"eslint-plugin-react-hooks@0.0.0-experimental-6de32a5a-20250822": {
Expand Down
2 changes: 1 addition & 1 deletion packages/create-next-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-next-app",
"version": "16.2.0-canary.8",
"version": "16.2.0-canary.11",
"keywords": [
"react",
"next",
Expand Down
2 changes: 1 addition & 1 deletion packages/create-next-app/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Bundler, GetTemplateFileArgs, InstallTemplateArgs } from "./types";

// Do not rename or format. sync-react script relies on this line.
// prettier-ignore
const nextjsReactPeerVersion = "19.2.3";
const nextjsReactPeerVersion = "19.2.4";
function sorted(obj: Record<string, string>) {
return Object.keys(obj)
.sort()
Expand Down
4 changes: 2 additions & 2 deletions packages/eslint-config-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-config-next",
"version": "16.2.0-canary.8",
"version": "16.2.0-canary.11",
"description": "ESLint configuration used by Next.js.",
"license": "MIT",
"repository": {
Expand All @@ -12,7 +12,7 @@
"dist"
],
"dependencies": {
"@next/eslint-plugin-next": "16.2.0-canary.8",
"@next/eslint-plugin-next": "16.2.0-canary.11",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-internal/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/eslint-plugin-internal",
"private": true,
"version": "16.2.0-canary.8",
"version": "16.2.0-canary.11",
"description": "ESLint plugin for working on Next.js.",
"exports": {
".": "./src/eslint-plugin-internal.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin-next/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/eslint-plugin-next",
"version": "16.2.0-canary.8",
"version": "16.2.0-canary.11",
"description": "ESLint plugin for Next.js.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/font/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@next/font",
"private": true,
"version": "16.2.0-canary.8",
"version": "16.2.0-canary.11",
"repository": {
"url": "vercel/next.js",
"directory": "packages/font"
Expand Down
2 changes: 1 addition & 1 deletion packages/next-bundle-analyzer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next/bundle-analyzer",
"version": "16.2.0-canary.8",
"version": "16.2.0-canary.11",
"main": "index.js",
"types": "index.d.ts",
"license": "MIT",
Expand Down
Loading
Loading