diff --git a/.vscode/settings.json b/.vscode/settings.json index 2de2ef61939c..3b28ea4bab51 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,19 +18,15 @@ ], // Disable Jest autoRun as otherwise it will start running all tests the first time. "jest.autoRun": "off", - // Debugging. "debug.javascript.unmapMissingSources": true, - "files.exclude": { "**/node_modules": false, "node_modules": true, "*[!test]**/node_modules": true }, - // Ensure enough terminal history is preserved when running tests. "terminal.integrated.scrollback": 10000, - // Configure todo-tree to exclude node_modules, dist, and compiled. "todo-tree.filtering.excludeGlobs": [ "**/node_modules", @@ -48,10 +44,8 @@ "[x]", "TODO-APP" ], - // Disable TypeScript surveys. "typescript.surveys.enabled": false, - // Enable file nesting for unit test files. "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { @@ -82,5 +76,6 @@ "language": "markdown", "scheme": "file" } - ] + ], + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx b/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx index f54290791cdb..4284a137212e 100644 --- a/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx +++ b/docs/02-app/01-building-your-application/03-rendering/01-server-components.mdx @@ -29,7 +29,7 @@ There are a couple of benefits to doing the rendering work on the server, includ ## Using Server Components in Next.js -By default, Next.js uses Server Components. This allows you to automatically implement server rendering with no additional configuration, and you can opt into using Client Components when you needed, see [Client Components](/docs/app/building-your-application/rendering/client-components). +By default, Next.js uses Server Components. This allows you to automatically implement server rendering with no additional configuration, and you can opt into using Client Components when needed, see [Client Components](/docs/app/building-your-application/rendering/client-components). ## How are Server Components rendered? diff --git a/lerna.json b/lerna.json index 19acc69763d4..9473ac4b6bb8 100644 --- a/lerna.json +++ b/lerna.json @@ -16,5 +16,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "13.4.20-canary.18" + "version": "13.4.20-canary.20" } diff --git a/package.json b/package.json index 5582290ae2d4..21a75dfea927 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@types/node-fetch": "2.6.1", "@types/react": "18.2.8", "@types/react-dom": "18.2.4", - "@types/relay-runtime": "13.0.0", + "@types/relay-runtime": "14.1.13", "@types/selenium-webdriver": "4.0.15", "@types/sharp": "0.29.3", "@types/string-hash": "1.1.1", @@ -226,7 +226,7 @@ "tree-kill": "1.2.2", "tsec": "0.2.1", "turbo": "1.10.9", - "typescript": "5.1.3", + "typescript": "5.2.2", "unfetch": "4.2.0", "wait-port": "0.2.2", "webpack": "5.86.0", diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 39368ecbda9b..cca799bdb09c 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "keywords": [ "react", "next", diff --git a/packages/eslint-config-next/package.json b/packages/eslint-config-next/package.json index 40b39de435e8..8e028a9ab6d7 100644 --- a/packages/eslint-config-next/package.json +++ b/packages/eslint-config-next/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-next", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "description": "ESLint configuration used by Next.js.", "main": "index.js", "license": "MIT", @@ -10,7 +10,7 @@ }, "homepage": "https://nextjs.org/docs/app/building-your-application/configuring/eslint#eslint-config", "dependencies": { - "@next/eslint-plugin-next": "13.4.20-canary.18", + "@next/eslint-plugin-next": "13.4.20-canary.20", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", "eslint-import-resolver-node": "^0.3.6", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index bf8c14b93007..61d7b0aa5012 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": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "description": "ESLint plugin for NextJS.", "main": "dist/index.js", "license": "MIT", diff --git a/packages/font/package.json b/packages/font/package.json index 5e9555792474..590100e48738 100644 --- a/packages/font/package.json +++ b/packages/font/package.json @@ -1,6 +1,6 @@ { "name": "@next/font", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "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 ee431062f783..11e767e1d13e 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "main": "index.js", "types": "index.d.ts", "license": "MIT", diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index 2975b947fedc..42ac260ab258 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "license": "MIT", "repository": { "type": "git", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index 27a38c5c7f2e..dbac7a4a5d26 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 19808d2a42ee..75739c7731f1 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index 63ffdaf3b8ef..1af2a0530ad1 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "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 e6b62dd55494..f333bb901008 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "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 154996666e17..c7b23dcd1e42 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next-swc/crates/next-api/src/app.rs b/packages/next-swc/crates/next-api/src/app.rs index 12ed8e549887..295b57369a6d 100644 --- a/packages/next-swc/crates/next-api/src/app.rs +++ b/packages/next-swc/crates/next-api/src/app.rs @@ -8,7 +8,7 @@ use next_core::{ mode::NextMode, next_app::{ get_app_client_references_chunks, get_app_client_shared_chunks, get_app_page_entry, - get_app_route_entry, AppEntry, + get_app_route_entry, AppEntry, AppPage, }, next_client::{ get_client_module_options_context, get_client_resolve_options_context, @@ -344,8 +344,7 @@ impl AppProject { .map(|(pathname, app_entrypoint)| async { Ok(( pathname.clone(), - *app_entry_point_to_route(self, app_entrypoint.clone(), pathname.clone()) - .await?, + *app_entry_point_to_route(self, app_entrypoint.clone()).await?, )) }) .try_join() @@ -360,13 +359,9 @@ impl AppProject { pub async fn app_entry_point_to_route( app_project: Vc, entrypoint: AppEntrypoint, - pathname: String, ) -> Vc { match entrypoint { - AppEntrypoint::AppPage { - original_name, - loader_tree, - } => Route::AppPage { + AppEntrypoint::AppPage { page, loader_tree } => Route::AppPage { html_endpoint: Vc::upcast( AppEndpoint { ty: AppEndpointType::Page { @@ -374,8 +369,7 @@ pub async fn app_entry_point_to_route( loader_tree, }, app_project, - pathname: pathname.clone(), - original_name: original_name.clone(), + page: page.clone(), } .cell(), ), @@ -386,22 +380,17 @@ pub async fn app_entry_point_to_route( loader_tree, }, app_project, - pathname, - original_name, + page, } .cell(), ), }, - AppEntrypoint::AppRoute { - original_name, - path, - } => Route::AppRoute { + AppEntrypoint::AppRoute { page, path } => Route::AppRoute { endpoint: Vc::upcast( AppEndpoint { ty: AppEndpointType::Route { path }, app_project, - pathname, - original_name, + page, } .cell(), ), @@ -431,8 +420,7 @@ enum AppEndpointType { struct AppEndpoint { ty: AppEndpointType, app_project: Vc, - pathname: String, - original_name: String, + page: AppPage, } #[turbo_tasks::value_impl] @@ -444,8 +432,7 @@ impl AppEndpoint { self.app_project.edge_rsc_module_context(), loader_tree, self.app_project.app_dir(), - self.pathname.clone(), - self.original_name.clone(), + self.page.clone(), self.app_project.project().project_path(), ) } @@ -456,8 +443,7 @@ impl AppEndpoint { self.app_project.rsc_module_context(), self.app_project.edge_rsc_module_context(), Vc::upcast(FileSource::new(path)), - self.pathname.clone(), - self.original_name.clone(), + self.page.clone(), self.app_project.project().project_path(), ) } diff --git a/packages/next-swc/crates/next-build/src/next_app/app_entries.rs b/packages/next-swc/crates/next-build/src/next_app/app_entries.rs index 973b30ea7d54..9c7271ecdd66 100644 --- a/packages/next-swc/crates/next-build/src/next_app/app_entries.rs +++ b/packages/next-swc/crates/next-build/src/next_app/app_entries.rs @@ -187,31 +187,23 @@ pub async fn get_app_entries( let mut entries = entrypoints .await? .iter() - .map(|(pathname, entrypoint)| async move { + .map(|(_, entrypoint)| async move { Ok(match entrypoint { - Entrypoint::AppPage { - original_name, - loader_tree, - } => get_app_page_entry( + Entrypoint::AppPage { page, loader_tree } => get_app_page_entry( rsc_context, // TODO add edge support rsc_context, *loader_tree, app_dir, - pathname.clone(), - original_name.clone(), + page.clone(), project_root, ), - Entrypoint::AppRoute { - original_name, - path, - } => get_app_route_entry( + Entrypoint::AppRoute { page, path } => get_app_route_entry( rsc_context, // TODO add edge support rsc_context, Vc::upcast(FileSource::new(*path)), - pathname.clone(), - original_name.clone(), + page.clone(), project_root, ), }) diff --git a/packages/next-swc/crates/next-core/Cargo.toml b/packages/next-swc/crates/next-core/Cargo.toml index 5b50aff1185c..38bcb02a3cda 100644 --- a/packages/next-swc/crates/next-core/Cargo.toml +++ b/packages/next-swc/crates/next-core/Cargo.toml @@ -39,6 +39,7 @@ turbopack-binding = { workspace = true, features = [ "__turbo_tasks_hash", "__turbopack", "__turbopack_build", + "__turbopack_cli_utils", "__turbopack_core", "__turbopack_dev", "__turbopack_dev_server", diff --git a/packages/next-swc/crates/next-core/js/tsconfig.json b/packages/next-swc/crates/next-core/js/tsconfig.json index a10e86411737..f7c18306acf8 100644 --- a/packages/next-swc/crates/next-core/js/tsconfig.json +++ b/packages/next-swc/crates/next-core/js/tsconfig.json @@ -4,23 +4,19 @@ "strict": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - // interop constraints "allowSyntheticDefaultImports": true, "esModuleInterop": true, - // js support "allowJs": true, "checkJs": false, - // environment "jsx": "react-jsx", "lib": ["ESNext", "DOM"], "target": "esnext", - // modules "baseUrl": ".", - "module": "esnext", + "module": "node16", "moduleResolution": "node16", "paths": { "@vercel/turbopack-next/*": ["src/*"], @@ -31,7 +27,6 @@ }, "resolveJsonModule": true, "types": ["react/next"], - // emit "noEmit": true, "stripInternal": true diff --git a/packages/next-swc/crates/next-core/src/app_source.rs b/packages/next-swc/crates/next-core/src/app_source.rs index e5af263c3d6b..cf35f3795f88 100644 --- a/packages/next-swc/crates/next-core/src/app_source.rs +++ b/packages/next-swc/crates/next-core/src/app_source.rs @@ -65,7 +65,7 @@ use crate::{ fallback::get_fallback_page, loader_tree::{LoaderTreeModule, ServerComponentTransition}, mode::NextMode, - next_app::UnsupportedDynamicMetadataIssue, + next_app::{AppPage, AppPath, PathSegment, UnsupportedDynamicMetadataIssue}, next_client::{ context::{ get_client_assets_path, get_client_module_options_context, @@ -95,31 +95,28 @@ use crate::{ util::{render_data, NextRuntime}, }; -fn pathname_to_segments(pathname: &str) -> Result<(Vec, RouteType)> { +fn app_path_to_segments(path: &AppPath) -> Result<(Vec, RouteType)> { let mut segments = Vec::new(); - let mut split = pathname.split('/'); - while let Some(segment) = split.next() { - if segment.is_empty() - || (segment.starts_with('(') && segment.ends_with(')') || segment.starts_with('@')) - { - // ignore - } else if segment.starts_with("[[...") && segment.ends_with("]]") - || segment.starts_with("[...") && segment.ends_with(']') - { - // (optional) catch all segment - if split.remainder().is_some() { - bail!( - "Invalid route {}, catch all segment must be the last segment", - pathname - ) + let mut iter = path.iter().peekable(); + + while let Some(segment) = iter.next() { + match segment { + PathSegment::Static(s) => { + segments.push(BaseSegment::Static(s.to_string())); + } + PathSegment::Dynamic(_) => { + segments.push(BaseSegment::Dynamic); + } + PathSegment::CatchAll(_) | PathSegment::OptionalCatchAll(_) => { + if iter.peek().is_some() { + bail!( + "Invalid route {}, catch all segment must be the last segment", + path + ) + } + + return Ok((segments, RouteType::CatchAll)); } - return Ok((segments, RouteType::CatchAll)); - } else if segment.starts_with('[') || segment.ends_with(']') { - // dynamic segment - segments.push(BaseSegment::Dynamic); - } else { - // normal segment - segments.push(BaseSegment::Static(segment.to_string())); } } Ok((segments, RouteType::Exact)) @@ -654,12 +651,12 @@ pub async fn create_app_source( let entrypoints = entrypoints.await?; let mut sources: Vec<_> = entrypoints .iter() - .map(|(pathname, entrypoint)| match *entrypoint { + .map(|(_, entrypoint)| match *entrypoint { Entrypoint::AppPage { - original_name: _, + ref page, loader_tree, } => create_app_page_source_for_route( - pathname.clone(), + page.clone(), loader_tree, context_ssr, context, @@ -672,11 +669,8 @@ pub async fn create_app_source( output_path, render_data, ), - Entrypoint::AppRoute { - original_name: _, - path, - } => create_app_route_source_for_route( - pathname.clone(), + Entrypoint::AppRoute { ref page, path } => create_app_route_source_for_route( + page.clone(), path, context_ssr, project_path, @@ -696,7 +690,7 @@ pub async fn create_app_source( .collect(); if let Some(&Entrypoint::AppPage { - original_name: _, + page: _, loader_tree, }) = entrypoints.get("/_not-found") { @@ -769,7 +763,7 @@ async fn create_global_metadata_source( #[turbo_tasks::function] async fn create_app_page_source_for_route( - pathname: String, + page: AppPage, loader_tree: Vc, context_ssr: Vc, context: Vc, @@ -782,11 +776,12 @@ async fn create_app_page_source_for_route( intermediate_output_path_root: Vc, render_data: Vc, ) -> Result>> { - let pathname_vc = Vc::cell(pathname.clone()); + let app_path = AppPath::from(page.clone()); + let pathname_vc = Vc::cell(app_path.to_string()); let params_matcher = NextParamsMatcher::new(pathname_vc); - let (base_segments, route_type) = pathname_to_segments(&pathname)?; + let (base_segments, route_type) = app_path_to_segments(&app_path)?; let source = create_node_rendered_source( project_path, @@ -814,7 +809,7 @@ async fn create_app_page_source_for_route( should_debug("app_source"), ); - Ok(source.issue_file_path(app_dir, format!("Next.js App Page Route {pathname}"))) + Ok(source.issue_file_path(app_dir, format!("Next.js App Page Route {app_path}"))) } #[turbo_tasks::function] @@ -864,7 +859,7 @@ async fn create_app_not_found_page_source( #[turbo_tasks::function] async fn create_app_route_source_for_route( - pathname: String, + page: AppPage, entry_path: Vc, context_ssr: Vc, project_path: Vc, @@ -875,11 +870,12 @@ async fn create_app_route_source_for_route( intermediate_output_path_root: Vc, render_data: Vc, ) -> Result>> { - let pathname_vc = Vc::cell(pathname.to_string()); + let app_path = AppPath::from(page.clone()); + let pathname_vc = Vc::cell(app_path.to_string()); let params_matcher = NextParamsMatcher::new(pathname_vc); - let (base_segments, route_type) = pathname_to_segments(&pathname)?; + let (base_segments, route_type) = app_path_to_segments(&app_path)?; let source = create_node_api_source( project_path, @@ -906,7 +902,7 @@ async fn create_app_route_source_for_route( should_debug("app_source"), ); - Ok(source.issue_file_path(app_dir, format!("Next.js App Route {pathname}"))) + Ok(source.issue_file_path(app_dir, format!("Next.js App Route {app_path}"))) } /// The renderer for pages in app directory diff --git a/packages/next-swc/crates/next-core/src/app_structure.rs b/packages/next-swc/crates/next-core/src/app_structure.rs index c09ccdc644c9..a3597079af6c 100644 --- a/packages/next-swc/crates/next-core/src/app_structure.rs +++ b/packages/next-swc/crates/next-core/src/app_structure.rs @@ -1,7 +1,11 @@ use std::collections::{BTreeMap, HashMap}; use anyhow::{bail, Result}; -use indexmap::{indexmap, map::Entry, IndexMap}; +use indexmap::{ + indexmap, + map::{Entry, OccupiedEntry}, + IndexMap, +}; use once_cell::sync::Lazy; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -14,7 +18,11 @@ use turbopack_binding::{ turbopack::core::issue::{Issue, IssueExt, IssueSeverity}, }; -use crate::{next_config::NextConfig, next_import_map::get_next_package}; +use crate::{ + next_app::{AppPage, AppPath}, + next_config::NextConfig, + next_import_map::get_next_package, +}; /// A final route in the app directory. #[turbo_tasks::value] @@ -447,11 +455,11 @@ async fn merge_loader_trees( )] pub enum Entrypoint { AppPage { - original_name: String, + page: AppPage, loader_tree: Vc, }, AppRoute { - original_name: String, + page: AppPage, path: Vc, }, } @@ -487,131 +495,119 @@ async fn add_parallel_route( Ok(()) } +fn conflict_issue( + app_dir: Vc, + e: &OccupiedEntry, + a: &str, + b: &str, + value_a: &AppPage, + value_b: &AppPage, +) { + let item_names = if a == b { + format!("{}s", a) + } else { + format!("{} and {}", a, b) + }; + + DirectoryTreeIssue { + app_dir, + message: Vc::cell(format!( + "Conflicting {} at {}: {a} at {value_a} and {b} at {value_b}", + item_names, + e.key(), + )), + severity: IssueSeverity::Error.cell(), + } + .cell() + .emit(); +} + async fn add_app_page( app_dir: Vc, result: &mut IndexMap, - key: String, - original_name: String, + page: AppPage, loader_tree: Vc, ) -> Result<()> { - match result.entry(key) { - Entry::Occupied(mut e) => { - let value = e.get(); - match value { - Entrypoint::AppPage { - original_name: existing_original_name, - .. - } => { - if *existing_original_name != original_name { - DirectoryTreeIssue { - app_dir, - message: Vc::cell(format!( - "Conflicting pages at {}: {existing_original_name} and \ - {original_name}", - e.key() - )), - severity: IssueSeverity::Error.cell(), - } - .cell() - .emit(); - return Ok(()); - } - if let Entrypoint::AppPage { - loader_tree: value, .. - } = e.get_mut() - { - *value = merge_loader_trees(app_dir, *value, loader_tree) - .resolve() - .await?; - } - } - Entrypoint::AppRoute { - original_name: existing_original_name, - .. - } => { - DirectoryTreeIssue { - app_dir, - message: Vc::cell(format!( - "Conflicting page and route at {}: route at {existing_original_name} \ - and page at {original_name}", - e.key() - )), - severity: IssueSeverity::Error.cell(), - } - .cell() - .emit(); - return Ok(()); - } + let pathname = AppPath::from(page.clone()); + + let mut e = match result.entry(format!("{pathname}")) { + Entry::Occupied(e) => e, + Entry::Vacant(e) => { + e.insert(Entrypoint::AppPage { page, loader_tree }); + return Ok(()); + } + }; + + let conflict = |existing_name: &str, existing_page: &AppPage| { + conflict_issue(app_dir, &e, "page", existing_name, &page, existing_page); + }; + + let value = e.get(); + match value { + Entrypoint::AppPage { + page: existing_page, + .. + } => { + if *existing_page != page { + conflict("page", existing_page); + return Ok(()); + } + + if let Entrypoint::AppPage { + loader_tree: value, .. + } = e.get_mut() + { + *value = merge_loader_trees(app_dir, *value, loader_tree) + .resolve() + .await?; } } - Entry::Vacant(e) => { - e.insert(Entrypoint::AppPage { - original_name, - loader_tree, - }); + Entrypoint::AppRoute { + page: existing_page, + .. + } => { + conflict("route", existing_page); } } + Ok(()) } -async fn add_app_route( +fn add_app_route( app_dir: Vc, result: &mut IndexMap, - key: String, - original_name: String, + page: AppPage, path: Vc, -) -> Result<()> { - match result.entry(key) { - Entry::Occupied(mut e) => { - let value = e.get(); - match value { - Entrypoint::AppPage { - original_name: existing_original_name, - .. - } => { - DirectoryTreeIssue { - app_dir, - message: Vc::cell(format!( - "Conflicting route and page at {}: route at {original_name} and page \ - at {existing_original_name}", - e.key() - )), - severity: IssueSeverity::Error.cell(), - } - .cell() - .emit(); - } - Entrypoint::AppRoute { - original_name: existing_original_name, - .. - } => { - DirectoryTreeIssue { - app_dir, - message: Vc::cell(format!( - "Conflicting routes at {}: {existing_original_name} and \ - {original_name}", - e.key() - )), - severity: IssueSeverity::Error.cell(), - } - .cell() - .emit(); - return Ok(()); - } - } - *e.get_mut() = Entrypoint::AppRoute { - original_name, - path, - }; - } +) { + let pathname = AppPath::from(page.clone()); + + let e = match result.entry(format!("{pathname}")) { + Entry::Occupied(e) => e, Entry::Vacant(e) => { - e.insert(Entrypoint::AppRoute { - original_name, - path, - }); + e.insert(Entrypoint::AppRoute { page, path }); + return; + } + }; + + let conflict = |existing_name: &str, existing_page: &AppPage| { + conflict_issue(app_dir, &e, "route", existing_name, &page, existing_page); + }; + + let value = e.get(); + match value { + Entrypoint::AppPage { + page: existing_page, + .. + } => { + conflict("page", existing_page); + } + Entrypoint::AppRoute { + page: existing_page, + .. + } => { + conflict("route", existing_page); } } - Ok(()) } #[turbo_tasks::function] @@ -627,13 +623,7 @@ fn directory_tree_to_entrypoints( app_dir: Vc, directory_tree: Vc, ) -> Vc { - directory_tree_to_entrypoints_internal( - app_dir, - "".to_string(), - directory_tree, - "/".to_string(), - "/".to_string(), - ) + directory_tree_to_entrypoints_internal(app_dir, "".to_string(), directory_tree, AppPage::new()) } #[turbo_tasks::function] @@ -641,8 +631,7 @@ async fn directory_tree_to_entrypoints_internal( app_dir: Vc, directory_name: String, directory_tree: Vc, - path_prefix: String, - original_name_prefix: String, + app_page: AppPage, ) -> Result> { let mut result = IndexMap::new(); @@ -657,8 +646,7 @@ async fn directory_tree_to_entrypoints_internal( add_app_page( app_dir, &mut result, - path_prefix.to_string(), - original_name_prefix.to_string(), + app_page.clone(), if current_level_is_parallel_route { LoaderTree { segment: "__PAGE__".to_string(), @@ -697,8 +685,7 @@ async fn directory_tree_to_entrypoints_internal( add_app_page( app_dir, &mut result, - path_prefix.to_string(), - original_name_prefix.to_string(), + app_page.clone(), if current_level_is_parallel_route { LoaderTree { segment: "__DEFAULT__".to_string(), @@ -734,17 +721,11 @@ async fn directory_tree_to_entrypoints_internal( } if let Some(route) = components.route { - add_app_route( - app_dir, - &mut result, - path_prefix.to_string(), - original_name_prefix.to_string(), - route, - ) - .await?; + add_app_route(app_dir, &mut result, app_page.clone(), route); } - if path_prefix == "/" { + // root path: / + if app_page.len() == 0 { // Next.js has this logic in "collect-app-paths", where the root not-found page // is considered as its own entry point. if let Some(_not_found) = components.not_found { @@ -766,22 +747,14 @@ async fn directory_tree_to_entrypoints_internal( } .cell(); - add_app_page( - app_dir, - &mut result, - "/not-found".to_string(), - "/not-found".to_string(), - dev_not_found_tree, - ) - .await?; - add_app_page( - app_dir, - &mut result, - "/_not-found".to_string(), - "/_not-found".to_string(), - dev_not_found_tree, - ) - .await?; + { + let app_page = app_page.clone_push_str("not-found")?; + add_app_page(app_dir, &mut result, app_page, dev_not_found_tree).await?; + } + { + let app_page = app_page.clone_push_str("_not-found")?; + add_app_page(app_dir, &mut result, app_page, dev_not_found_tree).await?; + } } else { // Create default not-found page for production if there's no customized // not-found @@ -803,55 +776,35 @@ async fn directory_tree_to_entrypoints_internal( } .cell(); - add_app_page( - app_dir, - &mut result, - "/_not-found".to_string(), - "/_not-found".to_string(), - prod_not_found_tree, - ) - .await?; + let app_page = app_page.clone_push_str("_not-found")?; + add_app_page(app_dir, &mut result, app_page, prod_not_found_tree).await?; } } for (subdir_name, &subdirectory) in subdirectories.iter() { - let is_route_group = subdir_name.starts_with('(') && subdir_name.ends_with(')'); let parallel_route_key = match_parallel_route(subdir_name); + + let mut app_page = app_page.clone(); + if parallel_route_key.is_none() { + app_page.push_str(subdir_name)?; + } + let map = directory_tree_to_entrypoints_internal( app_dir, subdir_name.to_string(), subdirectory, - if is_route_group || parallel_route_key.is_some() { - path_prefix.clone() - } else if path_prefix == "/" { - format!("/{subdir_name}") - } else { - format!("{path_prefix}/{subdir_name}") - }, - if parallel_route_key.is_some() { - original_name_prefix.clone() - } else if original_name_prefix == "/" { - format!("/{subdir_name}") - } else { - format!("{original_name_prefix}/{subdir_name}") - }, + app_page, ) .await?; - for (full_path, entrypoint) in map.iter() { + + for (_, entrypoint) in map.iter() { match *entrypoint { Entrypoint::AppPage { - ref original_name, + ref page, loader_tree, } => { if current_level_is_parallel_route { - add_app_page( - app_dir, - &mut result, - full_path.clone(), - original_name.clone(), - loader_tree, - ) - .await?; + add_app_page(app_dir, &mut result, page.clone(), loader_tree).await?; } else { let key = parallel_route_key.unwrap_or("children").to_string(); let child_loader_tree = LoaderTree { @@ -862,28 +815,11 @@ async fn directory_tree_to_entrypoints_internal( components: components.without_leafs().cell(), } .cell(); - add_app_page( - app_dir, - &mut result, - full_path.clone(), - original_name.clone(), - child_loader_tree, - ) - .await?; + add_app_page(app_dir, &mut result, page.clone(), child_loader_tree).await?; } } - Entrypoint::AppRoute { - ref original_name, - path, - } => { - add_app_route( - app_dir, - &mut result, - full_path.clone(), - original_name.clone(), - path, - ) - .await?; + Entrypoint::AppRoute { ref page, path } => { + add_app_route(app_dir, &mut result, page.clone(), path); } } } diff --git a/packages/next-swc/crates/next-core/src/next_app/app_favicon_entry.rs b/packages/next-swc/crates/next-core/src/next_app/app_favicon_entry.rs index 0f4df5eb01f3..b72fc9af399a 100644 --- a/packages/next-swc/crates/next-core/src/next_app/app_favicon_entry.rs +++ b/packages/next-swc/crates/next-core/src/next_app/app_favicon_entry.rs @@ -14,7 +14,10 @@ use turbopack_binding::{ }; use super::app_route_entry::get_app_route_entry; -use crate::{app_structure::MetadataItem, next_app::AppEntry}; +use crate::{ + app_structure::MetadataItem, + next_app::{AppEntry, AppPage, PageSegment}, +}; /// Computes the entry for a Next.js favicon file. #[turbo_tasks::function] @@ -57,7 +60,7 @@ pub async fn get_app_route_favicon_entry( const contentType = {content_type} const cacheControl = {cache_control} const buffer = Buffer.from({original_file_content_b64}, 'base64') - + export function GET() {{ return new NextResponse(buffer, {{ headers: {{ @@ -66,7 +69,7 @@ pub async fn get_app_route_favicon_entry( }}, }}) }} - + export const dynamic = 'force-static' "#, content_type = StringifyJs(&content_type), @@ -84,8 +87,7 @@ pub async fn get_app_route_favicon_entry( edge_context, Vc::upcast(source), // TODO(alexkirsz) Get this from the metadata? - "/favicon.ico".to_string(), - "/favicon.ico".to_string(), + AppPage(vec![PageSegment::Static("/favicon.ico".to_string())]), project_root, )) } diff --git a/packages/next-swc/crates/next-core/src/next_app/app_page_entry.rs b/packages/next-swc/crates/next-core/src/next_app/app_page_entry.rs index bde79e2c38b4..fd99d2566467 100644 --- a/packages/next-swc/crates/next-core/src/next_app/app_page_entry.rs +++ b/packages/next-swc/crates/next-core/src/next_app/app_page_entry.rs @@ -19,7 +19,7 @@ use crate::{ app_structure::LoaderTree, loader_tree::{LoaderTreeModule, ServerComponentTransition}, mode::NextMode, - next_app::UnsupportedDynamicMetadataIssue, + next_app::{AppPage, AppPath, UnsupportedDynamicMetadataIssue}, next_server_component::NextServerComponentTransition, parse_segment_config_from_loader_tree, util::{load_next_js_template, virtual_next_js_template_path, NextRuntime}, @@ -32,8 +32,7 @@ pub async fn get_app_page_entry( edge_context: Vc, loader_tree: Vc, app_dir: Vc, - pathname: String, - original_name: String, + page: AppPage, project_root: Vc, ) -> Result> { let config = parse_segment_config_from_loader_tree(loader_tree, Vc::upcast(nodejs_context)); @@ -79,6 +78,9 @@ pub async fn get_app_page_entry( let pages = pages.iter().map(|page| page.to_string()).try_join().await?; + let original_name = page.to_string(); + let pathname = AppPath::from(page.clone()).to_string(); + let original_page_name = get_original_page_name(&original_name); let template_file = "build/templates/app-page.js"; @@ -90,7 +92,7 @@ pub async fn get_app_page_entry( .to_str()? .replace( "\"VAR_DEFINITION_PAGE\"", - &StringifyJs(&original_name).to_string(), + &StringifyJs(&page.to_string()).to_string(), ) .replace( "\"VAR_DEFINITION_PATHNAME\"", diff --git a/packages/next-swc/crates/next-core/src/next_app/app_route_entry.rs b/packages/next-swc/crates/next-core/src/next_app/app_route_entry.rs index 45cf5470f40e..e60c963e0ece 100644 --- a/packages/next-swc/crates/next-core/src/next_app/app_route_entry.rs +++ b/packages/next-swc/crates/next-core/src/next_app/app_route_entry.rs @@ -21,7 +21,7 @@ use turbopack_binding::{ }; use crate::{ - next_app::AppEntry, + next_app::{AppEntry, AppPage, AppPath}, parse_segment_config_from_source, util::{load_next_js_template, virtual_next_js_template_path, NextRuntime}, }; @@ -32,8 +32,7 @@ pub async fn get_app_route_entry( nodejs_context: Vc, edge_context: Vc, source: Vc>, - pathname: String, - original_name: String, + page: AppPage, project_root: Vc, ) -> Result> { let config = parse_segment_config_from_source( @@ -52,6 +51,9 @@ pub async fn get_app_route_entry( let mut result = RopeBuilder::default(); + let original_name = page.to_string(); + let pathname = AppPath::from(page.clone()).to_string(); + let original_page_name = get_original_route_name(&original_name); let path = source.ident().path(); diff --git a/packages/next-swc/crates/next-core/src/next_app/mod.rs b/packages/next-swc/crates/next-core/src/next_app/mod.rs index 7f40ca34fb94..885ecdf4cd71 100644 --- a/packages/next-swc/crates/next-core/src/next_app/mod.rs +++ b/packages/next-swc/crates/next-core/src/next_app/mod.rs @@ -6,12 +6,283 @@ pub(crate) mod app_page_entry; pub(crate) mod app_route_entry; pub(crate) mod unsupported_dynamic_metadata_issue; -pub use app_client_references_chunks::{ - get_app_client_references_chunks, ClientReferenceChunks, ClientReferencesChunks, +use std::{ + fmt::{Display, Formatter, Write}, + ops::Deref, }; -pub use app_client_shared_chunks::get_app_client_shared_chunks; -pub use app_entry::AppEntry; -pub use app_favicon_entry::get_app_route_favicon_entry; -pub use app_page_entry::get_app_page_entry; -pub use app_route_entry::get_app_route_entry; -pub use unsupported_dynamic_metadata_issue::UnsupportedDynamicMetadataIssue; + +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; +use turbo_tasks::{trace::TraceRawVcs, TaskInput}; + +pub use crate::next_app::{ + app_client_references_chunks::{ + get_app_client_references_chunks, ClientReferenceChunks, ClientReferencesChunks, + }, + app_client_shared_chunks::get_app_client_shared_chunks, + app_entry::AppEntry, + app_favicon_entry::get_app_route_favicon_entry, + app_page_entry::get_app_page_entry, + app_route_entry::get_app_route_entry, + unsupported_dynamic_metadata_issue::UnsupportedDynamicMetadataIssue, +}; + +#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, TaskInput, TraceRawVcs)] +pub enum PageSegment { + Static(String), + Dynamic(String), + CatchAll(String), + OptionalCatchAll(String), + Group(String), + Parallel(String), + PageType(PageType), +} + +impl PageSegment { + pub fn parse(segment: &str) -> Result { + if segment.is_empty() { + bail!("empty segments are not allowed"); + } + + if segment.contains('/') { + bail!("slashes are not allowed in segments"); + } + + if let Some(s) = segment.strip_prefix('(').and_then(|s| s.strip_suffix(')')) { + return Ok(PageSegment::Group(s.to_string())); + } + + if let Some(s) = segment.strip_prefix('@') { + return Ok(PageSegment::Parallel(s.to_string())); + } + + if let Some(s) = segment + .strip_prefix("[[...") + .and_then(|s| s.strip_suffix("]]")) + { + return Ok(PageSegment::OptionalCatchAll(s.to_string())); + } + + if let Some(s) = segment + .strip_prefix("[...") + .and_then(|s| s.strip_suffix(']')) + { + return Ok(PageSegment::CatchAll(s.to_string())); + } + + if let Some(s) = segment.strip_prefix('[').and_then(|s| s.strip_suffix(']')) { + return Ok(PageSegment::Dynamic(s.to_string())); + } + + Ok(PageSegment::Static(segment.to_string())) + } +} + +impl Display for PageSegment { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PageSegment::Static(s) => f.write_str(s), + PageSegment::Dynamic(s) => { + f.write_char('[')?; + f.write_str(s)?; + f.write_char(']') + } + PageSegment::CatchAll(s) => { + f.write_str("[...")?; + f.write_str(s)?; + f.write_char(']') + } + PageSegment::OptionalCatchAll(s) => { + f.write_str("[[...")?; + f.write_str(s)?; + f.write_str("]]") + } + PageSegment::Group(s) => { + f.write_char('(')?; + f.write_str(s)?; + f.write_char(')') + } + PageSegment::Parallel(s) => { + f.write_char('@')?; + f.write_str(s) + } + PageSegment::PageType(s) => Display::fmt(s, f), + } + } +} + +#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, TaskInput, TraceRawVcs)] +pub enum PageType { + Page, + Route, +} + +impl Display for PageType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + PageType::Page => "page", + PageType::Route => "route", + }) + } +} + +/// Describes the pathname including all internal modifiers such as +/// intercepting routes, parallel routes and route/page suffixes that are not +/// part of the pathname. +#[derive( + Clone, Debug, Hash, PartialEq, Eq, Default, Serialize, Deserialize, TaskInput, TraceRawVcs, +)] +pub struct AppPage(pub Vec); + +impl AppPage { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, segment: PageSegment) -> Result<()> { + if matches!( + self.0.last(), + Some(PageSegment::CatchAll(..) | PageSegment::OptionalCatchAll(..)) + ) && !matches!(segment, PageSegment::PageType(..)) + { + bail!( + "Invalid segment {}, catch all segment must be the last segment", + segment + ) + } + + self.0.push(segment); + Ok(()) + } + + pub fn push_str(&mut self, segment: &str) -> Result<()> { + if segment.is_empty() { + return Ok(()); + } + + self.push(PageSegment::parse(segment)?) + } + + pub fn clone_push(&self, segment: PageSegment) -> Result { + let mut cloned = self.clone(); + cloned.push(segment)?; + Ok(cloned) + } + + pub fn clone_push_str(&self, segment: &str) -> Result { + let mut cloned = self.clone(); + cloned.push_str(segment)?; + Ok(cloned) + } + + pub fn parse(page: &str) -> Result { + let mut app_page = Self::new(); + + for segment in page.split('/') { + app_page.push_str(segment)?; + } + + Ok(app_page) + } +} + +impl Display for AppPage { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.0.is_empty() { + return f.write_char('/'); + } + + for segment in &self.0 { + f.write_char('/')?; + Display::fmt(segment, f)?; + } + + Ok(()) + } +} + +impl Deref for AppPage { + type Target = [PageSegment]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, TaskInput, TraceRawVcs)] +pub enum PathSegment { + Static(String), + Dynamic(String), + CatchAll(String), + OptionalCatchAll(String), +} + +impl Display for PathSegment { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PathSegment::Static(s) => f.write_str(s), + PathSegment::Dynamic(s) => { + f.write_char('[')?; + f.write_str(s)?; + f.write_char(']') + } + PathSegment::CatchAll(s) => { + f.write_str("[...")?; + f.write_str(s)?; + f.write_char(']') + } + PathSegment::OptionalCatchAll(s) => { + f.write_str("[[...")?; + f.write_str(s)?; + f.write_str("]]") + } + } + } +} + +/// The pathname (including dynamic placeholders) for a route to resolve. +#[derive( + Clone, Debug, Hash, PartialEq, Eq, Default, Serialize, Deserialize, TaskInput, TraceRawVcs, +)] +pub struct AppPath(pub Vec); + +impl Deref for AppPath { + type Target = [PathSegment]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Display for AppPath { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if self.0.is_empty() { + return f.write_char('/'); + } + + for segment in &self.0 { + f.write_char('/')?; + Display::fmt(segment, f)?; + } + + Ok(()) + } +} + +impl From for AppPath { + fn from(value: AppPage) -> Self { + AppPath( + value + .0 + .into_iter() + .filter_map(|segment| match segment { + PageSegment::Static(s) => Some(PathSegment::Static(s)), + PageSegment::Dynamic(s) => Some(PathSegment::Dynamic(s)), + PageSegment::CatchAll(s) => Some(PathSegment::CatchAll(s)), + PageSegment::OptionalCatchAll(s) => Some(PathSegment::OptionalCatchAll(s)), + _ => None, + }) + .collect(), + ) + } +} diff --git a/packages/next-swc/crates/next-dev-tests/tests/integration/next/styled-jsx/input/index.js b/packages/next-swc/crates/next-dev-tests/tests/integration/next/styled-jsx/input/index.js deleted file mode 100644 index f2e030e055fa..000000000000 --- a/packages/next-swc/crates/next-dev-tests/tests/integration/next/styled-jsx/input/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import TestRenderer from 'react-test-renderer' - -describe('styled-jsx', () => { - it('compiles away - - ) - - expect(test.toJSON()).toMatchObject({ - children: ['This should be color: red'], - props: { - className: /jsx\-[0-9a-f]+/, - }, - type: 'span', - }) - }) -}) diff --git a/packages/next-swc/package.json b/packages/next-swc/package.json index 23e310684c26..87ac07c84398 100644 --- a/packages/next-swc/package.json +++ b/packages/next-swc/package.json @@ -1,6 +1,6 @@ { "name": "@next/swc", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "private": true, "scripts": { "clean": "node ../../scripts/rm.mjs native", diff --git a/packages/next/package.json b/packages/next/package.json index a5d5ac891d2b..28fbd949a897 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -1,6 +1,6 @@ { "name": "next", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "description": "The React Framework", "main": "./dist/server/next.js", "license": "MIT", @@ -89,7 +89,7 @@ ] }, "dependencies": { - "@next/env": "13.4.20-canary.18", + "@next/env": "13.4.20-canary.20", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -144,11 +144,11 @@ "@mswjs/interceptors": "0.23.0", "@napi-rs/cli": "2.16.2", "@napi-rs/triples": "1.1.0", - "@next/polyfill-module": "13.4.20-canary.18", - "@next/polyfill-nomodule": "13.4.20-canary.18", - "@next/react-dev-overlay": "13.4.20-canary.18", - "@next/react-refresh-utils": "13.4.20-canary.18", - "@next/swc": "13.4.20-canary.18", + "@next/polyfill-module": "13.4.20-canary.20", + "@next/polyfill-nomodule": "13.4.20-canary.20", + "@next/react-dev-overlay": "13.4.20-canary.20", + "@next/react-refresh-utils": "13.4.20-canary.20", + "@next/swc": "13.4.20-canary.20", "@opentelemetry/api": "1.4.1", "@playwright/test": "^1.35.1", "@segment/ajv-human-errors": "2.1.2", @@ -172,7 +172,7 @@ "@types/fresh": "0.5.0", "@types/glob": "7.1.1", "@types/jsonwebtoken": "9.0.0", - "@types/lodash": "4.14.149", + "@types/lodash": "4.14.198", "@types/lodash.curry": "4.1.6", "@types/lru-cache": "5.1.0", "@types/micromatch": "4.0.2", @@ -253,7 +253,7 @@ "lru-cache": "5.1.1", "micromatch": "4.0.4", "mini-css-extract-plugin": "2.4.3", - "msw": "^1.2.2", + "msw": "1.3.0", "nanoid": "3.1.32", "native-url": "0.3.4", "neo-async": "2.6.1", diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 2382d6a83925..db181118a674 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -33,12 +33,12 @@ import { ACTION_RESTORE, ACTION_SERVER_ACTION, ACTION_SERVER_PATCH, + Mutable, PrefetchKind, ReducerActions, RouterChangeByServerResponse, RouterNavigate, ServerActionDispatcher, - ServerActionMutable, } from './router-reducer/router-reducer-types' import { createHrefFromUrl } from './router-reducer/create-href-from-url' import { @@ -73,7 +73,7 @@ export function getServerActionDispatcher() { return globalServerActionDispatcher } -let globalServerActionMutable: ServerActionMutable['globalMutable'] = { +let globalMutable: Mutable['globalMutable'] = { refresh: () => {}, // noop until the router is initialized } @@ -145,7 +145,7 @@ function useServerActionDispatcher(dispatch: React.Dispatch) { dispatch({ ...actionPayload, type: ACTION_SERVER_ACTION, - mutable: { globalMutable: globalServerActionMutable }, + mutable: { globalMutable }, cache: createEmptyCacheNode(), }) }) @@ -174,7 +174,7 @@ function useChangeByServerResponse( previousTree, overrideCanonicalUrl, cache: createEmptyCacheNode(), - mutable: {}, + mutable: { globalMutable }, }) }) }, @@ -186,7 +186,7 @@ function useNavigate(dispatch: React.Dispatch): RouterNavigate { return useCallback( (href, navigateType, forceOptimisticNavigation, shouldScroll) => { const url = new URL(addBasePath(href), location.href) - globalServerActionMutable.pendingNavigatePath = href + globalMutable.pendingNavigatePath = href return dispatch({ type: ACTION_NAVIGATE, @@ -197,7 +197,7 @@ function useNavigate(dispatch: React.Dispatch): RouterNavigate { shouldScroll: shouldScroll ?? true, navigateType, cache: createEmptyCacheNode(), - mutable: {}, + mutable: { globalMutable }, }) }, [dispatch] @@ -322,7 +322,7 @@ function Router({ dispatch({ type: ACTION_REFRESH, cache: createEmptyCacheNode(), - mutable: {}, + mutable: { globalMutable }, origin: window.location.origin, }) }) @@ -338,7 +338,7 @@ function Router({ dispatch({ type: ACTION_FAST_REFRESH, cache: createEmptyCacheNode(), - mutable: {}, + mutable: { globalMutable }, origin: window.location.origin, }) }) @@ -357,7 +357,7 @@ function Router({ }, [appRouter]) useEffect(() => { - globalServerActionMutable.refresh = appRouter.refresh + globalMutable.refresh = appRouter.refresh }, [appRouter.refresh]) if (process.env.NODE_ENV !== 'production') { @@ -409,11 +409,16 @@ function Router({ // in . At least I hope so. (It will run twice in dev strict mode, // but that's... fine?) if (pushRef.mpaNavigation) { - const location = window.location - if (pushRef.pendingPush) { - location.assign(canonicalUrl) - } else { - location.replace(canonicalUrl) + // if there's a re-render, we don't want to trigger another redirect if one is already in flight to the same URL + if (globalMutable.pendingMpaPath !== canonicalUrl) { + const location = window.location + if (pushRef.pendingPush) { + location.assign(canonicalUrl) + } else { + location.replace(canonicalUrl) + } + + globalMutable.pendingMpaPath = canonicalUrl } // TODO-APP: Should we listen to navigateerror here to catch failed // navigations somehow? And should we call window.stop() if a SPA navigation diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.test.tsx b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.test.tsx index 6ff039d14cf5..db40adfa3c5b 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.test.tsx +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.test.tsx @@ -106,6 +106,10 @@ const getInitialRouterStateTree = (): FlightRouterState => [ true, ] +const globalMutable = { + refresh: () => {}, +} + async function runPromiseThrowChain(fn: any): Promise { try { return await fn() @@ -194,7 +198,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } const newState = await runPromiseThrowChain(() => @@ -438,7 +442,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -633,7 +637,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -792,7 +796,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -948,7 +952,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -1147,7 +1151,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -1317,7 +1321,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -1630,7 +1634,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => navigateReducer(state, action)) @@ -1841,6 +1845,7 @@ describe('navigateReducer', () => { hashFragment: '#hash', pendingPush: true, shouldScroll: true, + globalMutable, }, } @@ -1983,7 +1988,7 @@ describe('navigateReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } const newState = await runPromiseThrowChain(() => diff --git a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.test.tsx b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.test.tsx index 1ffbb376e2f9..90ce7dc9423a 100644 --- a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.test.tsx +++ b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.test.tsx @@ -66,6 +66,10 @@ const getInitialRouterStateTree = (): FlightRouterState => [ true, ] +const globalMutable = { + refresh: () => {}, +} + async function runPromiseThrowChain(fn: any): Promise { try { return await fn() @@ -139,7 +143,7 @@ describe('refreshReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, origin: new URL('/linking', 'https://localhost').origin, } @@ -300,7 +304,7 @@ describe('refreshReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, origin: new URL('/linking', 'https://localhost').origin, } @@ -487,7 +491,7 @@ describe('refreshReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, origin: new URL('/linking', 'https://localhost').origin, } @@ -723,7 +727,7 @@ describe('refreshReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, origin: new URL('/linking', 'https://localhost').origin, } diff --git a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.test.tsx b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.test.tsx index 37ef29296c89..db72a92fce74 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.test.tsx +++ b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.test.tsx @@ -7,6 +7,10 @@ import type { const buildId = 'development' +const globalMutable = { + refresh: () => {}, +} + jest.mock('../fetch-server-response', () => { const flightData: FlightData = [ [ @@ -184,7 +188,7 @@ describe('serverPatchReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } const newState = await runPromiseThrowChain(() => @@ -375,7 +379,7 @@ describe('serverPatchReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } await runPromiseThrowChain(() => serverPatchReducer(state, action)) @@ -514,7 +518,7 @@ describe('serverPatchReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } const state = createInitialRouterState({ @@ -556,7 +560,7 @@ describe('serverPatchReducer', () => { subTreeData: null, parallelRoutes: new Map(), }, - mutable: {}, + mutable: { globalMutable }, } const newState = await runPromiseThrowChain(() => diff --git a/packages/next/src/client/components/router-reducer/router-reducer-types.ts b/packages/next/src/client/components/router-reducer/router-reducer-types.ts index 48840a29db6d..284628dbf909 100644 --- a/packages/next/src/client/components/router-reducer/router-reducer-types.ts +++ b/packages/next/src/client/components/router-reducer/router-reducer-types.ts @@ -38,11 +38,15 @@ export interface Mutable { prefetchCache?: AppRouterState['prefetchCache'] hashFragment?: string shouldScroll?: boolean + globalMutable: { + pendingNavigatePath?: string + pendingMpaPath?: string + refresh: () => void + } } export interface ServerActionMutable extends Mutable { inFlightServerAction?: Promise | null - globalMutable: { pendingNavigatePath?: string; refresh: () => void } actionResultResolved?: boolean } diff --git a/packages/next/src/compiled/debug/index.js b/packages/next/src/compiled/debug/index.js index 0bdf3d36ef5d..853a1a2db787 100644 --- a/packages/next/src/compiled/debug/index.js +++ b/packages/next/src/compiled/debug/index.js @@ -1 +1 @@ -(()=>{var e={237:(e,t,r)=>{t.log=log;t.formatArgs=formatArgs;t.save=save;t.load=load;t.useColors=useColors;t.storage=localstorage();t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function useColors(){if(typeof window!=="undefined"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs)){return true}if(typeof navigator!=="undefined"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)){return false}return typeof document!=="undefined"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window!=="undefined"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator!=="undefined"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||typeof navigator!=="undefined"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function formatArgs(t){t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff);if(!this.useColors){return}const r="color: "+this.color;t.splice(1,0,r,"color: inherit");let s=0;let n=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{if(e==="%%"){return}s++;if(e==="%c"){n=s}}));t.splice(n,0,r)}function log(...e){return typeof console==="object"&&console.log&&console.log(...e)}function save(e){try{if(e){t.storage.setItem("debug",e)}else{t.storage.removeItem("debug")}}catch(e){}}function load(){let e;try{e=t.storage.getItem("debug")}catch(e){}if(!e&&typeof process!=="undefined"&&"env"in process){e=process.env.DEBUG}return e}function localstorage(){try{return localStorage}catch(e){}}e.exports=r(573)(t);const{formatters:s}=e.exports;s.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},573:(e,t,r)=>{function setup(e){createDebug.debug=createDebug;createDebug.default=createDebug;createDebug.coerce=coerce;createDebug.disable=disable;createDebug.enable=enable;createDebug.enabled=enabled;createDebug.humanize=r(79);Object.keys(e).forEach((t=>{createDebug[t]=e[t]}));createDebug.instances=[];createDebug.names=[];createDebug.skips=[];createDebug.formatters={};function selectColor(e){let t=0;for(let r=0;r{if(t==="%%"){return t}o++;const n=createDebug.formatters[s];if(typeof n==="function"){const s=e[o];t=n.call(r,s);e.splice(o,1);o--}return t}));createDebug.formatArgs.call(r,e);const c=r.log||createDebug.log;c.apply(r,e)}debug.namespace=e;debug.enabled=createDebug.enabled(e);debug.useColors=createDebug.useColors();debug.color=selectColor(e);debug.destroy=destroy;debug.extend=extend;if(typeof createDebug.init==="function"){createDebug.init(debug)}createDebug.instances.push(debug);return debug}function destroy(){const e=createDebug.instances.indexOf(this);if(e!==-1){createDebug.instances.splice(e,1);return true}return false}function extend(e,t){const r=createDebug(this.namespace+(typeof t==="undefined"?":":t)+e);r.log=this.log;return r}function enable(e){createDebug.save(e);createDebug.names=[];createDebug.skips=[];let t;const r=(typeof e==="string"?e:"").split(/[\s,]+/);const s=r.length;for(t=0;t"-"+e))].join(",");createDebug.enable("");return e}function enabled(e){if(e[e.length-1]==="*"){return true}let t;let r;for(t=0,r=createDebug.skips.length;t{if(typeof process==="undefined"||process.type==="renderer"||process.browser===true||process.__nwjs){e.exports=r(237)}else{e.exports=r(354)}},354:(e,t,r)=>{const s=r(224);const n=r(837);t.init=init;t.log=log;t.formatArgs=formatArgs;t.save=save;t.load=load;t.useColors=useColors;t.colors=[6,2,3,4,5,1];try{const e=r(220);if(e&&(e.stderr||e).level>=2){t.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221]}}catch(e){}t.inspectOpts=Object.keys(process.env).filter((e=>/^debug_/i.test(e))).reduce(((e,t)=>{const r=t.substring(6).toLowerCase().replace(/_([a-z])/g,((e,t)=>t.toUpperCase()));let s=process.env[t];if(/^(yes|on|true|enabled)$/i.test(s)){s=true}else if(/^(no|off|false|disabled)$/i.test(s)){s=false}else if(s==="null"){s=null}else{s=Number(s)}e[r]=s;return e}),{});function useColors(){return"colors"in t.inspectOpts?Boolean(t.inspectOpts.colors):s.isatty(process.stderr.fd)}function formatArgs(t){const{namespace:r,useColors:s}=this;if(s){const s=this.color;const n="[3"+(s<8?s:"8;5;"+s);const o=` ${n};1m${r} `;t[0]=o+t[0].split("\n").join("\n"+o);t.push(n+"m+"+e.exports.humanize(this.diff)+"")}else{t[0]=getDate()+r+" "+t[0]}}function getDate(){if(t.inspectOpts.hideDate){return""}return(new Date).toISOString()+" "}function log(...e){return process.stderr.write(n.format(...e)+"\n")}function save(e){if(e){process.env.DEBUG=e}else{delete process.env.DEBUG}}function load(){return process.env.DEBUG}function init(e){e.inspectOpts={};const r=Object.keys(t.inspectOpts);for(let s=0;s{"use strict";e.exports=(e,t)=>{t=t||process.argv;const r=e.startsWith("-")?"":e.length===1?"-":"--";const s=t.indexOf(r+e);const n=t.indexOf("--");return s!==-1&&(n===-1?true:s{var t=1e3;var r=t*60;var s=r*60;var n=s*24;var o=n*7;var c=n*365.25;e.exports=function(e,t){t=t||{};var r=typeof e;if(r==="string"&&e.length>0){return parse(e)}else if(r==="number"&&isFinite(e)){return t.long?fmtLong(e):fmtShort(e)}throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))};function parse(e){e=String(e);if(e.length>100){return}var a=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(!a){return}var u=parseFloat(a[1]);var i=(a[2]||"ms").toLowerCase();switch(i){case"years":case"year":case"yrs":case"yr":case"y":return u*c;case"weeks":case"week":case"w":return u*o;case"days":case"day":case"d":return u*n;case"hours":case"hour":case"hrs":case"hr":case"h":return u*s;case"minutes":case"minute":case"mins":case"min":case"m":return u*r;case"seconds":case"second":case"secs":case"sec":case"s":return u*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return u;default:return undefined}}function fmtShort(e){var o=Math.abs(e);if(o>=n){return Math.round(e/n)+"d"}if(o>=s){return Math.round(e/s)+"h"}if(o>=r){return Math.round(e/r)+"m"}if(o>=t){return Math.round(e/t)+"s"}return e+"ms"}function fmtLong(e){var o=Math.abs(e);if(o>=n){return plural(e,o,n,"day")}if(o>=s){return plural(e,o,s,"hour")}if(o>=r){return plural(e,o,r,"minute")}if(o>=t){return plural(e,o,t,"second")}return e+" ms"}function plural(e,t,r,s){var n=t>=r*1.5;return Math.round(e/r)+" "+s+(n?"s":"")}},220:(e,t,r)=>{"use strict";const s=r(37);const n=r(343);const o=process.env;let c;if(n("no-color")||n("no-colors")||n("color=false")){c=false}else if(n("color")||n("colors")||n("color=true")||n("color=always")){c=true}if("FORCE_COLOR"in o){c=o.FORCE_COLOR.length===0||parseInt(o.FORCE_COLOR,10)!==0}function translateLevel(e){if(e===0){return false}return{level:e,hasBasic:true,has256:e>=2,has16m:e>=3}}function supportsColor(e){if(c===false){return 0}if(n("color=16m")||n("color=full")||n("color=truecolor")){return 3}if(n("color=256")){return 2}if(e&&!e.isTTY&&c!==true){return 0}const t=c?1:0;if(process.platform==="win32"){const e=s.release().split(".");if(Number(process.versions.node.split(".")[0])>=8&&Number(e[0])>=10&&Number(e[2])>=10586){return Number(e[2])>=14931?3:2}return 1}if("CI"in o){if(["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some((e=>e in o))||o.CI_NAME==="codeship"){return 1}return t}if("TEAMCITY_VERSION"in o){return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(o.TEAMCITY_VERSION)?1:0}if(o.COLORTERM==="truecolor"){return 3}if("TERM_PROGRAM"in o){const e=parseInt((o.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(o.TERM_PROGRAM){case"iTerm.app":return e>=3?3:2;case"Apple_Terminal":return 2}}if(/-256(color)?$/i.test(o.TERM)){return 2}if(/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(o.TERM)){return 1}if("COLORTERM"in o){return 1}if(o.TERM==="dumb"){return t}return t}function getSupportLevel(e){const t=supportsColor(e);return translateLevel(t)}e.exports={supportsColor:getSupportLevel,stdout:getSupportLevel(process.stdout),stderr:getSupportLevel(process.stderr)}},37:e=>{"use strict";e.exports=require("os")},224:e=>{"use strict";e.exports=require("tty")},837:e=>{"use strict";e.exports=require("util")}};var t={};function __nccwpck_require__(r){var s=t[r];if(s!==undefined){return s.exports}var n=t[r]={exports:{}};var o=true;try{e[r](n,n.exports,__nccwpck_require__);o=false}finally{if(o)delete t[r]}return n.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r=__nccwpck_require__(792);module.exports=r})(); \ No newline at end of file +(()=>{var e={237:(e,t,r)=>{t.log=log;t.formatArgs=formatArgs;t.save=save;t.load=load;t.useColors=useColors;t.storage=localstorage();t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function useColors(){if(typeof window!=="undefined"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs)){return true}if(typeof navigator!=="undefined"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)){return false}return typeof document!=="undefined"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window!=="undefined"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator!=="undefined"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||typeof navigator!=="undefined"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function formatArgs(t){t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff);if(!this.useColors){return}const r="color: "+this.color;t.splice(1,0,r,"color: inherit");let s=0;let n=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{if(e==="%%"){return}s++;if(e==="%c"){n=s}}));t.splice(n,0,r)}function log(...e){return typeof console==="object"&&console.log&&console.log(...e)}function save(e){try{if(e){t.storage.setItem("debug",e)}else{t.storage.removeItem("debug")}}catch(e){}}function load(){let e;try{e=t.storage.getItem("debug")}catch(e){}if(!e&&typeof process!=="undefined"&&"env"in process){e=process.env.DEBUG}return e}function localstorage(){try{return localStorage}catch(e){}}e.exports=r(573)(t);const{formatters:s}=e.exports;s.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}},573:(e,t,r)=>{function setup(e){createDebug.debug=createDebug;createDebug.default=createDebug;createDebug.coerce=coerce;createDebug.disable=disable;createDebug.enable=enable;createDebug.enabled=enabled;createDebug.humanize=r(958);Object.keys(e).forEach((t=>{createDebug[t]=e[t]}));createDebug.instances=[];createDebug.names=[];createDebug.skips=[];createDebug.formatters={};function selectColor(e){let t=0;for(let r=0;r{if(t==="%%"){return t}o++;const n=createDebug.formatters[s];if(typeof n==="function"){const s=e[o];t=n.call(r,s);e.splice(o,1);o--}return t}));createDebug.formatArgs.call(r,e);const c=r.log||createDebug.log;c.apply(r,e)}debug.namespace=e;debug.enabled=createDebug.enabled(e);debug.useColors=createDebug.useColors();debug.color=selectColor(e);debug.destroy=destroy;debug.extend=extend;if(typeof createDebug.init==="function"){createDebug.init(debug)}createDebug.instances.push(debug);return debug}function destroy(){const e=createDebug.instances.indexOf(this);if(e!==-1){createDebug.instances.splice(e,1);return true}return false}function extend(e,t){const r=createDebug(this.namespace+(typeof t==="undefined"?":":t)+e);r.log=this.log;return r}function enable(e){createDebug.save(e);createDebug.names=[];createDebug.skips=[];let t;const r=(typeof e==="string"?e:"").split(/[\s,]+/);const s=r.length;for(t=0;t"-"+e))].join(",");createDebug.enable("");return e}function enabled(e){if(e[e.length-1]==="*"){return true}let t;let r;for(t=0,r=createDebug.skips.length;t{if(typeof process==="undefined"||process.type==="renderer"||process.browser===true||process.__nwjs){e.exports=r(237)}else{e.exports=r(354)}},354:(e,t,r)=>{const s=r(224);const n=r(837);t.init=init;t.log=log;t.formatArgs=formatArgs;t.save=save;t.load=load;t.useColors=useColors;t.colors=[6,2,3,4,5,1];try{const e=r(220);if(e&&(e.stderr||e).level>=2){t.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221]}}catch(e){}t.inspectOpts=Object.keys(process.env).filter((e=>/^debug_/i.test(e))).reduce(((e,t)=>{const r=t.substring(6).toLowerCase().replace(/_([a-z])/g,((e,t)=>t.toUpperCase()));let s=process.env[t];if(/^(yes|on|true|enabled)$/i.test(s)){s=true}else if(/^(no|off|false|disabled)$/i.test(s)){s=false}else if(s==="null"){s=null}else{s=Number(s)}e[r]=s;return e}),{});function useColors(){return"colors"in t.inspectOpts?Boolean(t.inspectOpts.colors):s.isatty(process.stderr.fd)}function formatArgs(t){const{namespace:r,useColors:s}=this;if(s){const s=this.color;const n="[3"+(s<8?s:"8;5;"+s);const o=` ${n};1m${r} `;t[0]=o+t[0].split("\n").join("\n"+o);t.push(n+"m+"+e.exports.humanize(this.diff)+"")}else{t[0]=getDate()+r+" "+t[0]}}function getDate(){if(t.inspectOpts.hideDate){return""}return(new Date).toISOString()+" "}function log(...e){return process.stderr.write(n.format(...e)+"\n")}function save(e){if(e){process.env.DEBUG=e}else{delete process.env.DEBUG}}function load(){return process.env.DEBUG}function init(e){e.inspectOpts={};const r=Object.keys(t.inspectOpts);for(let s=0;s{"use strict";e.exports=(e,t)=>{t=t||process.argv;const r=e.startsWith("-")?"":e.length===1?"-":"--";const s=t.indexOf(r+e);const n=t.indexOf("--");return s!==-1&&(n===-1?true:s{var t=1e3;var r=t*60;var s=r*60;var n=s*24;var o=n*7;var c=n*365.25;e.exports=function(e,t){t=t||{};var r=typeof e;if(r==="string"&&e.length>0){return parse(e)}else if(r==="number"&&isFinite(e)){return t.long?fmtLong(e):fmtShort(e)}throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))};function parse(e){e=String(e);if(e.length>100){return}var a=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(!a){return}var u=parseFloat(a[1]);var i=(a[2]||"ms").toLowerCase();switch(i){case"years":case"year":case"yrs":case"yr":case"y":return u*c;case"weeks":case"week":case"w":return u*o;case"days":case"day":case"d":return u*n;case"hours":case"hour":case"hrs":case"hr":case"h":return u*s;case"minutes":case"minute":case"mins":case"min":case"m":return u*r;case"seconds":case"second":case"secs":case"sec":case"s":return u*t;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return u;default:return undefined}}function fmtShort(e){var o=Math.abs(e);if(o>=n){return Math.round(e/n)+"d"}if(o>=s){return Math.round(e/s)+"h"}if(o>=r){return Math.round(e/r)+"m"}if(o>=t){return Math.round(e/t)+"s"}return e+"ms"}function fmtLong(e){var o=Math.abs(e);if(o>=n){return plural(e,o,n,"day")}if(o>=s){return plural(e,o,s,"hour")}if(o>=r){return plural(e,o,r,"minute")}if(o>=t){return plural(e,o,t,"second")}return e+" ms"}function plural(e,t,r,s){var n=t>=r*1.5;return Math.round(e/r)+" "+s+(n?"s":"")}},220:(e,t,r)=>{"use strict";const s=r(37);const n=r(343);const o=process.env;let c;if(n("no-color")||n("no-colors")||n("color=false")){c=false}else if(n("color")||n("colors")||n("color=true")||n("color=always")){c=true}if("FORCE_COLOR"in o){c=o.FORCE_COLOR.length===0||parseInt(o.FORCE_COLOR,10)!==0}function translateLevel(e){if(e===0){return false}return{level:e,hasBasic:true,has256:e>=2,has16m:e>=3}}function supportsColor(e){if(c===false){return 0}if(n("color=16m")||n("color=full")||n("color=truecolor")){return 3}if(n("color=256")){return 2}if(e&&!e.isTTY&&c!==true){return 0}const t=c?1:0;if(process.platform==="win32"){const e=s.release().split(".");if(Number(process.versions.node.split(".")[0])>=8&&Number(e[0])>=10&&Number(e[2])>=10586){return Number(e[2])>=14931?3:2}return 1}if("CI"in o){if(["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some((e=>e in o))||o.CI_NAME==="codeship"){return 1}return t}if("TEAMCITY_VERSION"in o){return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(o.TEAMCITY_VERSION)?1:0}if(o.COLORTERM==="truecolor"){return 3}if("TERM_PROGRAM"in o){const e=parseInt((o.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(o.TERM_PROGRAM){case"iTerm.app":return e>=3?3:2;case"Apple_Terminal":return 2}}if(/-256(color)?$/i.test(o.TERM)){return 2}if(/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(o.TERM)){return 1}if("COLORTERM"in o){return 1}if(o.TERM==="dumb"){return t}return t}function getSupportLevel(e){const t=supportsColor(e);return translateLevel(t)}e.exports={supportsColor:getSupportLevel,stdout:getSupportLevel(process.stdout),stderr:getSupportLevel(process.stderr)}},37:e=>{"use strict";e.exports=require("os")},224:e=>{"use strict";e.exports=require("tty")},837:e=>{"use strict";e.exports=require("util")}};var t={};function __nccwpck_require__(r){var s=t[r];if(s!==undefined){return s.exports}var n=t[r]={exports:{}};var o=true;try{e[r](n,n.exports,__nccwpck_require__);o=false}finally{if(o)delete t[r]}return n.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r=__nccwpck_require__(792);module.exports=r})(); \ No newline at end of file diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index d826f2bdd4e0..bacb1cc38e4f 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -124,6 +124,7 @@ import { } from './web/spec-extension/adapters/next-request' import { matchNextDataPathname } from './lib/match-next-data-pathname' import getRouteFromAssetPath from '../shared/lib/router/utils/get-route-from-asset-path' +import { stripInternalHeaders } from './internal-utils' export type FindComponentsResult = { components: LoadComponentsReturnType @@ -1538,6 +1539,28 @@ export default abstract class Server { ) } + protected stripInternalHeaders(req: BaseNextRequest): void { + // Skip stripping internal headers in test mode while the header stripping + // has been explicitly disabled. This allows tests to verify internal + // routing behavior. + if ( + process.env.__NEXT_TEST_MODE && + process.env.__NEXT_NO_STRIP_INTERNAL_HEADERS === '1' + ) { + return + } + + // Strip the internal headers from both the request and the original + // request. + stripInternalHeaders(req.headers) + if ( + 'originalRequest' in req && + 'headers' in (req as NodeNextRequest).originalRequest + ) { + stripInternalHeaders((req as NodeNextRequest).originalRequest.headers) + } + } + private async renderToResponseWithComponentsImpl( { req, res, pathname, renderOpts: opts }: RequestContext, { components, query }: FindComponentsResult @@ -1546,6 +1569,10 @@ export default abstract class Server { // For edge runtime 404 page, /_not-found needs to be treated as 404 page (process.env.NEXT_RUNTIME === 'edge' && pathname === '/_not-found') || pathname === '/404' + + // Strip the internal headers. + this.stripInternalHeaders(req) + const is500Page = pathname === '/500' const isAppPath = components.isAppPath const hasServerProps = !!components.getServerSideProps diff --git a/packages/next/src/server/internal-utils.ts b/packages/next/src/server/internal-utils.ts index 1ae559ea09db..1e712b16379a 100644 --- a/packages/next/src/server/internal-utils.ts +++ b/packages/next/src/server/internal-utils.ts @@ -1,6 +1,8 @@ -import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' +import type { IncomingHttpHeaders } from 'http' import type { NextParsedUrlQuery } from './request-meta' +import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers' + const INTERNAL_QUERY_NAMES = [ '__nextFallback', '__nextLocale', @@ -36,3 +38,27 @@ export function stripInternalSearchParams( return (isStringUrl ? instance.toString() : instance) as T } + +/** + * Headers that are set by the Next.js server and should be stripped from the + * request headers going to the user's application. + */ +const INTERNAL_HEADERS = [ + 'x-invoke-path', + 'x-invoke-status', + 'x-invoke-error', + 'x-invoke-query', + 'x-invoke-output', + 'x-middleware-invoke', +] as const + +/** + * Strip internal headers from the request headers. + * + * @param headers the headers to strip of internal headers + */ +export function stripInternalHeaders(headers: IncomingHttpHeaders) { + for (const key of INTERNAL_HEADERS) { + delete headers[key] + } +} diff --git a/packages/next/src/server/lib/router-utils/filesystem.ts b/packages/next/src/server/lib/router-utils/filesystem.ts index d442b87fff85..270580f1f81a 100644 --- a/packages/next/src/server/lib/router-utils/filesystem.ts +++ b/packages/next/src/server/lib/router-utils/filesystem.ts @@ -249,7 +249,7 @@ export async function setupFsCheck(opts: { ? new RegExp( route.dataRouteRegex.replace( `/${escapedBuildId}/`, - `/${escapedBuildId}/(?.+?)/` + `/${escapedBuildId}/(?[^/]+?)/` ) ) : new RegExp(route.dataRouteRegex), diff --git a/packages/next/src/server/lib/router-utils/setup-dev.ts b/packages/next/src/server/lib/router-utils/setup-dev.ts index 24b8a095d113..cf17fb2d7d9e 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev.ts @@ -186,6 +186,15 @@ async function startWatcher(opts: SetupOpts) { const { jsConfig } = await loadJsConfig(dir, opts.nextConfig) + // For the debugging purpose, check if createNext or equivalent next instance setup in test cases + // works correctly. Normally `run-test` hides output so only will be visible when `--debug` flag is used. + if (process.env.TURBOPACK && process.env.NEXT_TEST_MODE) { + require('console').log('Creating turbopack project', { + dir, + testMode: process.env.NEXT_TEST_MODE, + }) + } + const project = await bindings.turbo.createProject({ projectPath: dir, rootPath: opts.nextConfig.experimental.outputFileTracingRoot || dir, @@ -1775,7 +1784,7 @@ async function startWatcher(opts: SetupOpts) { ? new RegExp( route.dataRouteRegex.replace( `/development/`, - `/development/(?.+?)/` + `/development/(?[^/]+?)/` ) ) : new RegExp(route.dataRouteRegex), diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 087752225090..0fe80339d1a0 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -1585,6 +1585,9 @@ export default class NextNodeServer extends BaseServer { ReturnType > + // Strip the internal headers. + this.stripInternalHeaders(req) + try { await this.ensureMiddleware() diff --git a/packages/next/src/server/web/spec-extension/response.ts b/packages/next/src/server/web/spec-extension/response.ts index 18fa7158c5ad..86b09e3616a8 100644 --- a/packages/next/src/server/web/spec-extension/response.ts +++ b/packages/next/src/server/web/spec-extension/response.ts @@ -71,7 +71,6 @@ export class NextResponse extends Response { body: JsonBody, init?: ResponseInit ): NextResponse { - // @ts-expect-error This is not in lib/dom right now, and we can't augment it. const response: Response = Response.json(body, init) return new NextResponse(response.body, response) } diff --git a/packages/react-dev-overlay/package.json b/packages/react-dev-overlay/package.json index 04836aa7c5a5..cc6d2a5b422a 100644 --- a/packages/react-dev-overlay/package.json +++ b/packages/react-dev-overlay/package.json @@ -1,6 +1,6 @@ { "name": "@next/react-dev-overlay", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "description": "A development-only overlay for developing React applications.", "repository": { "url": "vercel/next.js", diff --git a/packages/react-dev-overlay/tsconfig.json b/packages/react-dev-overlay/tsconfig.json index 59e05adbe1d2..01235b6a1bf8 100644 --- a/packages/react-dev-overlay/tsconfig.json +++ b/packages/react-dev-overlay/tsconfig.json @@ -12,6 +12,7 @@ "jsx": "react", "noFallthroughCasesInSwitch": true, "skipLibCheck": true, + "module": "Node16", "moduleResolution": "Node16" }, "include": ["src/**/*.ts", "src/**/*.tsx", "types/local.d.ts"], diff --git a/packages/react-refresh-utils/package.json b/packages/react-refresh-utils/package.json index 12830b5389eb..20e4acbdcca7 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": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "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 ece657014fca..7677d8733511 100644 --- a/packages/third-parties/package.json +++ b/packages/third-parties/package.json @@ -1,6 +1,6 @@ { "name": "@next/third-parties", - "version": "13.4.20-canary.18", + "version": "13.4.20-canary.20", "private": true, "repository": { "url": "vercel/next.js", @@ -23,7 +23,7 @@ "third-party-capital": "1.0.20" }, "devDependencies": { - "next": "13.4.20-canary.18", + "next": "13.4.20-canary.20", "outdent": "0.8.0", "prettier": "2.5.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aea9761ebbc4..50f7630acfd8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,8 +150,8 @@ importers: specifier: 18.2.4 version: 18.2.4 '@types/relay-runtime': - specifier: 13.0.0 - version: 13.0.0 + specifier: 14.1.13 + version: 14.1.13 '@types/selenium-webdriver': specifier: 4.0.15 version: 4.0.15 @@ -166,10 +166,10 @@ importers: version: 2.0.3 '@typescript-eslint/eslint-plugin': specifier: 6.1.0 - version: 6.1.0(@typescript-eslint/parser@6.1.0)(eslint@7.24.0)(typescript@5.1.3) + version: 6.1.0(@typescript-eslint/parser@6.1.0)(eslint@7.24.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: 6.1.0 - version: 6.1.0(eslint@7.24.0)(typescript@5.1.3) + version: 6.1.0(eslint@7.24.0)(typescript@5.2.2) '@vercel/fetch': specifier: 6.1.1 version: 6.1.1(@types/node-fetch@2.6.1)(node-fetch@2.6.7) @@ -250,7 +250,7 @@ importers: version: 2.22.1(@typescript-eslint/parser@6.1.0)(eslint@7.24.0) eslint-plugin-jest: specifier: 24.3.5 - version: 24.3.5(@typescript-eslint/eslint-plugin@6.1.0)(eslint@7.24.0)(typescript@5.1.3) + version: 24.3.5(@typescript-eslint/eslint-plugin@6.1.0)(eslint@7.24.0)(typescript@5.2.2) eslint-plugin-jsdoc: specifier: 39.6.4 version: 39.6.4(eslint@7.24.0) @@ -520,13 +520,13 @@ importers: version: 1.2.2 tsec: specifier: 0.2.1 - version: 0.2.1(@bazel/bazelisk@1.12.1)(typescript@5.1.3) + version: 0.2.1(@bazel/bazelisk@1.12.1)(typescript@5.2.2) turbo: specifier: 1.10.9 version: 1.10.9 typescript: - specifier: 5.1.3 - version: 5.1.3 + specifier: 5.2.2 + version: 5.2.2 unfetch: specifier: 4.2.0 version: 4.2.0 @@ -729,7 +729,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.3.3 @@ -790,7 +790,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../next-env '@swc/helpers': specifier: 0.5.1 @@ -917,19 +917,19 @@ importers: specifier: 1.1.0 version: 1.1.0 '@next/polyfill-module': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../next-polyfill-nomodule '@next/react-dev-overlay': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../react-dev-overlay '@next/react-refresh-utils': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../react-refresh-utils '@next/swc': - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../next-swc '@opentelemetry/api': specifier: 1.4.1 @@ -1001,8 +1001,8 @@ importers: specifier: 9.0.0 version: 9.0.0 '@types/lodash': - specifier: 4.14.149 - version: 4.14.149 + specifier: 4.14.198 + version: 4.14.198 '@types/lodash.curry': specifier: 4.1.6 version: 4.1.6 @@ -1244,8 +1244,8 @@ importers: specifier: 2.4.3 version: 2.4.3(webpack@5.86.0) msw: - specifier: ^1.2.2 - version: 1.2.2(typescript@5.1.3) + specifier: 1.3.0 + version: 1.3.0(typescript@5.2.2) nanoid: specifier: 3.1.32 version: 3.1.32 @@ -1685,7 +1685,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 13.4.20-canary.18 + specifier: 13.4.20-canary.20 version: link:../next outdent: specifier: 0.8.0 @@ -6845,7 +6845,7 @@ packages: '@types/debug': 4.1.8 '@xmldom/xmldom': 0.8.10 debug: 4.3.4 - headers-polyfill: 3.1.2 + headers-polyfill: 3.2.3 outvariant: 1.4.0 strict-event-emitter: 0.2.8 web-encoding: 1.1.5 @@ -8333,11 +8333,11 @@ packages: /@types/lodash.curry@4.1.6: resolution: {integrity: sha512-x3ctCcmOYqRrihNNnQJW6fe/yZFCgnrIa6p80AiPQRO8Jis29bBdy1dEw1FwngoF/mCZa3Bx+33fUZvOEE635Q==} dependencies: - '@types/lodash': 4.14.149 + '@types/lodash': 4.14.198 dev: true - /@types/lodash@4.14.149: - resolution: {integrity: sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==} + /@types/lodash@4.14.198: + resolution: {integrity: sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==} dev: true /@types/long@4.0.1: @@ -8460,8 +8460,8 @@ packages: '@types/scheduler': 0.16.2 csstype: 3.0.10 - /@types/relay-runtime@13.0.0: - resolution: {integrity: sha512-yzv6F8EZPWA2rtfFP2qMluS8tsz1q4lfdYxLegCshdAjX5uqxTR2pAliATj9wrzD6OMZF4fl9aU+Y+zmSfm2EA==} + /@types/relay-runtime@14.1.13: + resolution: {integrity: sha512-NODqEnGjERJr02M0YQclUnXWCldmerNUkpFfuO317h/od1uXuwAW5131vpeiROE11BizPC/Qhup5VrwKsENazw==} dev: true /@types/resolve@1.17.1: @@ -8618,7 +8618,7 @@ packages: '@types/yargs-parser': 21.0.0 dev: true - /@typescript-eslint/eslint-plugin@6.1.0(@typescript-eslint/parser@6.1.0)(eslint@7.24.0)(typescript@5.1.3): + /@typescript-eslint/eslint-plugin@6.1.0(@typescript-eslint/parser@6.1.0)(eslint@7.24.0)(typescript@5.2.2): resolution: {integrity: sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -8630,10 +8630,10 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 6.1.0(eslint@7.24.0)(typescript@5.1.3) + '@typescript-eslint/parser': 6.1.0(eslint@7.24.0)(typescript@5.2.2) '@typescript-eslint/scope-manager': 6.1.0 - '@typescript-eslint/type-utils': 6.1.0(eslint@7.24.0)(typescript@5.1.3) - '@typescript-eslint/utils': 6.1.0(eslint@7.24.0)(typescript@5.1.3) + '@typescript-eslint/type-utils': 6.1.0(eslint@7.24.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.1.0(eslint@7.24.0)(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.1.0 debug: 4.3.4 eslint: 7.24.0 @@ -8642,13 +8642,13 @@ packages: natural-compare: 1.4.0 natural-compare-lite: 1.4.0 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.1.3) - typescript: 5.1.3 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/experimental-utils@4.29.1(eslint@7.24.0)(typescript@5.1.3): + /@typescript-eslint/experimental-utils@4.29.1(eslint@7.24.0)(typescript@5.2.2): resolution: {integrity: sha512-kl6QG6qpzZthfd2bzPNSJB2YcZpNOrP6r9jueXupcZHnL74WiuSjaft7WSu17J9+ae9zTlk0KJMXPUj0daBxMw==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -8657,7 +8657,7 @@ packages: '@types/json-schema': 7.0.12 '@typescript-eslint/scope-manager': 4.29.1 '@typescript-eslint/types': 4.29.1 - '@typescript-eslint/typescript-estree': 4.29.1(typescript@5.1.3) + '@typescript-eslint/typescript-estree': 4.29.1(typescript@5.2.2) eslint: 7.24.0 eslint-scope: 5.1.1 eslint-utils: 3.0.0(eslint@7.24.0) @@ -8666,7 +8666,7 @@ packages: - typescript dev: true - /@typescript-eslint/parser@6.1.0(eslint@7.24.0)(typescript@5.1.3): + /@typescript-eslint/parser@6.1.0(eslint@7.24.0)(typescript@5.2.2): resolution: {integrity: sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -8678,11 +8678,11 @@ packages: dependencies: '@typescript-eslint/scope-manager': 6.1.0 '@typescript-eslint/types': 6.1.0 - '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.1.3) + '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.2.2) '@typescript-eslint/visitor-keys': 6.1.0 debug: 4.3.4 eslint: 7.24.0 - typescript: 5.1.3 + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true @@ -8723,7 +8723,7 @@ packages: '@typescript-eslint/types': 6.1.0 '@typescript-eslint/visitor-keys': 6.1.0 - /@typescript-eslint/type-utils@6.1.0(eslint@7.24.0)(typescript@5.1.3): + /@typescript-eslint/type-utils@6.1.0(eslint@7.24.0)(typescript@5.2.2): resolution: {integrity: sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -8733,12 +8733,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.1.3) - '@typescript-eslint/utils': 6.1.0(eslint@7.24.0)(typescript@5.1.3) + '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.1.0(eslint@7.24.0)(typescript@5.2.2) debug: 4.3.4 eslint: 7.24.0 - ts-api-utils: 1.0.1(typescript@5.1.3) - typescript: 5.1.3 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true @@ -8752,7 +8752,7 @@ packages: resolution: {integrity: sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==} engines: {node: ^16.0.0 || >=18.0.0} - /@typescript-eslint/typescript-estree@4.29.1(typescript@5.1.3): + /@typescript-eslint/typescript-estree@4.29.1(typescript@5.2.2): resolution: {integrity: sha512-lIkkrR9E4lwZkzPiRDNq0xdC3f2iVCUjw/7WPJ4S2Sl6C3nRWkeE1YXCQ0+KsiaQRbpY16jNaokdWnm9aUIsfw==} engines: {node: ^10.12.0 || >=12.0.0} peerDependencies: @@ -8767,8 +8767,8 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.3.7 - tsutils: 3.21.0(typescript@5.1.3) - typescript: 5.1.3 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true @@ -8794,7 +8794,7 @@ packages: - supports-color dev: false - /@typescript-eslint/typescript-estree@6.1.0(typescript@5.1.3): + /@typescript-eslint/typescript-estree@6.1.0(typescript@5.2.2): resolution: {integrity: sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -8809,13 +8809,13 @@ packages: globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 - ts-api-utils: 1.0.1(typescript@5.1.3) - typescript: 5.1.3 + ts-api-utils: 1.0.1(typescript@5.2.2) + typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@6.1.0(eslint@7.24.0)(typescript@5.1.3): + /@typescript-eslint/utils@6.1.0(eslint@7.24.0)(typescript@5.2.2): resolution: {integrity: sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: @@ -8826,7 +8826,7 @@ packages: '@types/semver': 7.5.0 '@typescript-eslint/scope-manager': 6.1.0 '@typescript-eslint/types': 6.1.0 - '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.1.3) + '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.2.2) eslint: 7.24.0 semver: 7.5.4 transitivePeerDependencies: @@ -10707,14 +10707,6 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk@4.1.1: - resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -12176,7 +12168,7 @@ packages: supports-color: optional: true dependencies: - ms: 2.1.2 + ms: 2.1.3 /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -13129,7 +13121,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.1.0(eslint@7.24.0)(typescript@5.1.3) + '@typescript-eslint/parser': 6.1.0(eslint@7.24.0)(typescript@5.2.2) debug: 3.2.7 eslint-import-resolver-node: 0.3.6 find-up: 2.1.0 @@ -13188,7 +13180,7 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 6.1.0(eslint@7.24.0)(typescript@5.1.3) + '@typescript-eslint/parser': 6.1.0(eslint@7.24.0)(typescript@5.2.2) array-includes: 3.1.4 array.prototype.flat: 1.2.5 contains-path: 0.1.0 @@ -13244,7 +13236,7 @@ packages: - supports-color dev: false - /eslint-plugin-jest@24.3.5(@typescript-eslint/eslint-plugin@6.1.0)(eslint@7.24.0)(typescript@5.1.3): + /eslint-plugin-jest@24.3.5(@typescript-eslint/eslint-plugin@6.1.0)(eslint@7.24.0)(typescript@5.2.2): resolution: {integrity: sha512-XG4rtxYDuJykuqhsOqokYIR84/C8pRihRtEpVskYLbIIKGwPNW2ySxdctuVzETZE+MbF/e7wmsnbNVpzM0rDug==} engines: {node: '>=10'} peerDependencies: @@ -13254,8 +13246,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - '@typescript-eslint/eslint-plugin': 6.1.0(@typescript-eslint/parser@6.1.0)(eslint@7.24.0)(typescript@5.1.3) - '@typescript-eslint/experimental-utils': 4.29.1(eslint@7.24.0)(typescript@5.1.3) + '@typescript-eslint/eslint-plugin': 6.1.0(@typescript-eslint/parser@6.1.0)(eslint@7.24.0)(typescript@5.2.2) + '@typescript-eslint/experimental-utils': 4.29.1(eslint@7.24.0)(typescript@5.2.2) eslint: 7.24.0 transitivePeerDependencies: - supports-color @@ -15328,6 +15320,10 @@ packages: resolution: {integrity: sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==} dev: true + /headers-polyfill@3.2.3: + resolution: {integrity: sha512-oj6MO8sdFQ9gQQedSVdMGh96suxTNp91vPQu7C4qx/57FqYsA5TiNr92nhIZwVQq8zygn4nu3xS1aEqpakGqdw==} + dev: true + /hex-color-regex@1.1.0: resolution: {integrity: sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==} dev: true @@ -19484,13 +19480,13 @@ packages: isarray: 1.0.0 dev: true - /msw@1.2.2(typescript@5.1.3): - resolution: {integrity: sha512-GsW3PE/Es/a1tYThXcM8YHOZ1S1MtivcS3He/LQbbTCx3rbWJYCtWD5XXyJ53KlNPT7O1VI9sCW3xMtgFe8XpQ==} + /msw@1.3.0(typescript@5.2.2): + resolution: {integrity: sha512-nnWAZlQyQOKeYRblCpseT1kSPt1aF5e/jHz1hn/18IxbsMFreSVV1cJriT0uV+YG6+wvwFRMHXU3zVuMvuwERQ==} engines: {node: '>=14'} hasBin: true requiresBuild: true peerDependencies: - typescript: '>= 4.4.x <= 5.1.x' + typescript: '>= 4.4.x <= 5.2.x' peerDependenciesMeta: typescript: optional: true @@ -19500,11 +19496,11 @@ packages: '@open-draft/until': 1.0.3 '@types/cookie': 0.4.1 '@types/js-levenshtein': 1.1.1 - chalk: 4.1.1 + chalk: 4.1.2 chokidar: 3.5.3 cookie: 0.4.2 graphql: 16.7.1 - headers-polyfill: 3.1.2 + headers-polyfill: 3.2.3 inquirer: 8.2.0 is-node-process: 1.2.0 js-levenshtein: 1.1.6 @@ -19513,7 +19509,7 @@ packages: path-to-regexp: 6.2.1 strict-event-emitter: 0.4.6 type-fest: 2.19.0 - typescript: 5.1.3 + typescript: 5.2.2 yargs: 17.5.1 transitivePeerDependencies: - encoding @@ -25706,13 +25702,13 @@ packages: typescript: 4.8.2 dev: false - /ts-api-utils@1.0.1(typescript@5.1.3): + /ts-api-utils@1.0.1(typescript@5.2.2): resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} engines: {node: '>=16.13.0'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.1.3 + typescript: 5.2.2 dev: true /tsconfig-paths@3.14.1: @@ -25733,7 +25729,7 @@ packages: strip-bom: 3.0.0 dev: false - /tsec@0.2.1(@bazel/bazelisk@1.12.1)(typescript@5.1.3): + /tsec@0.2.1(@bazel/bazelisk@1.12.1)(typescript@5.2.2): resolution: {integrity: sha512-RP9vhbRbRI9VH4CfOlQvo5W9HdfiPKq0gdiUOWI5oKmLaZKNFN8CsPwBfT5ySmhnKNwmmAS/BtY3WoTfABwwig==} hasBin: true peerDependencies: @@ -25743,7 +25739,7 @@ packages: '@bazel/bazelisk': 1.12.1 glob: 7.2.0 minimatch: 3.1.2 - typescript: 5.1.3 + typescript: 5.2.2 dev: true /tslib@1.11.1: @@ -25763,14 +25759,14 @@ packages: /tslib@2.5.3: resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} - /tsutils@3.21.0(typescript@5.1.3): + /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.11.1 - typescript: 5.1.3 + typescript: 5.2.2 dev: true /tty-browserify@0.0.1: @@ -25973,8 +25969,8 @@ packages: engines: {node: '>=4.2.0'} hasBin: true - /typescript@5.1.3: - resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} hasBin: true dev: true diff --git a/test/e2e/app-dir/navigation/app/mpa-nav-test/page.js b/test/e2e/app-dir/navigation/app/mpa-nav-test/page.js new file mode 100644 index 000000000000..9a335e2d0995 --- /dev/null +++ b/test/e2e/app-dir/navigation/app/mpa-nav-test/page.js @@ -0,0 +1,38 @@ +'use client' +import Link from 'next/link' +import { useEffect, useRef } from 'react' + +export default function Page() { + const prefetchRef = useRef() + const slowPageRef = useRef() + + useEffect(() => { + function triggerPrefetch() { + const event = new MouseEvent('mouseover', { + view: window, + bubbles: true, + cancelable: true, + }) + + prefetchRef.current.dispatchEvent(event) + console.log('dispatched') + } + + slowPageRef.current.click() + + setInterval(() => { + triggerPrefetch() + }, 1000) + }, []) + + return ( + <> + + To /slow-page + + + Prefetch link + + + ) +} diff --git a/test/e2e/app-dir/navigation/navigation.test.ts b/test/e2e/app-dir/navigation/navigation.test.ts index 67beabf483fe..b3e1cf19c426 100644 --- a/test/e2e/app-dir/navigation/navigation.test.ts +++ b/test/e2e/app-dir/navigation/navigation.test.ts @@ -1,5 +1,5 @@ import { createNextDescribe } from 'e2e-utils' -import { check } from 'next-test-utils' +import { check, waitFor } from 'next-test-utils' import type { Request } from 'playwright-chromium' createNextDescribe( @@ -497,6 +497,33 @@ createNextDescribe( .waitForElementByCss('#link-to-app') expect(await browser.url()).toBe(next.url + '/some') }) + + if (!isNextDev) { + // this test is pretty hard to test in playwright, so most of the heavy lifting is in the page component itself + // it triggers a hover on a link to initiate a prefetch request every second, and so we check that + // it doesn't repeatedly initiate the mpa navigation request + it('should not continously initiate a mpa navigation to the same URL when router state changes', async () => { + let requestCount = 0 + const browser = await next.browser('/mpa-nav-test', { + beforePageLoad(page) { + page.on('request', (request) => { + const url = new URL(request.url()) + // skip rsc prefetches + if (url.pathname === '/slow-page' && !url.search) { + requestCount++ + } + }) + }, + }) + + await browser.waitForElementByCss('#link-to-slow-page') + + // wait a few seconds since prefetches are triggered in 1s intervals in the page component + await waitFor(5000) + + expect(requestCount).toBe(1) + }) + } }) describe('nested navigation', () => { @@ -562,7 +589,7 @@ createNextDescribe( ) }) - it('should emit refresh meta tag (peramnent) for redirect page when streaming', async () => { + it('should emit refresh meta tag (permanent) for redirect page when streaming', async () => { const html = await next.render('/redirect/suspense-2') expect(html).toContain( '' diff --git a/test/e2e/app-dir/navigation/pages/slow-page.js b/test/e2e/app-dir/navigation/pages/slow-page.js new file mode 100644 index 000000000000..ad37357cb6dc --- /dev/null +++ b/test/e2e/app-dir/navigation/pages/slow-page.js @@ -0,0 +1,13 @@ +export default function Page() { + return 'Hello from slow page' +} + +export async function getServerSideProps({ resolvedUrl }) { + if (!resolvedUrl.includes('?_rsc')) { + // only stall on the navigation, not prefetch + await new Promise((resolve) => setTimeout(resolve, 100000)) + } + return { + props: {}, + } +} diff --git a/test/e2e/i18n-data-route/components/page.tsx b/test/e2e/i18n-data-route/components/page.tsx new file mode 100644 index 000000000000..60418c58cba4 --- /dev/null +++ b/test/e2e/i18n-data-route/components/page.tsx @@ -0,0 +1,12 @@ +import React from 'react' + +export function Page({ page }) { + return

