From b785969e83dfcc948be69eb7ec182a2808c183ff Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 7 May 2026 10:37:54 +0200 Subject: [PATCH 1/6] Order loader tree imports by tree depth (#93537) ### What? Reorder the imports emitted by the app-page loader-tree builder so that they are grouped by their depth in the loader tree (shallow segments first, deep segments last) instead of the somewhat arbitrary order produced by recursive walking. ### Why? The loader tree is built by walking the app router tree recursively, and each segment contributes a few `require(...)` declarations (layout, error, loading, page, metadata, ...) to a shared list of imports that is later concatenated into the generated entry module. The order of that list is what ends up in the bundle, and walking the tree recursively interleaves segments at different depths in a way that depends on traversal order rather than on the structure of the route. For chunking and readability we want a more predictable order: *outer (shallower) layouts/segments before inner (deeper) ones*, with imports inside the same depth keeping their original relative order. This gives downstream chunking a more useful signal. ### How? In `crates/next-core/src/base_loader_tree.rs`: - `BaseLoaderTreeBuilder::imports` is now `Vec<(u32, RcStr)>` instead of `Vec`. The `u32` is a sort key (the depth at which the import was produced). - `create_module_tuple_code` takes a new `position: u32` argument and stores it alongside the generated `require` line. In `crates/next-core/src/app_page_loader_tree.rs`: - `walk_tree` takes a new `depth: u32` argument. The initial call from `build()` passes `0`. - `depth` is threaded through `write_modules_entry`, `write_metadata`, `write_metadata_items`, `write_metadata_item`, and `write_static_metadata_item`, so the three other places that push directly into `self.base.imports` (dynamic image metadata, static metadata items, and their alt-text companions) also tag their entries with the current depth. - When recursing into `parallel_routes`, depth is incremented only for the `"children"` slot. Named parallel routes (e.g. `@modal`) are sibling slots of the same segment and therefore stay at the same depth. - In `AppPageLoaderTreeBuilder::build`, the collected `(depth, import)` pairs are stable-sorted by depth (`sort_by_key`, which is stable in Rust, preserving the original relative order within each depth) and then stripped back to `Vec` before being placed on `AppPageLoaderTreeModule.imports`. The public type of `AppPageLoaderTreeModule.imports` (`Vec`) is unchanged, so consumers in `crates/next-core/src/next_app/app_page_entry.rs` need no adjustments. Co-authored-by: v-work-app[bot] <262237222+v-work-app[bot]@users.noreply.github.com> Co-authored-by: Claude Co-authored-by: Tobias Koppers --- crates/next-core/src/app_page_loader_tree.rs | 83 +++++++++++++------- crates/next-core/src/base_loader_tree.rs | 8 +- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/crates/next-core/src/app_page_loader_tree.rs b/crates/next-core/src/app_page_loader_tree.rs index 24f1d202ab85..746aeec2f6b2 100644 --- a/crates/next-core/src/app_page_loader_tree.rs +++ b/crates/next-core/src/app_page_loader_tree.rs @@ -50,11 +50,12 @@ impl AppPageLoaderTreeBuilder { &mut self, module_type: AppDirModuleType, path: Option, + depth: u32, ) -> Result<()> { if let Some(path) = path { let tuple_code = self .base - .create_module_tuple_code(module_type, path) + .create_module_tuple_code(module_type, path, depth) .await?; writeln!( @@ -71,6 +72,7 @@ impl AppPageLoaderTreeBuilder { app_page: &AppPage, metadata: &Metadata, global_metadata: Option<&GlobalMetadata>, + depth: u32, ) -> Result<()> { if metadata.is_empty() && global_metadata @@ -107,13 +109,13 @@ impl AppPageLoaderTreeBuilder { icon.clone() }; - self.write_metadata_items(app_page, "icon", icon.iter()) + self.write_metadata_items(app_page, "icon", icon.iter(), depth) .await?; - self.write_metadata_items(app_page, "apple", apple.iter()) + self.write_metadata_items(app_page, "apple", apple.iter(), depth) .await?; - self.write_metadata_items(app_page, "twitter", twitter.iter()) + self.write_metadata_items(app_page, "twitter", twitter.iter(), depth) .await?; - self.write_metadata_items(app_page, "openGraph", open_graph.iter()) + self.write_metadata_items(app_page, "openGraph", open_graph.iter(), depth) .await?; if let Some(global_metadata) = global_metadata { @@ -151,6 +153,7 @@ impl AppPageLoaderTreeBuilder { app_page: &AppPage, name: &str, it: impl Iterator, + depth: u32, ) -> Result<()> { let mut it = it.peekable(); if it.peek().is_none() { @@ -158,7 +161,8 @@ impl AppPageLoaderTreeBuilder { } writeln!(self.loader_tree_code, " {name}: [")?; for item in it { - self.write_metadata_item(app_page, name, item).await?; + self.write_metadata_item(app_page, name, item, depth) + .await?; } writeln!(self.loader_tree_code, " ],")?; Ok(()) @@ -169,6 +173,7 @@ impl AppPageLoaderTreeBuilder { app_page: &AppPage, name: &str, item: &MetadataWithAltItem, + depth: u32, ) -> Result<()> { match item { MetadataWithAltItem::Static { path, alt_path } => { @@ -178,6 +183,7 @@ impl AppPageLoaderTreeBuilder { item, path.clone(), alt_path.clone(), + depth, ) .await?; } @@ -189,13 +195,14 @@ impl AppPageLoaderTreeBuilder { // This should use the same importing mechanism as create_module_tuple_code, so that // the relative order of items is retained (which isn't the case // when mixing ESM imports and requires). - self.base.imports.push( + self.base.imports.push(( + depth, format!( "const {identifier} = () => require(/*turbopackChunkingType: \ shared*/\"{inner_module_id}\");" ) .into(), - ); + )); let source = dynamic_image_metadata_source( *ResolvedVc::upcast(self.base.module_asset_context), @@ -226,6 +233,7 @@ impl AppPageLoaderTreeBuilder { item: &MetadataWithAltItem, path: FileSystemPath, alt_path: Option, + depth: u32, ) -> Result<()> { let i = self.base.unique_number(); @@ -235,13 +243,14 @@ impl AppPageLoaderTreeBuilder { // This should use the same importing mechanism as create_module_tuple_code, so that the // relative order of items is retained (which isn't the case when mixing ESM imports and // requires). - self.base.imports.push( + self.base.imports.push(( + depth, format!( "const {identifier} = () => require(/*turbopackChunkingType: \ shared*/\"{inner_module_id}\");" ) .into(), - ); + )); let module = StructuredImageModuleType::create_module( Vc::upcast(FileSource::new(path.clone())), BlurPlaceholderMode::None, @@ -259,13 +268,14 @@ impl AppPageLoaderTreeBuilder { // This should use the same importing mechanism as create_module_tuple_code, so that the // relative order of items is retained (which isn't the case when mixing ESM imports and // requires). - self.base.imports.push( + self.base.imports.push(( + depth, format!( "const {identifier} = () => require(/*turbopackChunkingType: \ shared*/\"{inner_module_id}\");" ) .into(), - ); + )); let module = self .base @@ -342,7 +352,12 @@ impl AppPageLoaderTreeBuilder { Ok(()) } - async fn walk_tree(&mut self, loader_tree: &AppPageLoaderTree, root: bool) -> Result<()> { + async fn walk_tree( + &mut self, + loader_tree: &AppPageLoaderTree, + root: bool, + depth: u32, + ) -> Result<()> { use std::fmt::Write; let AppPageLoaderTree { @@ -385,38 +400,48 @@ impl AppPageLoaderTreeBuilder { app_page, metadata, if root { Some(global_metadata) } else { None }, + depth, ) .await?; - self.write_modules_entry(AppDirModuleType::Layout, layout.clone()) + self.write_modules_entry(AppDirModuleType::Layout, layout.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::Error, error.clone()) + self.write_modules_entry(AppDirModuleType::Error, error.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::Loading, loading.clone()) + self.write_modules_entry(AppDirModuleType::Loading, loading.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::Template, template.clone()) + self.write_modules_entry(AppDirModuleType::Template, template.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::NotFound, not_found.clone()) + self.write_modules_entry(AppDirModuleType::NotFound, not_found.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::Forbidden, forbidden.clone()) + self.write_modules_entry(AppDirModuleType::Forbidden, forbidden.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::Unauthorized, unauthorized.clone()) + self.write_modules_entry(AppDirModuleType::Unauthorized, unauthorized.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::Page, page.clone()) + self.write_modules_entry(AppDirModuleType::Page, page.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::DefaultPage, default.clone()) + self.write_modules_entry(AppDirModuleType::DefaultPage, default.clone(), depth) .await?; - self.write_modules_entry(AppDirModuleType::GlobalError, global_error.clone()) - .await?; - self.write_modules_entry(AppDirModuleType::GlobalNotFound, global_not_found.clone()) + self.write_modules_entry(AppDirModuleType::GlobalError, global_error.clone(), depth) .await?; + self.write_modules_entry( + AppDirModuleType::GlobalNotFound, + global_not_found.clone(), + depth, + ) + .await?; let modules_code = replace(&mut self.loader_tree_code, temp_loader_tree_code); // add parallel_routes for (key, parallel_route) in parallel_routes.iter() { write!(self.loader_tree_code, "{key}: ", key = StringifyJs(key))?; - Box::pin(self.walk_tree(parallel_route, false)).await?; + let next_depth = if key.as_str() == "children" { + depth + 1 + } else { + depth + }; + Box::pin(self.walk_tree(parallel_route, false, next_depth)).await?; writeln!(self.loader_tree_code, ",")?; } writeln!(self.loader_tree_code, "}}, {{")?; @@ -454,9 +479,11 @@ impl AppPageLoaderTreeBuilder { .insert(GLOBAL_NOT_FOUND.into(), module); }; - self.walk_tree(loader_tree, true).await?; + self.walk_tree(loader_tree, true, 0).await?; + let mut imports = self.base.imports; + imports.sort_by_key(|(position, _)| *position); Ok(AppPageLoaderTreeModule { - imports: self.base.imports, + imports: imports.into_iter().map(|(_, import)| import).collect(), loader_tree_code: self.loader_tree_code.into(), inner_assets: self.base.inner_assets, }) diff --git a/crates/next-core/src/base_loader_tree.rs b/crates/next-core/src/base_loader_tree.rs index bbb42f49e661..7d8be54a92e5 100644 --- a/crates/next-core/src/base_loader_tree.rs +++ b/crates/next-core/src/base_loader_tree.rs @@ -15,7 +15,7 @@ use turbopack_ecmascript::{magic_identifier, utils::StringifyJs}; pub struct BaseLoaderTreeBuilder { pub inner_assets: FxIndexMap>>, counter: usize, - pub imports: Vec, + pub imports: Vec<(u32, RcStr)>, pub module_asset_context: ResolvedVc, pub server_component_transition: ResolvedVc>, } @@ -91,12 +91,14 @@ impl BaseLoaderTreeBuilder { &mut self, module_type: AppDirModuleType, path: FileSystemPath, + position: u32, ) -> Result { let name = module_type.name(); let i = self.unique_number(); let identifier = magic_identifier::mangle(&format!("{name} #{i}")); - self.imports.push( + self.imports.push(( + position, formatdoc!( r#" const {} = () => require(/*turbopackChunkingType: shared*/"MODULE_{}"); @@ -105,7 +107,7 @@ impl BaseLoaderTreeBuilder { i ) .into(), - ); + )); let module = self .process_source(Vc::upcast(FileSource::new(path.clone()))) From ead8013f88519250e34c9d0bf93f31e4e5342169 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Thu, 7 May 2026 10:50:02 +0200 Subject: [PATCH 2/6] Turbopack: more strict vergen setup (#93541) Co-authored-by: Tobias Koppers Co-authored-by: Sebastian Sebbie Silbermann --- crates/next-api/build.rs | 2 +- crates/next-napi-bindings/build.rs | 9 +++++++-- scripts/docker-native-build.sh | 4 ++++ turbopack/crates/turbopack-cli/build.rs | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/next-api/build.rs b/crates/next-api/build.rs index 65d1aed8ba3b..1ea5e13aa508 100644 --- a/crates/next-api/build.rs +++ b/crates/next-api/build.rs @@ -6,8 +6,8 @@ fn main() -> anyhow::Result<()> { .target_triple(true) .build()?; vergen::Emitter::default() - .add_instructions(&cargo)? .fail_on_error() + .add_instructions(&cargo)? .emit()?; Ok(()) diff --git a/crates/next-napi-bindings/build.rs b/crates/next-napi-bindings/build.rs index fb230f35079a..212c67b304a0 100644 --- a/crates/next-napi-bindings/build.rs +++ b/crates/next-napi-bindings/build.rs @@ -57,9 +57,9 @@ fn main() -> anyhow::Result<()> { ) .build()?; vergen_gitcl::Emitter::default() + .fail_on_error() .add_instructions(&cargo)? .add_instructions(&git)? - .fail_on_error() .emit()?; match Command::new("git").args(["rev-parse", "HEAD"]).output() { @@ -67,7 +67,12 @@ fn main() -> anyhow::Result<()> { "cargo:warning=git HEAD: {}", str::from_utf8(&out.stdout).unwrap() ), - _ => println!("cargo:warning=`git rev-parse HEAD` failed"), + Ok(out) => println!( + "cargo:warning=`git rev-parse HEAD` failed with status {}: {}", + out.status, + str::from_utf8(&out.stderr).unwrap() + ), + Err(e) => println!("cargo:warning=`git rev-parse HEAD` could not be spawned: {e}"), } if !is_macos_target { diff --git a/scripts/docker-native-build.sh b/scripts/docker-native-build.sh index 7f9619fd5045..c77181219592 100755 --- a/scripts/docker-native-build.sh +++ b/scripts/docker-native-build.sh @@ -16,6 +16,10 @@ set -xeo pipefail +# /build is bind-mounted from the host with a uid that differs from the +# container's root user; mark it safe so vergen's `git rev-parse` works. +git config --global --add safe.directory /build + BUILD_TASK="${BUILD_TASK:-build-native-release}" # Node.js (installed via nodesource) is used only as a build tool (runs diff --git a/turbopack/crates/turbopack-cli/build.rs b/turbopack/crates/turbopack-cli/build.rs index 3840ad52c040..cfc3e3466db0 100644 --- a/turbopack/crates/turbopack-cli/build.rs +++ b/turbopack/crates/turbopack-cli/build.rs @@ -28,8 +28,8 @@ fn main() -> anyhow::Result<()> { ) .build()?; vergen_gitcl::Emitter::default() - .add_instructions(&git)? .fail_on_error() + .add_instructions(&git)? .emit()?; Ok(()) From 401ce3803c2c1098056f1b1009fbdb8bac9d069a Mon Sep 17 00:00:00 2001 From: "next-js-bot[bot]" <279046576+next-js-bot[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 08:55:48 +0000 Subject: [PATCH 3/6] v16.3.0-canary.14 --- lerna.json | 2 +- packages/create-next-app/package.json | 2 +- packages/eslint-config-next/package.json | 4 ++-- packages/eslint-plugin-internal/package.json | 2 +- packages/eslint-plugin-next/package.json | 2 +- packages/font/package.json | 2 +- packages/next-bundle-analyzer/package.json | 2 +- packages/next-codemod/package.json | 2 +- packages/next-env/package.json | 2 +- packages/next-mdx/package.json | 2 +- packages/next-playwright/package.json | 2 +- packages/next-plugin-storybook/package.json | 2 +- packages/next-polyfill-module/package.json | 2 +- packages/next-polyfill-nomodule/package.json | 2 +- packages/next-routing/package.json | 2 +- packages/next-rspack/package.json | 2 +- packages/next-swc/package.json | 2 +- packages/next/package.json | 14 +++++++------- packages/react-refresh-utils/package.json | 2 +- packages/third-parties/package.json | 4 ++-- pnpm-lock.yaml | 16 ++++++++-------- 21 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lerna.json b/lerna.json index 5a5b879c6fb9..57fd330c0f3b 100644 --- a/lerna.json +++ b/lerna.json @@ -15,5 +15,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "16.3.0-canary.13" + "version": "16.3.0-canary.14" } \ No newline at end of file diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 4c82b6f617fb..e80964eeb7fc 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 79c4b8a54ab1..5a345b2fd7ac 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "ESLint configuration used by Next.js.", "license": "MIT", "repository": { @@ -12,7 +12,7 @@ "dist" ], "dependencies": { - "@next/eslint-plugin-next": "16.3.0-canary.13", + "@next/eslint-plugin-next": "16.3.0-canary.14", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index dcc2701fe0f1..1bb5097910ee 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,7 +1,7 @@ { "name": "@next/eslint-plugin-internal", "private": true, - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "ESLint plugin for working on Next.js.", "exports": { ".": "./src/eslint-plugin-internal.js" diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 4fe8b37bd2e0..a575959aae04 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "ESLint plugin for Next.js.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/font/package.json b/packages/font/package.json index ebc6de6119e6..e804f2672921 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,7 +1,7 @@ { "name": "@next/font", "private": true, - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "repository": { "url": "vercel/next.js", "directory": "packages/font" diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index f57a46b98257..2cac73f0253a 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 067f4f12a87a..fe5aa6197c95 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index b6635b0ca4a3..2b49ecbf1253 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index fa25a872cbc6..ae15231701da 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-playwright/package.json b/packages/next-playwright/package.json index 5a9646983ea1..7df5603c4fbf 100644 --- a/packages/next-playwright/package.json +++ b/packages/next-playwright/package.json @@ -1,6 +1,6 @@ { "name": "@next/playwright", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "repository": { "url": "vercel/next.js", "directory": "packages/next-playwright" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index b1e610593d0f..01e47424f4d6 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index 44d372e60dca..3f64a3d32ed1 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index d12a2c39c88c..fea6fe6c9dc3 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-routing/package.json b/packages/next-routing/package.json index 342f33d6a39f..f42ea9c3aa8b 100644 --- a/packages/next-routing/package.json +++ b/packages/next-routing/package.json @@ -1,6 +1,6 @@ { "name": "@next/routing", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "keywords": [ "react", "next", diff --git a/packages/next-rspack/package.json b/packages/next-rspack/package.json index fbf3dfd76999..d8f302b9182a 100644 --- a/packages/next-rspack/package.json +++ b/packages/next-rspack/package.json @@ -1,6 +1,6 @@ { "name": "next-rspack", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "repository": { "url": "vercel/next.js", "directory": "packages/next-rspack" diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 8b7770d729de..240a72ce406b 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "private": true, "files": [ "native/" diff --git a/packages/next/package.json b/packages/next/package.json index f60b29dcbc19..c799202b8638 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -101,7 +101,7 @@ ] }, "dependencies": { - "@next/env": "16.3.0-canary.13", + "@next/env": "16.3.0-canary.14", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -165,11 +165,11 @@ "@modelcontextprotocol/sdk": "1.18.1", "@mswjs/interceptors": "0.23.0", "@napi-rs/triples": "1.2.0", - "@next/font": "16.3.0-canary.13", - "@next/polyfill-module": "16.3.0-canary.13", - "@next/polyfill-nomodule": "16.3.0-canary.13", - "@next/react-refresh-utils": "16.3.0-canary.13", - "@next/swc": "16.3.0-canary.13", + "@next/font": "16.3.0-canary.14", + "@next/polyfill-module": "16.3.0-canary.14", + "@next/polyfill-nomodule": "16.3.0-canary.14", + "@next/react-refresh-utils": "16.3.0-canary.14", + "@next/swc": "16.3.0-canary.14", "@opentelemetry/api": "1.6.0", "@playwright/test": "1.58.2", "@rspack/core": "1.6.7", diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 24a5aff3a985..5e43c526ac09 100644 --- a/packages/react-refresh-utils/package.json +++ b/packages/react-refresh-utils/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-refresh-utils", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "description": "An experimental package providing utilities for React Refresh.", "repository": { "url": "vercel/next.js", diff --git a/packages/third-parties/package.json b/packages/third-parties/package.json index b6d745f1b8a2..64458dcd868d 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "16.3.0-canary.13", + "version": "16.3.0-canary.14", "repository": { "url": "vercel/next.js", "directory": "packages/third-parties" @@ -27,7 +27,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "16.3.0-canary.13", + "next": "16.3.0-canary.14", "outdent": "0.8.0", "prettier": "2.5.1", "typescript": "6.0.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae23c6049b83..75ca9e7ab5c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -983,7 +983,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../eslint-plugin-next eslint: specifier: '>=9.0.0' @@ -1060,7 +1060,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../next-env '@swc/helpers': specifier: 0.5.15 @@ -1181,19 +1181,19 @@ importers: specifier: 1.2.0 version: 1.2.0 '@next/font': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../font '@next/polyfill-module': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../next-polyfill-nomodule '@next/react-refresh-utils': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../react-refresh-utils '@next/swc': - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1927,7 +1927,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 16.3.0-canary.13 + specifier: 16.3.0-canary.14 version: link:../next outdent: specifier: 0.8.0 From d457e63ed6b0994792a49922a10a9fd93fb5f4a1 Mon Sep 17 00:00:00 2001 From: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com> Date: Thu, 7 May 2026 12:11:37 +0200 Subject: [PATCH 4/6] [devtools] Force ANSI colors on overlay code frames regardless of TTY (#93550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What? The dev overlay's code frame loses syntax highlighting whenever `next dev` runs under a non-TTY parent (VS Code / Cursor's integrated terminal, Docker without `-t`, `concurrently`, the JavaScript Debug Terminal, CI logs, etc.). Tokens render as plain monochrome text instead of the usual colored output. ### Why? The "syntax highlighting" in the overlay isn't real highlighting. The server embeds ANSI escape codes into the code frame string, and the browser overlay parses them via Anser to render colored ``s. Whether the dev server's stdout is a TTY is irrelevant on this path — the consumer is the browser. Since #71860, `getOriginalCodeFrame` defaults `colors` to `process.stdout.isTTY`, and the overlay middlewares relied on that default. The moment stdout was a pipe, the overlay had nothing to color. ### How? `createOriginalStackFrame` and `getOriginalStackFrames` are shared by five consumers (overlay HTTP endpoint, webpack `Module not found` plugin, browser-log forwarding, MCP error formatter, hot-reloader resolver). Hardcoding `colors: true` would leak escape sequences into the four non-overlay paths. Thread a `codeFrameOptions: { colors?, maxWidth? }` parameter through both functions. The overlay HTTP middlewares opt in to `colors: true` and `maxWidth: DEVTOOLS_CODE_FRAME_MAX_WIDTH`; everyone else keeps the existing TTY-gated colors and terminal-width wrapping. (The 1000-column override from #92621 was suffering from the same leak — also scoped to the overlay now.) Tested from Cursor's integrated terminal with/without fix: Before: CleanShot 2026-05-06 at 22 34
45@2x After: CleanShot 2026-05-06 at 22 34
30@2x --- .../src/server/dev/middleware-turbopack.ts | 31 ++++++++++++++++--- .../next/src/server/dev/middleware-webpack.ts | 31 +++++++++++++++++-- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/packages/next/src/server/dev/middleware-turbopack.ts b/packages/next/src/server/dev/middleware-turbopack.ts index 5ad2701b1ef6..a3a9247167bd 100644 --- a/packages/next/src/server/dev/middleware-turbopack.ts +++ b/packages/next/src/server/dev/middleware-turbopack.ts @@ -283,10 +283,22 @@ async function nativeTraceSource( return undefined } +/** + * Code frame rendering options. The defaults match terminal consumers; only + * the overlay HTTP path opts in to always-on colors and the wide max width. + */ +type CodeFrameOptions = { + /** Defaults to `process.stdout.isTTY`. */ + colors?: boolean + /** Defaults to the dev server's terminal width. */ + maxWidth?: number +} + async function createOriginalStackFrame( project: Project, projectPath: string, - frame: TurbopackStackFrame + frame: TurbopackStackFrame, + codeFrameOptions?: CodeFrameOptions ): Promise { const traced = (await nativeTraceSource(frame)) ?? @@ -324,9 +336,8 @@ async function createOriginalStackFrame( get originalCodeFrame() { if (originalCodeFrame === undefined) { originalCodeFrame = getOriginalCodeFrame(tracedFrame, traced.source, { - // The overlay renders in a browser with horizontal scrolling, - // so don't truncate lines to the server's terminal width. - maxWidth: DEVTOOLS_CODE_FRAME_MAX_WIDTH, + colors: codeFrameOptions?.colors, + maxWidth: codeFrameOptions?.maxWidth, }) } return originalCodeFrame @@ -372,6 +383,13 @@ export function getOverlayMiddleware({ isServer: request.isServer, isEdgeServer: request.isEdgeServer, isAppDirectory: request.isAppDirectory, + codeFrameOptions: { + // Overlay parses ANSI in JS and renders in a scrollable + // `
`, so colors are always wanted and terminal width is
+          // irrelevant.
+          colors: true,
+          maxWidth: DEVTOOLS_CODE_FRAME_MAX_WIDTH,
+        },
       })
 
       ignoreListAnonymousStackFramesIfSandwiched(result)
@@ -492,6 +510,7 @@ export async function getOriginalStackFrames({
   isServer,
   isEdgeServer,
   isAppDirectory,
+  codeFrameOptions,
 }: {
   project: Project
   projectPath: string
@@ -499,6 +518,7 @@ export async function getOriginalStackFrames({
   isServer: boolean
   isEdgeServer: boolean
   isAppDirectory: boolean
+  codeFrameOptions?: CodeFrameOptions
 }): Promise {
   const stackFrames = createStackFrames({
     frames,
@@ -513,7 +533,8 @@ export async function getOriginalStackFrames({
         const stackFrame = await createOriginalStackFrame(
           project,
           projectPath,
-          frame
+          frame,
+          codeFrameOptions
         )
         if (stackFrame === null) {
           return {
diff --git a/packages/next/src/server/dev/middleware-webpack.ts b/packages/next/src/server/dev/middleware-webpack.ts
index a2e9fad69aec..556cb652497d 100644
--- a/packages/next/src/server/dev/middleware-webpack.ts
+++ b/packages/next/src/server/dev/middleware-webpack.ts
@@ -182,12 +182,24 @@ function findOriginalSourcePositionAndContentFromCompilation(
   return module?.buildInfo?.importLocByPath?.get(importedModule) ?? null
 }
 
+/**
+ * Code frame rendering options. The defaults match terminal consumers; only
+ * the overlay HTTP path opts in to always-on colors and the wide max width.
+ */
+type CodeFrameOptions = {
+  /** Defaults to `process.stdout.isTTY`. */
+  colors?: boolean
+  /** Defaults to the dev server's terminal width. */
+  maxWidth?: number
+}
+
 export async function createOriginalStackFrame({
   ignoredByDefault,
   source,
   rootDirectory,
   frame,
   errorMessage,
+  codeFrameOptions,
 }: {
   /** setting this to true will not consult ignoreList */
   ignoredByDefault: boolean
@@ -195,6 +207,7 @@ export async function createOriginalStackFrame({
   rootDirectory: string
   frame: StackFrame
   errorMessage?: string
+  codeFrameOptions?: CodeFrameOptions
 }): Promise {
   const moduleNotFound = findModuleNotFoundFromError(errorMessage)
   const result = await (() => {
@@ -263,9 +276,8 @@ export async function createOriginalStackFrame({
     get originalCodeFrame() {
       if (originalCodeFrame === undefined) {
         originalCodeFrame = getOriginalCodeFrame(traced, sourceContent, {
-          // The overlay renders in a browser with horizontal scrolling,
-          // so don't truncate lines to the server's terminal width.
-          maxWidth: DEVTOOLS_CODE_FRAME_MAX_WIDTH,
+          colors: codeFrameOptions?.colors,
+          maxWidth: codeFrameOptions?.maxWidth,
         })
       }
       return originalCodeFrame
@@ -393,6 +405,7 @@ export async function getOriginalStackFrames({
   serverStats,
   edgeServerStats,
   rootDirectory,
+  codeFrameOptions,
 }: {
   isServer: boolean
   isEdgeServer: boolean
@@ -402,6 +415,7 @@ export async function getOriginalStackFrames({
   serverStats: () => webpack.Stats | null
   edgeServerStats: () => webpack.Stats | null
   rootDirectory: string
+  codeFrameOptions?: CodeFrameOptions
 }): Promise {
   const frameResponses = await Promise.all(
     frames.map(
@@ -415,6 +429,7 @@ export async function getOriginalStackFrames({
           serverStats,
           edgeServerStats,
           rootDirectory,
+          codeFrameOptions,
         }).then(
           (value) => {
             return {
@@ -446,6 +461,7 @@ async function getOriginalStackFrame({
   serverStats,
   edgeServerStats,
   rootDirectory,
+  codeFrameOptions,
 }: {
   isServer: boolean
   isEdgeServer: boolean
@@ -455,6 +471,7 @@ async function getOriginalStackFrame({
   serverStats: () => webpack.Stats | null
   edgeServerStats: () => webpack.Stats | null
   rootDirectory: string
+  codeFrameOptions?: CodeFrameOptions
 }): Promise {
   const filename = frame.file ?? ''
   const source = await getSource(frame, {
@@ -534,6 +551,7 @@ async function getOriginalStackFrame({
     frame,
     source,
     rootDirectory,
+    codeFrameOptions,
   })
 
   if (!originalStackFrameResponse) {
@@ -601,6 +619,13 @@ export function getOverlayMiddleware(options: {
             serverStats,
             edgeServerStats,
             rootDirectory,
+            codeFrameOptions: {
+              // Overlay parses ANSI in JS and renders in a scrollable
+              // `
`, so colors are always wanted and terminal width
+              // is irrelevant.
+              colors: true,
+              maxWidth: DEVTOOLS_CODE_FRAME_MAX_WIDTH,
+            },
           })
         )
       } catch (err) {

From 0b8a0fb42965b9d1dcaf8476aa7e9c9a482824b9 Mon Sep 17 00:00:00 2001
From: Joseph 
Date: Thu, 7 May 2026 14:46:44 +0200
Subject: [PATCH 5/6] docs: escape literal triple-backticks in contribution
 guide (#93584)

Updates:
https://nextjs.org/docs/community/contribution-guide#language-and-filename

to:


Screenshot 2026-05-07 at 14 26 35
---
 docs/04-community/01-contribution-guide.mdx | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/docs/04-community/01-contribution-guide.mdx b/docs/04-community/01-contribution-guide.mdx
index 52c8acfe291a..fc3a4e31992a 100644
--- a/docs/04-community/01-contribution-guide.mdx
+++ b/docs/04-community/01-contribution-guide.mdx
@@ -243,17 +243,17 @@ Most examples in the docs are written in `tsx` and `jsx`, and a few in `bash`. H
 
 When writing JavaScript code blocks, we use the following language and extension combinations.
 
-|                                | Language | Extension |
-| ------------------------------ | -------- | --------- |
-| JavaScript files with JSX code | ```jsx   | .js       |
-| JavaScript files without JSX   | ```js    | .js       |
-| TypeScript files with JSX      | ```tsx   | .tsx      |
-| TypeScript files without JSX   | ```ts    | .ts       |
+|                                | Language   | Extension |
+| ------------------------------ | ---------- | --------- |
+| JavaScript files with JSX code | ` ```jsx ` | .js       |
+| JavaScript files without JSX   | ` ```js `  | .js       |
+| TypeScript files with JSX      | ` ```tsx ` | .tsx      |
+| TypeScript files without JSX   | ` ```ts `  | .ts       |
 
 > **Good to know**:
 >
 > - Make sure to use **`.js`** extension for JavaScript files with **JSX** code.
-> - For example, ```jsx filename="app/layout.js"
+> - For example, ` ```jsx filename="app/layout.js" `
 
 ### TS and JS Switcher
 

From 225be6afd8e5f0ca1075a8f7a789eea9bd352bbc Mon Sep 17 00:00:00 2001
From: "Sebastian \"Sebbie\" Silbermann" 
Date: Thu, 7 May 2026 15:01:25 +0200
Subject: [PATCH 6/6] [test] Pin package manager versions used (#93592)

---
 .github/next-stats-action.Dockerfile                 |  2 +-
 .github/package.json                                 |  5 +++++
 .github/pnpm-lock.yaml                               |  2 ++
 test/e2e/app-dir/nx-handling/nx-handling.test.ts     |  1 +
 test/e2e/filesystem-cache/filesystem-cache.test.ts   |  1 +
 .../e2e/handle-non-hoisted-swc-helpers/index.test.ts | 12 +++++++++++-
 test/e2e/yarn-pnp/test/utils.ts                      |  5 +++++
 .../create-next-app/package-manager/pnpm.test.ts     |  8 ++++++++
 test/lib/create-next-install.js                      |  5 +++++
 test/lib/next-modes/base.ts                          |  7 +++++++
 10 files changed, 46 insertions(+), 2 deletions(-)
 create mode 100644 .github/package.json

diff --git a/.github/next-stats-action.Dockerfile b/.github/next-stats-action.Dockerfile
index 8a0b3304f2e4..214095a8c725 100644
--- a/.github/next-stats-action.Dockerfile
+++ b/.github/next-stats-action.Dockerfile
@@ -16,7 +16,7 @@ RUN corepack enable
 FROM base AS pnpm-deploy
 
 WORKDIR /dot-github
-COPY pnpm-lock.yaml pnpm-workspace.yaml .
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
 COPY --exclude=actions/*/node_modules \
   actions/next-stats-action actions/next-stats-action
 RUN pnpm deploy --filter=next-stats-action --production /next-stats
diff --git a/.github/package.json b/.github/package.json
new file mode 100644
index 000000000000..a59d9a3884b2
--- /dev/null
+++ b/.github/package.json
@@ -0,0 +1,5 @@
+{
+  "name": "next-github-workflows",
+  "private": true,
+  "packageManager": "pnpm@10.33.0"
+}
diff --git a/.github/pnpm-lock.yaml b/.github/pnpm-lock.yaml
index 91800d35ee90..004c92e24e2e 100644
--- a/.github/pnpm-lock.yaml
+++ b/.github/pnpm-lock.yaml
@@ -7,6 +7,8 @@ settings:
 
 importers:
 
+  .: {}
+
   actions/needs-triage:
     dependencies:
       '@actions/core':
diff --git a/test/e2e/app-dir/nx-handling/nx-handling.test.ts b/test/e2e/app-dir/nx-handling/nx-handling.test.ts
index 9e373efa8cdb..548144c0abd6 100644
--- a/test/e2e/app-dir/nx-handling/nx-handling.test.ts
+++ b/test/e2e/app-dir/nx-handling/nx-handling.test.ts
@@ -11,6 +11,7 @@ describe('nx-handling', () => {
       name: '@nx-next/source',
       version: '0.0.0',
       private: true,
+      packageManager: 'npm@10.9.2',
       scripts: {
         build: 'rm -rf dist; nx run next-nx-test:build',
         dev: 'nx run next-nx-test:dev',
diff --git a/test/e2e/filesystem-cache/filesystem-cache.test.ts b/test/e2e/filesystem-cache/filesystem-cache.test.ts
index dece69e81029..283056869860 100644
--- a/test/e2e/filesystem-cache/filesystem-cache.test.ts
+++ b/test/e2e/filesystem-cache/filesystem-cache.test.ts
@@ -48,6 +48,7 @@ for (const cacheEnabled of [false, true]) {
       files: __dirname,
       skipDeployment: true,
       packageJson: {
+        packageManager: 'npm@10.9.2',
         scripts: {
           build: `${envVars} next build`,
           dev: `${envVars} next dev`,
diff --git a/test/e2e/handle-non-hoisted-swc-helpers/index.test.ts b/test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
index dc9927231afe..89059f33b388 100644
--- a/test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
+++ b/test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
@@ -1,4 +1,4 @@
-import { createNext } from 'e2e-utils'
+import { createNext, isNextDev } from 'e2e-utils'
 import { NextInstance } from 'e2e-utils'
 import { renderViaHTTP } from 'next-test-utils'
 
@@ -24,8 +24,18 @@ describe('handle-non-hoisted-swc-helpers', () => {
           }
         `,
       },
+      packageJson: {
+        packageManager: 'npm@10.9.2',
+        scripts: {
+          build: 'next build',
+          dev: 'next dev',
+          start: 'next start',
+        },
+      },
       installCommand:
         'npm install; mkdir -p node_modules/next/node_modules/@swc; mv node_modules/@swc/helpers node_modules/next/node_modules/@swc/',
+      buildCommand: 'npm run build',
+      startCommand: isNextDev ? 'npm run dev' : 'npm run start',
       dependencies: {},
     })
   })
diff --git a/test/e2e/yarn-pnp/test/utils.ts b/test/e2e/yarn-pnp/test/utils.ts
index 122a07ccf3ce..5cda96939550 100644
--- a/test/e2e/yarn-pnp/test/utils.ts
+++ b/test/e2e/yarn-pnp/test/utils.ts
@@ -45,6 +45,11 @@ export function runTests(
           },
           {} as { [key: string]: FileRef }
         ),
+        packageJson: {
+          // Bootstrap with classic yarn; the install command below runs
+          // `yarn set version berry` which rewrites this to the berry version.
+          packageManager: 'yarn@1.22.22',
+        },
         dependencies: {
           ...packageJson.dependencies,
           ...packageJson.devDependencies,
diff --git a/test/integration/create-next-app/package-manager/pnpm.test.ts b/test/integration/create-next-app/package-manager/pnpm.test.ts
index 726553762039..e9f074d944bf 100644
--- a/test/integration/create-next-app/package-manager/pnpm.test.ts
+++ b/test/integration/create-next-app/package-manager/pnpm.test.ts
@@ -1,4 +1,5 @@
 import {
+  command,
   DEFAULT_FILES,
   FULL_EXAMPLE_PATH,
   projectFilesShouldExist,
@@ -10,6 +11,11 @@ import {
 const lockFile = 'pnpm-lock.yaml'
 const files = [...DEFAULT_FILES, lockFile]
 
+// Match the monorepo's pinned pnpm so CNA's `pnpm install` doesn't drift to
+// whichever version corepack happens to fetch as "latest" at test time.
+const rootPackageManager: string =
+  require('../../../../package.json').packageManager
+
 describe('create-next-app with package manager pnpm', () => {
   let nextTgzFilename: string
 
@@ -18,6 +24,8 @@ describe('create-next-app with package manager pnpm', () => {
       throw new Error('This test needs to be run with `node run-tests.js`.')
     }
 
+    await command('corepack', ['prepare', '--activate', rootPackageManager])
+
     const pkgPaths = new Map(
       JSON.parse(process.env.NEXT_TEST_PKG_PATHS)
     )
diff --git a/test/lib/create-next-install.js b/test/lib/create-next-install.js
index e55eb0896b9b..27c0aabee13f 100644
--- a/test/lib/create-next-install.js
+++ b/test/lib/create-next-install.js
@@ -9,6 +9,7 @@ const { linkPackages } =
 
 const PREFER_OFFLINE = process.env.NEXT_TEST_PREFER_OFFLINE === '1'
 const useRspack = process.env.NEXT_TEST_USE_RSPACK === '1'
+const ROOT_PACKAGE_MANAGER = require('../../package.json').packageManager
 
 async function installDependencies(cwd, tmpDir) {
   const args = [
@@ -175,6 +176,10 @@ async function createNextInstall({
         path.join(installDir, 'package.json'),
         JSON.stringify(
           {
+            // Pin packageManager so corepack doesn't auto-inject a reference
+            // to the latest version (and rewrite this file mid-test).
+            // Callers can override via packageJson.packageManager.
+            packageManager: ROOT_PACKAGE_MANAGER,
             ...packageJson,
             scripts,
             dependencies: combinedDependencies,
diff --git a/test/lib/next-modes/base.ts b/test/lib/next-modes/base.ts
index 7851d194d78c..a4b51a836a9e 100644
--- a/test/lib/next-modes/base.ts
+++ b/test/lib/next-modes/base.ts
@@ -71,6 +71,9 @@ type OmitFirstArgument = F extends (
 // prettier-ignore
 const nextjsReactPeerVersion = "19.2.6";
 
+const ROOT_PACKAGE_MANAGER: string =
+  require('../../../package.json').packageManager
+
 export class NextInstance {
   protected files: ResolvedFileConfig
   protected overrideFiles: ResolvedFileConfig
@@ -275,6 +278,10 @@ export class NextInstance {
             path.join(this.testDir, 'package.json'),
             JSON.stringify(
               {
+                // Pin packageManager so corepack doesn't auto-inject a reference
+                // to the latest version (and rewrite this file mid-test).
+                // Callers can override via packageJson.packageManager.
+                packageManager: ROOT_PACKAGE_MANAGER,
                 ...this.packageJson,
                 dependencies: {
                   ...finalDependencies,