{page}

+} + +export function createGetServerSideProps(page: string) { + return async function getServerSideProps(ctx) { + const output = ctx.req.headers['x-invoke-output'] ?? null + return { props: { page, output } } + } +} diff --git a/test/e2e/i18n-data-route/i18n-data-route.test.ts b/test/e2e/i18n-data-route/i18n-data-route.test.ts new file mode 100644 index 000000000000..1231f78f0267 --- /dev/null +++ b/test/e2e/i18n-data-route/i18n-data-route.test.ts @@ -0,0 +1,87 @@ +import { createNextDescribe } from 'e2e-utils' + +const { i18n } = require('./next.config') + +const pages = [ + { url: '/about', page: '/about', params: null }, + { url: '/blog/about', page: '/[slug]/about', params: { slug: 'blog' } }, +] + +function checkDataRoute(data: any, page: string) { + expect(data).toHaveProperty('pageProps') + expect(data.pageProps).toHaveProperty('page', page) + expect(data.pageProps).toHaveProperty('output', page) +} + +createNextDescribe( + 'i18n-data-route', + { + files: __dirname, + env: { + // Disable internal header stripping so we can test the invoke output. + __NEXT_NO_STRIP_INTERNAL_HEADERS: '1', + }, + }, + ({ next }) => { + describe('with locale prefix', () => { + describe.each(i18n.locales)('/%s', (locale) => { + const prefixed = pages.map((page) => ({ + ...page, + url: `/${locale}${page.url}`, + })) + + it.each(prefixed)( + 'should render $page via $url', + async ({ url, page }) => { + const $ = await next.render$(url) + expect($('[data-page]').data('page')).toBe(page) + } + ) + + it.each(prefixed)( + 'should serve data for $page', + async ({ url, page, params }) => { + url = `/_next/data/${next.buildId}${url}.json` + if (params) { + const query = new URLSearchParams(params) + // Ensure the query is sorted so it's deterministic. + query.sort() + url += `?${query.toString()}` + } + + const res = await next.fetch(url) + expect(res.status).toBe(200) + expect(res.headers.get('content-type')).toBe('application/json') + const data = await res.json() + checkDataRoute(data, page) + } + ) + }) + }) + + describe('without locale prefix', () => { + it.each(pages)('should render $page via $url', async ({ url, page }) => { + const $ = await next.render$(url) + expect($('[data-page]').data('page')).toBe(page) + }) + + it.each(pages)( + 'should serve data for $page', + async ({ url, page, params }) => { + url = `/_next/data/${next.buildId}/${i18n.defaultLocale}${url}.json` + if (params) { + const query = new URLSearchParams(params) + // Ensure the query is sorted so it's deterministic. + query.sort() + url += `?${query.toString()}` + } + const res = await next.fetch(url) + expect(res.status).toBe(200) + expect(res.headers.get('content-type')).toBe('application/json') + const data = await res.json() + checkDataRoute(data, page) + } + ) + }) + } +) diff --git a/test/e2e/i18n-data-route/next.config.js b/test/e2e/i18n-data-route/next.config.js new file mode 100644 index 000000000000..32a015bd37ca --- /dev/null +++ b/test/e2e/i18n-data-route/next.config.js @@ -0,0 +1,9 @@ +/** + * @type {import('next').NextConfig} + */ +module.exports = { + i18n: { + locales: ['en-CA', 'fr-CA'], + defaultLocale: 'en-CA', + }, +} diff --git a/test/e2e/i18n-data-route/pages/[slug]/about/index.tsx b/test/e2e/i18n-data-route/pages/[slug]/about/index.tsx new file mode 100644 index 000000000000..3c8a96c601f4 --- /dev/null +++ b/test/e2e/i18n-data-route/pages/[slug]/about/index.tsx @@ -0,0 +1,5 @@ +import { Page, createGetServerSideProps } from '../../../components/page' + +export default Page + +export const getServerSideProps = createGetServerSideProps('/[slug]/about') diff --git a/test/e2e/i18n-data-route/pages/about/index.tsx b/test/e2e/i18n-data-route/pages/about/index.tsx new file mode 100644 index 000000000000..83ef2115f532 --- /dev/null +++ b/test/e2e/i18n-data-route/pages/about/index.tsx @@ -0,0 +1,5 @@ +import { Page, createGetServerSideProps } from '../../components/page' + +export default Page + +export const getServerSideProps = createGetServerSideProps('/about') diff --git a/test/e2e/styled-jsx/index.test.ts b/test/e2e/styled-jsx/index.test.ts index aa22928ece66..c6767579c1c4 100644 --- a/test/e2e/styled-jsx/index.test.ts +++ b/test/e2e/styled-jsx/index.test.ts @@ -1,7 +1,7 @@ import path from 'path' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -import { renderViaHTTP } from 'next-test-utils' +import { renderViaHTTP, shouldRunTurboDevTest } from 'next-test-utils' import webdriver from 'next-webdriver' const appDir = path.join(__dirname, 'app') @@ -11,6 +11,8 @@ function runTest() { let next: NextInstance beforeAll(async () => { + const devCommand = shouldRunTurboDevTest() ? 'dev --turbo' : 'dev' + next = await createNext({ files: { node_modules_bak: new FileRef(path.join(appDir, 'node_modules_bak')), @@ -21,7 +23,7 @@ function runTest() { scripts: { setup: `cp -r ./node_modules_bak/my-comps ./node_modules;`, build: `yarn setup && next build`, - dev: `yarn setup && next dev`, + dev: `yarn setup && next ${devCommand}`, start: 'next start', }, }, diff --git a/test/integration/tsconfig-verifier/test/index.test.js b/test/integration/tsconfig-verifier/test/index.test.js index fdb648b46ee1..5102737198a8 100644 --- a/test/integration/tsconfig-verifier/test/index.test.js +++ b/test/integration/tsconfig-verifier/test/index.test.js @@ -298,7 +298,7 @@ describe('tsconfig.json verifier', () => { await writeFile( tsConfig, - `{ "compilerOptions": { "esModuleInterop": false, "moduleResolution": "node16" } }` + `{ "compilerOptions": { "esModuleInterop": false, "moduleResolution": "node16", "module": "node16" } }` ) await new Promise((resolve) => setTimeout(resolve, 500)) const { code, stderr, stdout } = await nextBuild(appDir, undefined, { @@ -313,6 +313,7 @@ describe('tsconfig.json verifier', () => { \\"compilerOptions\\": { \\"esModuleInterop\\": true, \\"moduleResolution\\": \\"node16\\", + \\"module\\": \\"node16\\", \\"lib\\": [ \\"dom\\", \\"dom.iterable\\", @@ -323,7 +324,6 @@ describe('tsconfig.json verifier', () => { \\"strict\\": false, \\"noEmit\\": true, \\"incremental\\": true, - \\"module\\": \\"esnext\\", \\"resolveJsonModule\\": true, \\"isolatedModules\\": true, \\"jsx\\": \\"preserve\\", diff --git a/test/turbopack-tests-manifest.js b/test/turbopack-tests-manifest.js index 61e99157470a..cf7b40653624 100644 --- a/test/turbopack-tests-manifest.js +++ b/test/turbopack-tests-manifest.js @@ -21,6 +21,7 @@ const enabledTests = [ 'test/e2e/type-module-interop/index.test.ts', 'test/e2e/undici-fetch/index.test.ts', 'test/integration/bigint/test/index.test.js', + 'test/e2e/styled-jsx/index.test.ts', // TODO: re-enable once the logging is aligned // 'test/integration/middleware-basic/test/index.test.js', ]