From bd705537a351c76ea1199e95fa72eda32afba58e Mon Sep 17 00:00:00 2001 From: Jimmy Lai Date: Fri, 15 Sep 2023 21:49:39 +0200 Subject: [PATCH] server: bundle vendored react (#55362) ## What This PR changes Next.js to bundle its vendored React libraries so that the App Router pages can use those built-in versions. ## Why Next.js supports both Pages and App Router and we've gone through a lot of iteration to make sure that Next.js stays flexible wrt to the version of React used: in Pages, we want to use the React provided by the user and in the App Router, to be able to use it, we need to use the canary version of React, which we've built into Next.js for convenience. The problem stems from the fact that you can't run two different instances of React (by design). Previously we have a dual worker setup, where we would separate completely each Next.js versions (App and Pages) so that they would not overlap with each other, however this approach was not great performance and memory wise. We've recently tried using an ESM loader and a single process, but this change would still opt you into the React canary version if you had an app page, which breaks some assumptions. ## How A list of the changes in this PR: ### New versions of the Next.js runtime Since we now compile a runtime per type of page (app/route/api/pages), in order to bundle the two versions of React that we vendored, we introduced a new type of bundle suffixed by `-experimental`. This bundle will have the bleeding edge React needed for Server Actions and Next.js will opt you in into that runtime automatically. For internal contributors, it means that we now run a compiler for 10 subparts of Next.js: - next_bundle_server - next_bundle_pages_prod - next_bundle_pages_turbo - next_bundle_pages_dev - next_bundle_app_turbo_experimental - next_bundle_app_prod - next_bundle_app_prod_experimental - next_bundle_app_turbo - next_bundle_app_dev_experimental - next_bundle_app_dev ![image](https://github.com/vercel/next.js/assets/11064311/f340417d-845e-45b9-8e86-5b287a295c82) ### Simplified require-hook Since the versions of React are correctly re-routed at build time for app pages, we don't need the require hook anymore ### Turbopack changes The bundling logic in Turbopack has been addressed to properly follow the new logic ### Changes to the shared contexts system Some context files need to have a shared instance between the rendering runtime and the user code, like the one that powers the `next/image` component. In general, the aliasing setup takes care of that but we need the require hook for code that is not compiled to reroute to the correct runtime. This only happens for pages node_modules. A new Turbopack resolving plugin has been added to handle that logic in Turbopack. ### Misc changes - `runtime-config` (that powers `next/config`) has been converted to an `.external` file, as it should have been - there are some rules that have been added to the aliases to support the usage of `react-dom/server` in a server-components. We can do that now since the runtime takes care of separating the versions of React. Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- .../crates/next-core/src/next_import_map.rs | 279 ++++++++++++------ .../next-core/src/next_server/context.rs | 9 +- .../next-core/src/next_shared/resolve.rs | 73 ++++- packages/next/config.d.ts | 4 +- packages/next/config.js | 2 +- packages/next/src/build/index.ts | 36 ++- packages/next/src/build/utils.ts | 10 +- packages/next/src/build/webpack-config.ts | 257 ++++++++-------- .../action-client-wrapper.ts | 2 +- .../nextjs-require-cache-hot-reloader.ts | 23 +- .../router-reducer/fetch-server-response.ts | 10 +- .../reducers/server-action-reducer.ts | 15 +- packages/next/src/client/index.tsx | 4 +- packages/next/src/export/index.ts | 1 + packages/next/src/export/worker.ts | 10 +- packages/next/src/lib/constants.ts | 5 +- .../server/app-render/use-flight-response.tsx | 7 +- packages/next/src/server/base-server.ts | 2 +- .../src/server/dev/static-paths-worker.ts | 2 +- packages/next/src/server/esm-loader.mts | 5 +- .../route-modules/app-page/module.compiled.ts | 20 +- .../future/route-modules/app-page/module.ts | 24 +- .../route-modules/app-page/shared-modules.ts | 13 - .../app-page/vendored/contexts/amp-context.ts | 3 + .../vendored/contexts/app-router-context.ts | 3 + .../app-page/vendored/contexts/entrypoints.ts | 10 + .../vendored/contexts/head-manager-context.ts | 3 + .../vendored/contexts/hooks-client-context.ts | 3 + .../vendored/contexts/html-context.ts | 3 + .../vendored/contexts/image-config-context.ts | 3 + .../vendored/contexts/loadable-context.ts | 3 + .../app-page/vendored/contexts/loadable.ts | 1 + .../vendored/contexts/router-context.ts | 3 + .../vendored/contexts/server-inserted-html.ts | 3 + .../app-page/vendored/rsc/entrypoints.ts | 15 + .../app-page/vendored/rsc/react-dom.ts | 1 + .../react-server-dom-webpack-server-edge.ts | 3 + .../react-server-dom-webpack-server-node.ts | 3 + .../app-page/vendored/rsc/react.ts | 1 + .../app-page/vendored/shared/entrypoints.ts | 4 + .../vendored/shared/react-jsx-dev-runtime.ts | 3 + .../vendored/shared/react-jsx-runtime.ts | 3 + .../app-page/vendored/ssr/entrypoints.ts | 9 + .../vendored/ssr/react-dom-server-edge.ts | 3 + .../app-page/vendored/ssr/react-dom.ts | 1 + .../react-server-dom-webpack-client-edge.ts | 3 + .../app-page/vendored/ssr/react.ts | 1 + .../future/route-modules/pages/module.ts | 10 +- .../route-modules/pages/shared-modules.ts | 12 - .../pages/vendored/contexts/amp-context.ts | 3 + .../vendored/contexts/app-router-context.ts | 3 + .../pages/vendored/contexts/entrypoints.ts | 10 + .../vendored/contexts/head-manager-context.ts | 3 + .../vendored/contexts/hooks-client-context.ts | 3 + .../pages/vendored/contexts/html-context.ts | 3 + .../vendored/contexts/image-config-context.ts | 3 + .../vendored/contexts/loadable-context.ts | 3 + .../pages/vendored/contexts/loadable.ts | 1 + .../pages/vendored/contexts/router-context.ts | 3 + .../vendored/contexts/server-inserted-html.ts | 3 + packages/next/src/server/import-overrides.ts | 68 ----- .../src/server/lib/router-utils/setup-dev.ts | 1 + packages/next/src/server/render.tsx | 2 +- packages/next/src/server/require-hook.js | 37 ++- .../src/shared/lib/router/adapters.test.tsx | 2 +- ...apters.shared-runtime.tsx => adapters.tsx} | 0 ...-runtime.ts => runtime-config.external.ts} | 0 packages/next/taskfile.js | 113 ++++++- packages/next/types/misc.d.ts | 5 + packages/next/webpack.config.js | 162 ++++++++-- .../rsc-basic/app/app-react/client-react.js | 2 +- test/e2e/app-dir/rsc-basic/rsc-basic.test.ts | 8 +- .../custom-server/custom-server.test.ts | 4 +- 73 files changed, 898 insertions(+), 467 deletions(-) delete mode 100644 packages/next/src/server/future/route-modules/app-page/shared-modules.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/amp-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/app-router-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/entrypoints.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/head-manager-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/hooks-client-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/html-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/image-config-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/router-context.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/contexts/server-inserted-html.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/rsc/entrypoints.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-dom.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-edge.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-node.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/rsc/react.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/shared/entrypoints.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-dev-runtime.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-runtime.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/ssr/entrypoints.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom-server-edge.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-server-dom-webpack-client-edge.ts create mode 100644 packages/next/src/server/future/route-modules/app-page/vendored/ssr/react.ts delete mode 100644 packages/next/src/server/future/route-modules/pages/shared-modules.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/amp-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/app-router-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/entrypoints.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/head-manager-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/hooks-client-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/html-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/image-config-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/router-context.ts create mode 100644 packages/next/src/server/future/route-modules/pages/vendored/contexts/server-inserted-html.ts rename packages/next/src/shared/lib/router/{adapters.shared-runtime.tsx => adapters.tsx} (100%) rename packages/next/src/shared/lib/{runtime-config.shared-runtime.ts => runtime-config.external.ts} (100%) diff --git a/packages/next-swc/crates/next-core/src/next_import_map.rs b/packages/next-swc/crates/next-core/src/next_import_map.rs index 98d73a8345..adbd6b55f4 100644 --- a/packages/next-swc/crates/next-core/src/next_import_map.rs +++ b/packages/next-swc/crates/next-core/src/next_import_map.rs @@ -246,10 +246,7 @@ pub async fn get_next_server_import_map( | ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => { match mode { - NextMode::Build => { - import_map.insert_wildcard_alias("next/dist/server/", external); - import_map.insert_wildcard_alias("next/dist/shared/", external); - } + NextMode::Build => {} NextMode::DevServer => { // The sandbox can't be bundled and needs to be external import_map.insert_exact_alias("next/dist/server/web/sandbox", external); @@ -431,9 +428,11 @@ async fn insert_next_server_special_aliases( ); } (_, ServerContextType::PagesData { .. }) => {} - // In development, we *always* use the bundled version of React, even in - // SSR, since we're bundling Next.js alongside it. - (NextMode::DevServer, ServerContextType::AppSSR { app_dir }) => { + // the logic closely follows the one in createRSCAliases in webpack-config.ts + ( + NextMode::DevServer | NextMode::Build | NextMode::Development, + ServerContextType::AppSSR { app_dir }, + ) => { import_map.insert_exact_alias( "@opentelemetry/api", // TODO(WEB-625) this actually need to prefer the local version of @@ -441,51 +440,126 @@ async fn insert_next_server_special_aliases( request_to_import_mapping(app_dir, "next/dist/compiled/@opentelemetry/api"), ); import_map.insert_exact_alias( - "react", - passthrough_external_if_node(app_dir, "next/dist/compiled/react"), + "styled-jsx", + passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx"), ); import_map.insert_wildcard_alias( - "react/", - passthrough_external_if_node(app_dir, "next/dist/compiled/react/*"), + "styled-jsx/", + passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx/*"), + ); + import_map.insert_exact_alias( + "react/jsx-runtime", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react/jsx-runtime", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/shared/\ + react-jsx-runtime" + } + }, + ), + ); + import_map.insert_exact_alias( + "react/jsx-dev-runtime", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react/jsx-dev-runtime", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/shared/\ + react-jsx-dev-runtime" + } + }, + ), + ); + import_map.insert_exact_alias( + "react", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/react" + } + }, + ), ); import_map.insert_exact_alias( "react-dom", - passthrough_external_if_node( + request_to_import_mapping( app_dir, - "next/dist/compiled/react-dom/server-rendering-stub.js", + match runtime { + NextRuntime::Edge => "next/dist/compiled/react-dom", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/react-dom" + } + }, ), ); - import_map.insert_wildcard_alias( - "react-dom/", - passthrough_external_if_node(app_dir, "next/dist/compiled/react-dom/*"), + import_map.insert_exact_alias( + "react-server-dom-webpack/client.edge", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => { + "next/dist/compiled/react-server-dom-webpack/client.edge" + } + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/\ + react-server-dom-webpack-client-edge" + } + }, + ), ); + // some code also imports react-server-dom-webpack/client on the server + // it should never run so it's fine to just point it to the same place as + // react-server-dom-webpack/client.edge import_map.insert_exact_alias( - "styled-jsx", - passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx"), + "react-server-dom-webpack/client", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => { + "next/dist/compiled/react-server-dom-webpack/client.edge" + } + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/\ + react-server-dom-webpack-client-edge" + } + }, + ), ); - import_map.insert_wildcard_alias( - "styled-jsx/", - passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx/*"), + // not essential but we're providing this alias for people who might use it. + // A note here is that this will point toward the ReactDOMServer on the SSR + // layer TODO: add the rests + import_map.insert_exact_alias( + "react-dom/server", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react-dom/server.edge", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/\ + react-dom-server-edge" + } + }, + ), ); - import_map.insert_wildcard_alias( - "react-server-dom-webpack/", - passthrough_external_if_node( + import_map.insert_exact_alias( + "react-dom/server.edge", + request_to_import_mapping( app_dir, - "next/dist/compiled/react-server-dom-webpack/*", + match runtime { + NextRuntime::Edge => "next/dist/compiled/react-dom/server.edge", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/\ + react-dom-server-edge" + } + }, ), ); } - - // NOTE(alexkirsz) This logic maps loosely to - // `next.js/packages/next/src/build/webpack-config.ts`, where: - // - // ## RSC - // - // * always bundles - // * maps react -> react/shared-subset (through the "react-server" exports condition) - // * maps react-dom -> react-dom/server-rendering-stub - // * passes through (react|react-dom|react-server-dom-webpack)/(.*) to - // next/dist/compiled/$1/$2 ( NextMode::Build | NextMode::Development | NextMode::DevServer, ServerContextType::AppRSC { app_dir, .. } | ServerContextType::AppRoute { app_dir }, @@ -496,72 +570,103 @@ async fn insert_next_server_special_aliases( // @opentelemetry/api request_to_import_mapping(app_dir, "next/dist/compiled/@opentelemetry/api"), ); - if matches!(ty, ServerContextType::AppRSC { .. }) { - import_map.insert_exact_alias( - "react", - request_to_import_mapping( - app_dir, - "next/dist/compiled/react/react.shared-subset", - ), - ); - } else { - import_map.insert_exact_alias( - "react", - request_to_import_mapping(app_dir, "next/dist/compiled/react"), - ); - } + import_map.insert_exact_alias( - "react-dom", + "react/jsx-runtime", request_to_import_mapping( app_dir, - "next/dist/compiled/react-dom/server-rendering-stub", + match runtime { + NextRuntime::Edge => "next/dist/compiled/react/jsx-runtime", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/shared/\ + react-jsx-runtime" + } + }, ), ); - for (wildcard_alias, request) in [ - ("react/", "next/dist/compiled/react/*"), - ("react-dom/", "next/dist/compiled/react-dom/*"), - ( - "react-server-dom-webpack/", - "next/dist/compiled/react-server-dom-webpack/*", + import_map.insert_exact_alias( + "react/jsx-dev-runtime", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react/jsx-dev-runtime", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/shared/\ + react-jsx-dev-runtime" + } + }, ), - ] { - import_map.insert_wildcard_alias( - wildcard_alias, - request_to_import_mapping(app_dir, request), - ); - } - } - // ## SSR - // - // * always uses externals, to ensure we're using the same React instance as the Next.js - // runtime - // * maps react-dom -> react-dom/server-rendering-stub - // * passes through react and (react|react-dom|react-server-dom-webpack)/(.*) to - // next/dist/compiled/react and next/dist/compiled/$1/$2 resp. - (NextMode::Build | NextMode::Development, ServerContextType::AppSSR { app_dir }) => { + ); import_map.insert_exact_alias( "react", - external_if_node(app_dir, "next/dist/compiled/react"), + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/rsc/react" + } + }, + ), ); import_map.insert_exact_alias( "react-dom", - external_if_node( + request_to_import_mapping( app_dir, - "next/dist/compiled/react-dom/server-rendering-stub", + match runtime { + NextRuntime::Edge => "next/dist/compiled/react-dom", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/rsc/react-dom" + } + }, ), ); - - for (wildcard_alias, request) in [ - ("react/", "next/dist/compiled/react/*"), - ("react-dom/", "next/dist/compiled/react-dom/*"), - ( - "react-server-dom-webpack/", - "next/dist/compiled/react-server-dom-webpack/*", + import_map.insert_exact_alias( + "react-server-dom-webpack/server.edge", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => { + "next/dist/compiled/react-server-dom-webpack/server.edge" + } + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/rsc/\ + react-server-dom-webpack-server-edge" + } + }, ), - ] { - let import_mapping = external_if_node(app_dir, request); - import_map.insert_wildcard_alias(wildcard_alias, import_mapping); - } + ); + import_map.insert_exact_alias( + "react-server-dom-webpack/server.node", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => { + "next/dist/compiled/react-server-dom-webpack/server.node" + } + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/rsc/\ + react-server-dom-webpack-server-node" + } + }, + ), + ); + // not essential but we're providing this alias for people who might use it. + // A note here is that this will point toward the ReactDOMServer on the SSR + // layer TODO: add the rests + import_map.insert_exact_alias( + "react-dom/server.edge", + request_to_import_mapping( + app_dir, + match runtime { + NextRuntime::Edge => "next/dist/compiled/react-dom/server.edge", + NextRuntime::NodeJs => { + "next/dist/server/future/route-modules/app-page/vendored/ssr/\ + react-dom-server-edge" + } + }, + ), + ); } (_, ServerContextType::Middleware) => {} } diff --git a/packages/next-swc/crates/next-core/src/next_server/context.rs b/packages/next-swc/crates/next-core/src/next_server/context.rs index 1c6418a188..7939dbe5c1 100644 --- a/packages/next-swc/crates/next-core/src/next_server/context.rs +++ b/packages/next-swc/crates/next-core/src/next_server/context.rs @@ -48,7 +48,7 @@ use crate::{ next_shared::{ resolve::{ ModuleFeatureReportResolvePlugin, NextExternalResolvePlugin, - UnsupportedModulesResolvePlugin, + NextNodeSharedRuntimeResolvePlugin, UnsupportedModulesResolvePlugin, }, transforms::{ emotion::get_emotion_transform_plugin, get_relay_transform_plugin, @@ -125,6 +125,8 @@ pub async fn get_server_resolve_options_context( ); let next_external_plugin = NextExternalResolvePlugin::new(project_path); + let next_node_shared_runtime_plugin = + NextNodeSharedRuntimeResolvePlugin::new(project_path, Value::new(ty)); let plugins = match ty { ServerContextType::Pages { .. } | ServerContextType::PagesData { .. } => { @@ -133,6 +135,7 @@ pub async fn get_server_resolve_options_context( Vc::upcast(external_cjs_modules_plugin), Vc::upcast(unsupported_modules_resolve_plugin), Vc::upcast(next_external_plugin), + Vc::upcast(next_node_shared_runtime_plugin), ] } ServerContextType::AppSSR { .. } @@ -144,6 +147,7 @@ pub async fn get_server_resolve_options_context( Vc::upcast(server_component_externals_plugin), Vc::upcast(unsupported_modules_resolve_plugin), Vc::upcast(next_external_plugin), + Vc::upcast(next_node_shared_runtime_plugin), ] } }; @@ -175,7 +179,8 @@ fn defines(mode: NextMode) -> CompileTimeDefines { process.turbopack = true, process.env.NODE_ENV = mode.node_env(), process.env.__NEXT_CLIENT_ROUTER_FILTER_ENABLED = false, - process.env.NEXT_RUNTIME = "nodejs" + process.env.NEXT_RUNTIME = "nodejs", + process.env.__NEXT_EXPERIMENTAL_REACT = false, ) // TODO(WEB-937) there are more defines needed, see // packages/next/src/build/webpack-config.ts diff --git a/packages/next-swc/crates/next-core/src/next_shared/resolve.rs b/packages/next-swc/crates/next-core/src/next_shared/resolve.rs index 36866684af..8eebd1385e 100644 --- a/packages/next-swc/crates/next-core/src/next_shared/resolve.rs +++ b/packages/next-swc/crates/next-core/src/next_shared/resolve.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use anyhow::Result; use lazy_static::lazy_static; -use turbo_tasks::Vc; +use turbo_tasks::{Value, Vc}; use turbo_tasks_fs::glob::Glob; use turbopack_binding::{ turbo::tasks_fs::FileSystemPath, @@ -19,7 +19,7 @@ use turbopack_binding::{ }, }; -use crate::next_telemetry::ModuleFeatureTelemetry; +use crate::{next_server::ServerContextType, next_telemetry::ModuleFeatureTelemetry}; lazy_static! { static ref UNSUPPORTED_PACKAGES: HashSet<&'static str> = [].into(); @@ -122,10 +122,7 @@ impl ResolvePlugin for NextExternalResolvePlugin { fn after_resolve_condition(&self) -> Vc { ResolvePluginCondition::new( self.root.root(), - Glob::new( - "**/next/dist/**/*.{external,shared-runtime,runtime.dev,runtime.prod}.js" - .to_string(), - ), + Glob::new("**/next/dist/**/*.{external,runtime.dev,runtime.prod}.js".to_string()), ) } @@ -152,6 +149,70 @@ impl ResolvePlugin for NextExternalResolvePlugin { } } +#[turbo_tasks::value] +pub(crate) struct NextNodeSharedRuntimeResolvePlugin { + root: Vc, + context: ServerContextType, +} + +#[turbo_tasks::value_impl] +impl NextNodeSharedRuntimeResolvePlugin { + #[turbo_tasks::function] + pub fn new(root: Vc, context: Value) -> Vc { + let context = context.into_value(); + NextNodeSharedRuntimeResolvePlugin { root, context }.cell() + } +} + +#[turbo_tasks::value_impl] +impl ResolvePlugin for NextNodeSharedRuntimeResolvePlugin { + #[turbo_tasks::function] + fn after_resolve_condition(&self) -> Vc { + ResolvePluginCondition::new( + self.root.root(), + Glob::new("**/next/dist/**/*.shared-runtime.js".to_string()), + ) + } + + #[turbo_tasks::function] + async fn after_resolve( + &self, + fs_path: Vc, + _context: Vc, + _request: Vc, + ) -> Result> { + let stem = fs_path.file_stem().await?; + let stem = stem.as_deref().unwrap_or_default(); + let stem = stem.replace(".shared-runtime", ""); + + let resource_request = format!( + "next/dist/server/future/route-modules/{}/vendored/contexts/{}.js", + match self.context { + ServerContextType::Pages { .. } => "pages", + ServerContextType::AppRoute { .. } => "app-route", + ServerContextType::AppSSR { .. } | ServerContextType::AppRSC { .. } => "app-page", + _ => "unknown", + }, + stem + ); + + let raw_fs_path = &*fs_path.await?; + let path = raw_fs_path.path.to_string(); + + // Find the starting index of 'next/dist' and slice from that point. It should + // always be found since the glob pattern above is specific enough. + let starting_index = path.find("next/dist").unwrap(); + + let (base, _) = path.split_at(starting_index); + + let new_path = fs_path.root().join(format!("{base}/{resource_request}")); + + Ok(Vc::cell(Some( + ResolveResult::source(Vc::upcast(FileSource::new(new_path))).into(), + ))) + } +} + /// A resolver plugin tracks the usage of certain import paths, emit /// telemetry events if there is a match. #[turbo_tasks::value] diff --git a/packages/next/config.d.ts b/packages/next/config.d.ts index 2da1ee3c40..20c292fb46 100644 --- a/packages/next/config.d.ts +++ b/packages/next/config.d.ts @@ -1,3 +1,3 @@ -import getConfig from './dist/shared/lib/runtime-config.shared-runtime' -export * from './dist/shared/lib/runtime-config.shared-runtime' +import getConfig from './dist/shared/lib/runtime-config.external' +export * from './dist/shared/lib/runtime-config.external' export default getConfig diff --git a/packages/next/config.js b/packages/next/config.js index 6510748638..668ee7c54f 100644 --- a/packages/next/config.js +++ b/packages/next/config.js @@ -1 +1 @@ -module.exports = require('./dist/shared/lib/runtime-config.shared-runtime') +module.exports = require('./dist/shared/lib/runtime-config.external') diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 305eea8843..b9261362bd 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -147,11 +147,7 @@ import { startTypeChecking } from './type-check' import { generateInterceptionRoutesRewrites } from '../lib/generate-interception-routes-rewrites' import { buildDataRoute } from '../server/lib/router-utils/build-data-route' -import { - baseOverrides, - defaultOverrides, - experimentalOverrides, -} from '../server/import-overrides' +import { defaultOverrides } from '../server/import-overrides' import { initialize as initializeIncrementalCache } from '../server/lib/incremental-cache-server' import { nodeFs } from '../server/lib/node-fs-methods' import { getEsmLoaderPath } from '../server/lib/get-esm-loader-path' @@ -2100,12 +2096,6 @@ export default async function build( ) const sharedEntriesSet = [ - ...Object.values(baseOverrides).map((override) => - require.resolve(override) - ), - ...Object.values(experimentalOverrides).map((override) => - require.resolve(override) - ), ...(config.experimental.turbotrace ? [] : Object.keys(defaultOverrides).map((value) => @@ -2282,6 +2272,30 @@ export default async function build( } } } + + const moduleTypes = ['app-page', 'pages'] + + for (const type of moduleTypes) { + const contextDir = path.join( + path.dirname( + require.resolve( + `next/dist/server/future/route-modules/${type}/module` + ) + ), + 'vendored', + 'contexts' + ) + + for (const item of await fs.readdir(contextDir)) { + const itemPath = path.relative( + root, + path.join(contextDir, item) + ) + addToTracedFiles(root, itemPath, tracedFiles) + addToTracedFiles(root, itemPath, minimalTracedFiles) + } + } + await Promise.all([ fs.writeFile( nextServerTraceOutput, diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index ea34d4e702..3af37d71a9 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -1409,7 +1409,7 @@ export async function isPageStatic({ const isPageStaticSpan = trace('is-page-static-utils', parentId) return isPageStaticSpan .traceAsyncFn(async () => { - require('../shared/lib/runtime-config.shared-runtime').setConfig( + require('../shared/lib/runtime-config.external').setConfig( runtimeEnvConfig ) setHttpClientAndAgentOptions({ @@ -1695,9 +1695,7 @@ export async function hasCustomGetInitialProps( runtimeEnvConfig: any, checkingApp: boolean ): Promise { - require('../shared/lib/runtime-config.shared-runtime').setConfig( - runtimeEnvConfig - ) + require('../shared/lib/runtime-config.external').setConfig(runtimeEnvConfig) const components = await loadComponents({ distDir, @@ -1720,9 +1718,7 @@ export async function getDefinedNamedExports( distDir: string, runtimeEnvConfig: any ): Promise> { - require('../shared/lib/runtime-config.shared-runtime').setConfig( - runtimeEnvConfig - ) + require('../shared/lib/runtime-config.external').setConfig(runtimeEnvConfig) const components = await loadComponents({ distDir, page: page, diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 73b4da713f..aa80e4c6fb 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -17,6 +17,7 @@ import { RSC_ACTION_CLIENT_WRAPPER_ALIAS, RSC_ACTION_VALIDATE_ALIAS, WEBPACK_RESOURCE_QUERIES, + WebpackLayerName, } from '../lib/constants' import { CustomRoutes } from '../lib/load-custom-routes.js' import { isEdgeRuntime } from '../lib/is-edge-runtime' @@ -85,8 +86,8 @@ const NEXT_PROJECT_ROOT_DIST_CLIENT = path.join( 'client' ) -const isWebpackServerLayer = (layer: string | null) => - Boolean(layer && WEBPACK_LAYERS.GROUP.server.includes(layer)) +const isWebpackServerLayer = (layer: WebpackLayerName | null) => + Boolean(layer && WEBPACK_LAYERS.GROUP.server.includes(layer as any)) if (parseInt(React.version) < 18) { throw new Error('Next.js requires react >= 18.2.0 to be installed.') @@ -106,13 +107,9 @@ const asyncStoragesRegex = const pathSeparators = '[/\\\\]' const optionalEsmPart = `((${pathSeparators}esm)?${pathSeparators})` -const sharedRuntimeFileEnd = '(\\.shared-runtime(\\.js)?)$' const externalFileEnd = '(\\.external(\\.js)?)$' const nextDist = `next${pathSeparators}dist` -const sharedRuntimePattern = new RegExp( - `${nextDist}${optionalEsmPart}.*${sharedRuntimeFileEnd}` -) const externalPattern = new RegExp( `${nextDist}${optionalEsmPart}.*${externalFileEnd}` ) @@ -196,6 +193,7 @@ export function getDefineEnv({ isNodeServer, middlewareMatchers, previewModeId, + useServerActions, }: { allowedRevalidateHeaderKeys: string[] | undefined clientRouterFilters: Parameters< @@ -212,6 +210,7 @@ export function getDefineEnv({ isNodeServer: boolean middlewareMatchers: MiddlewareMatcher[] | undefined previewModeId: string | undefined + useServerActions: boolean }) { return { // internal field to identify the plugin config @@ -374,6 +373,12 @@ export function getDefineEnv({ } : undefined), 'process.env.TURBOPACK': JSON.stringify(false), + ...(isNodeServer + ? { + 'process.env.__NEXT_EXPERIMENTAL_REACT': + JSON.stringify(useServerActions), + } + : undefined), } } @@ -387,35 +392,64 @@ function getReactProfilingInProduction() { function createRSCAliases( bundledReactChannel: string, opts: { + layer: WebpackLayerName & ('rsc' | 'ssr' | 'app-pages-browser') + isEdgeServer: boolean reactProductionProfiling: boolean - reactSharedSubset: boolean - reactDomServerRenderingStub: boolean reactServerCondition?: boolean } ) { - const alias: Record = { - react$: `next/dist/compiled/react${bundledReactChannel}`, - 'react-dom$': `next/dist/compiled/react-dom${bundledReactChannel}`, - 'react/jsx-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-runtime`, - 'react/jsx-dev-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-dev-runtime`, - 'react-dom/client$': `next/dist/compiled/react-dom${bundledReactChannel}/client`, - 'react-dom/server$': `next/dist/compiled/react-dom${bundledReactChannel}/server`, - 'react-dom/server.edge$': `next/dist/compiled/react-dom${bundledReactChannel}/server.edge`, - 'react-dom/server.browser$': `next/dist/compiled/react-dom${bundledReactChannel}/server.browser`, - 'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client`, - 'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client.edge`, - 'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.edge`, - 'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.node`, + let alias: Record = {} + if (opts.layer === 'app-pages-browser' || opts.isEdgeServer) { + alias = { + react$: `next/dist/compiled/react${bundledReactChannel}`, + 'react-dom$': `next/dist/compiled/react-dom${bundledReactChannel}`, + 'react/jsx-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-runtime`, + 'react/jsx-dev-runtime$': `next/dist/compiled/react${bundledReactChannel}/jsx-dev-runtime`, + 'react-dom/client$': `next/dist/compiled/react-dom${bundledReactChannel}/client`, + 'react-dom/server$': `next/dist/compiled/react-dom${bundledReactChannel}/server`, + 'react-dom/server.edge$': `next/dist/compiled/react-dom${bundledReactChannel}/server.edge`, + 'react-dom/server.browser$': `next/dist/compiled/react-dom${bundledReactChannel}/server.browser`, + 'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client`, + 'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/client.edge`, + 'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.edge`, + 'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${bundledReactChannel}/server.node`, + } + } else if (opts.layer === 'ssr') { + alias = { + 'react/jsx-runtime$': `next/dist/server/future/route-modules/app-page/vendored/shared/react-jsx-runtime`, + 'react/jsx-dev-runtime$': `next/dist/server/future/route-modules/app-page/vendored/shared/react-jsx-dev-runtime`, + react$: `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react`, + 'react-dom$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-dom`, + 'react-dom/server.edge$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-dom-server-edge`, + 'react-server-dom-webpack/client.edge$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-server-dom-webpack-client-edge`, + // not essential but we're providing this alias for people who might use it + 'react-dom/server$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-dom-server-edge`, + } + } else if (opts.layer === 'rsc') { + alias = { + 'react/jsx-runtime$': `next/dist/server/future/route-modules/app-page/vendored/shared/react-jsx-runtime`, + 'react/jsx-dev-runtime$': `next/dist/server/future/route-modules/app-page/vendored/shared/react-jsx-dev-runtime`, + react$: `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react`, + 'react-dom$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-dom`, + 'react-server-dom-webpack/server.edge$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-server-dom-webpack-server-edge`, + 'react-server-dom-webpack/server.node$': `next/dist/server/future/route-modules/app-page/vendored/${opts.layer}/react-server-dom-webpack-server-node`, + // not essential but we're providing this alias for people who might use it. + // A note here is that this will point toward the ReactDOMServer on the SSR layer + // TODO: add the rests + 'react-dom/server.edge$': `next/dist/server/future/route-modules/app-page/vendored/ssr/react-dom-server-edge`, + } + } else { + throw new Error(`Unexpected layer: ${opts.layer}`) } - if (opts.reactSharedSubset) { - alias[ - 'react$' - ] = `next/dist/compiled/react${bundledReactChannel}/react.shared-subset` - } - // Use server rendering stub for RSC - // x-ref: https://github.com/facebook/react/pull/25436 - if (opts.reactDomServerRenderingStub) { + if (opts.isEdgeServer) { + if (opts.layer === 'rsc') { + alias[ + 'react$' + ] = `next/dist/compiled/react${bundledReactChannel}/react.shared-subset` + } + // Use server rendering stub for RSC and SSR + // x-ref: https://github.com/facebook/react/pull/25436 alias[ 'react-dom$' ] = `next/dist/compiled/react-dom${bundledReactChannel}/server-rendering-stub` @@ -1129,14 +1163,6 @@ export default async function getBaseWebpackConfig( '@opentelemetry/api': 'next/dist/compiled/@opentelemetry/api', }), - ...(hasAppDir - ? createRSCAliases(bundledReactChannel, { - reactSharedSubset: false, - reactDomServerRenderingStub: false, - reactProductionProfiling, - }) - : {}), - ...(config.images.loaderFile ? { 'next/dist/shared/lib/image-loader': config.images.loaderFile, @@ -1314,7 +1340,7 @@ export default async function getBaseWebpackConfig( context: string, request: string, dependencyType: string, - layer: string | null, + layer: WebpackLayerName | null, getResolve: ( options: any ) => ( @@ -1339,35 +1365,15 @@ export default async function getBaseWebpackConfig( return `commonjs next/dist/lib/import-next-warning` } - const isAppLayer = [ - WEBPACK_LAYERS.reactServerComponents, - WEBPACK_LAYERS.serverSideRendering, - WEBPACK_LAYERS.appPagesBrowser, - WEBPACK_LAYERS.actionBrowser, - WEBPACK_LAYERS.appRouteHandler, - ].includes(layer!) - - if ( - request === 'react/jsx-dev-runtime' || - request === 'react/jsx-runtime' - ) { - if (isAppLayer) { - return `commonjs next/dist/compiled/${request.replace( - 'react', - 'react' + bundledReactChannel - )}` - } - return - } - - // Special internal modules that must be bundled for Server Components. - if (layer === WEBPACK_LAYERS.reactServerComponents) { - // React needs to be bundled for Server Components so the special - // `react-server` export condition can be used. - if (reactPackagesRegex.test(request)) { - return - } - } + const isAppLayer = ( + [ + WEBPACK_LAYERS.reactServerComponents, + WEBPACK_LAYERS.serverSideRendering, + WEBPACK_LAYERS.appPagesBrowser, + WEBPACK_LAYERS.actionBrowser, + WEBPACK_LAYERS.appRouteHandler, + ] as WebpackLayerName[] + ).includes(layer!) // Relative requires don't need custom resolution, because they // are relative to requests we've already resolved here. @@ -1378,25 +1384,7 @@ export default async function getBaseWebpackConfig( return `commonjs ${request}` } - if (reactPackagesRegex.test(request)) { - // override react-dom to server-rendering-stub for server - if ( - request === 'react-dom' && - (layer === WEBPACK_LAYERS.serverSideRendering || - layer === WEBPACK_LAYERS.reactServerComponents || - layer === WEBPACK_LAYERS.actionBrowser) - ) { - request = `next/dist/compiled/react-dom${bundledReactChannel}/server-rendering-stub` - } else if (isAppLayer) { - request = - 'next/dist/compiled/' + - request.replace( - /^(react-server-dom-webpack|react-dom|react)/, - (name) => { - return name + bundledReactChannel - } - ) - } + if (reactPackagesRegex.test(request) && !isAppLayer) { return `commonjs ${request}` } @@ -1432,7 +1420,6 @@ export default async function getBaseWebpackConfig( * will rewrite the require to the correct bundle location depending on the layer at which the file is being used. */ const resolveNextExternal = (localRes: string) => { - const isSharedRuntime = sharedRuntimePattern.test(localRes) const isExternal = externalPattern.test(localRes) // if the file ends with .external, we need to make it a commonjs require in all cases @@ -1442,42 +1429,6 @@ export default async function getBaseWebpackConfig( // otherwise NFT will get tripped up return `commonjs ${localRes.replace(/.*?next[/\\]dist/, 'next/dist')}` } - // if the file ends with .shared-runtime, we need to make it point to the correct bundle depending on the layer - // this is because each shared-runtime files are unique per bundle, so if you use app-router context in pages, - // it'll be a different instance than the one used in the app-router runtime. - if (isSharedRuntime) { - if (dev) { - return `commonjs ${localRes}` - } - - const name = path.parse(localRes).name.replace('.shared-runtime', '') - - const camelCaseName = name.replace(/-([a-z])/g, (_, w) => - w.toUpperCase() - ) - - // there's no externals for API routes but if need be, they'll need to be added here and have - // their own layer - const runtime = - layer === 'app-route-handler' - ? 'app-route' - : isAppLayer - ? 'app-page' - : 'pages' - return [ - 'commonjs ' + - path.posix.join( - 'next', - 'dist', - 'compiled', - 'next-server', - `${runtime}.runtime.${dev ? 'dev' : 'prod'}` - ), - 'default', - 'sharedModules', - camelCaseName, - ] - } } // Don't bundle @vercel/og nodejs bundle for nodejs runtime. @@ -1522,15 +1473,6 @@ export default async function getBaseWebpackConfig( // Treat react packages and next internals as external for SSR layer, // also map react to builtin ones with require-hook. if (layer === WEBPACK_LAYERS.serverSideRendering) { - if (reactPackagesRegex.test(request)) { - return `commonjs next/dist/compiled/${request.replace( - /^(react-server-dom-webpack|react-dom|react)/, - (name) => { - return name + bundledReactChannel - } - )}` - } - const isRelative = request.startsWith('.') const fullRequest = isRelative ? path.join(context, request).replace(/\\/g, '/') @@ -1730,7 +1672,7 @@ export default async function getBaseWebpackConfig( context, request, dependencyType, - contextInfo.issuerLayer, + contextInfo.issuerLayer as WebpackLayerName, (options) => { const resolveFunction = getResolve(options) return (resolveContext: string, requestToResolve: string) => @@ -2157,11 +2099,11 @@ export default async function getBaseWebpackConfig( // react to the direct file path, not the package name. In that case the condition // will be ignored completely. alias: createRSCAliases(bundledReactChannel, { - reactSharedSubset: true, - reactDomServerRenderingStub: true, reactServerCondition: true, // No server components profiling reactProductionProfiling, + layer: WEBPACK_LAYERS.reactServerComponents, + isEdgeServer, }), }, use: { @@ -2220,10 +2162,10 @@ export default async function getBaseWebpackConfig( // It needs `conditionNames` here to require the proper asset, // when react is acting as dependency of compiled/react-dom. alias: createRSCAliases(bundledReactChannel, { - reactSharedSubset: true, - reactDomServerRenderingStub: true, reactServerCondition: true, reactProductionProfiling, + layer: WEBPACK_LAYERS.reactServerComponents, + isEdgeServer, }), }, }, @@ -2232,10 +2174,10 @@ export default async function getBaseWebpackConfig( issuerLayer: WEBPACK_LAYERS.serverSideRendering, resolve: { alias: createRSCAliases(bundledReactChannel, { - reactSharedSubset: false, - reactDomServerRenderingStub: true, reactServerCondition: false, reactProductionProfiling, + layer: WEBPACK_LAYERS.serverSideRendering, + isEdgeServer, }), }, }, @@ -2247,10 +2189,13 @@ export default async function getBaseWebpackConfig( resolve: { alias: createRSCAliases(bundledReactChannel, { // Only alias server rendering stub in client SSR layer. - reactSharedSubset: false, - reactDomServerRenderingStub: false, + // reactSharedSubset: false, + // reactDomServerRenderingStub: false, reactServerCondition: false, reactProductionProfiling, + // browser: isClient, + layer: WEBPACK_LAYERS.appPagesBrowser, + isEdgeServer, }), }, }, @@ -2483,6 +2428,35 @@ export default async function getBaseWebpackConfig( ].filter(Boolean), }, plugins: [ + isNodeServer && + new webpack.NormalModuleReplacementPlugin( + /\.\/(.+)\.shared-runtime$/, + function (resource) { + const moduleName = path.basename( + resource.request, + '.shared-runtime' + ) + const layer = resource.contextInfo.issuerLayer + + let runtime + + switch (layer) { + case WEBPACK_LAYERS.appRouteHandler: + runtime = 'app-route' + break + case WEBPACK_LAYERS.serverSideRendering: + case WEBPACK_LAYERS.reactServerComponents: + case WEBPACK_LAYERS.appPagesBrowser: + case WEBPACK_LAYERS.actionBrowser: + runtime = 'app-page' + break + default: + runtime = 'pages' + } + + resource.request = `next/dist/server/future/route-modules/${runtime}/vendored/contexts/${moduleName}` + } + ), dev && new MemoryWithGcCachePlugin({ maxGenerations: 5 }), dev && isClient && new ReactRefreshWebpackPlugin(webpack), // Makes sure `Buffer` and `process` are polyfilled in client and flight bundles (same behavior as webpack 4) @@ -2508,6 +2482,7 @@ export default async function getBaseWebpackConfig( isNodeServer, middlewareMatchers, previewModeId, + useServerActions, }) ), isClient && diff --git a/packages/next/src/build/webpack/loaders/next-flight-loader/action-client-wrapper.ts b/packages/next/src/build/webpack/loaders/next-flight-loader/action-client-wrapper.ts index a1095a8497..7288263bb4 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-loader/action-client-wrapper.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-loader/action-client-wrapper.ts @@ -11,7 +11,7 @@ export function createServerReference(id: string) { // we use the default and let Webpack to resolve it to the correct version. // 1: https://github.com/vercel/next.js/blob/16eb80b0b0be13f04a6407943664b5efd8f3d7d0/packages/next/src/server/app-render/use-flight-response.tsx#L24-L26 const { createServerReference: createServerReferenceImpl } = ( - typeof window === 'undefined' + !!process.env.NEXT_RUNTIME ? // eslint-disable-next-line import/no-extraneous-dependencies require('react-server-dom-webpack/client.edge') : // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts b/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts index 940ecd476f..349a3af2c4 100644 --- a/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts +++ b/packages/next/src/build/webpack/plugins/nextjs-require-cache-hot-reloader.ts @@ -12,11 +12,10 @@ const originModules = [ require.resolve('../../../server/require'), require.resolve('../../../server/load-components'), require.resolve('../../../server/next-server'), - require.resolve('../../../server/app-render/use-flight-response'), - require.resolve('../../../compiled/react-server-dom-webpack/client.edge'), - require.resolve( - '../../../compiled/react-server-dom-webpack-experimental/client.edge' - ), + require.resolve('next/dist/compiled/next-server/app-page.runtime.dev.js'), + require.resolve('next/dist/compiled/next-server/app-route.runtime.dev.js'), + require.resolve('next/dist/compiled/next-server/pages.runtime.dev.js'), + require.resolve('next/dist/compiled/next-server/pages-api.runtime.dev.js'), ] const RUNTIME_NAMES = ['webpack-runtime', 'webpack-api-runtime'] @@ -48,19 +47,9 @@ function deleteFromRequireCache(filePath: string) { } export function deleteAppClientCache() { - // ensure we reset the cache for rsc components - // loaded via react-server-dom-webpack - const reactServerDomModId = require.resolve( - 'react-server-dom-webpack/client.edge' + deleteFromRequireCache( + require.resolve('next/dist/compiled/next-server/app-page.runtime.dev.js') ) - const reactServerDomMod = require.cache[reactServerDomModId] - - if (reactServerDomMod) { - for (const child of [...reactServerDomMod.children]) { - deleteFromRequireCache(child.id) - } - deleteFromRequireCache(reactServerDomModId) - } } export function deleteCache(filePath: string) { diff --git a/packages/next/src/client/components/router-reducer/fetch-server-response.ts b/packages/next/src/client/components/router-reducer/fetch-server-response.ts index 689d27df77..7e13551cbc 100644 --- a/packages/next/src/client/components/router-reducer/fetch-server-response.ts +++ b/packages/next/src/client/components/router-reducer/fetch-server-response.ts @@ -2,7 +2,15 @@ // @ts-ignore // eslint-disable-next-line import/no-extraneous-dependencies -import { createFromFetch } from 'react-server-dom-webpack/client' +// import { createFromFetch } from 'react-server-dom-webpack/client' +const { createFromFetch } = ( + !!process.env.NEXT_RUNTIME + ? // eslint-disable-next-line import/no-extraneous-dependencies + require('react-server-dom-webpack/client.edge') + : // eslint-disable-next-line import/no-extraneous-dependencies + require('react-server-dom-webpack/client') +) as typeof import('react-server-dom-webpack/client') + import type { FlightRouterState, FlightData, diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts index 0c6caaba74..6fc0afbf06 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts @@ -12,10 +12,17 @@ import { } from '../../app-router-headers' import { createRecordFromThenable } from '../create-record-from-thenable' import { readRecordValue } from '../read-record-value' -// eslint-disable-next-line import/no-extraneous-dependencies -import { createFromFetch } from 'react-server-dom-webpack/client' -// eslint-disable-next-line import/no-extraneous-dependencies -import { encodeReply } from 'react-server-dom-webpack/client' +// // eslint-disable-next-line import/no-extraneous-dependencies +// import { createFromFetch } from 'react-server-dom-webpack/client' +// // eslint-disable-next-line import/no-extraneous-dependencies +// import { encodeReply } from 'react-server-dom-webpack/client' +const { createFromFetch, encodeReply } = ( + !!process.env.NEXT_RUNTIME + ? // eslint-disable-next-line import/no-extraneous-dependencies + require('react-server-dom-webpack/client.edge') + : // eslint-disable-next-line import/no-extraneous-dependencies + require('react-server-dom-webpack/client') +) as typeof import('react-server-dom-webpack/client') import { ReadonlyReducerState, diff --git a/packages/next/src/client/index.tsx b/packages/next/src/client/index.tsx index 45cdbf650e..487386b781 100644 --- a/packages/next/src/client/index.tsx +++ b/packages/next/src/client/index.tsx @@ -19,7 +19,7 @@ import { urlQueryToSearchParams, assign, } from '../shared/lib/router/utils/querystring' -import { setConfig } from '../shared/lib/runtime-config.shared-runtime' +import { setConfig } from '../shared/lib/runtime-config.external' import { getURL, loadGetInitialProps, @@ -43,7 +43,7 @@ import { adaptForAppRouterInstance, adaptForSearchParams, PathnameContextProviderAdapter, -} from '../shared/lib/router/adapters.shared-runtime' +} from '../shared/lib/router/adapters' import { SearchParamsContext } from '../shared/lib/hooks-client-context.shared-runtime' import onRecoverableError from './on-recoverable-error' import tracer from './tracing/tracer' diff --git a/packages/next/src/export/index.ts b/packages/next/src/export/index.ts index 7c2fc4ebd8..f8f32f48d5 100644 --- a/packages/next/src/export/index.ts +++ b/packages/next/src/export/index.ts @@ -730,6 +730,7 @@ export default async function exportApp( fetchCacheKeyPrefix: nextConfig.experimental.fetchCacheKeyPrefix, incrementalCacheHandlerPath: nextConfig.experimental.incrementalCacheHandlerPath, + serverActions: nextConfig.experimental.serverActions, }) for (const validation of result.ampValidations || []) { diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts index 0a554e32da..7329d10687 100644 --- a/packages/next/src/export/worker.ts +++ b/packages/next/src/export/worker.ts @@ -13,6 +13,7 @@ import type { OutgoingHttpHeaders } from 'http' // Polyfill fetch for the export worker. import '../server/node-polyfill-fetch' import '../server/node-environment' +process.env.NEXT_IS_EXPORT_WORKER = 'true' import { extname, join, dirname, sep, posix } from 'path' import fs, { promises } from 'fs' @@ -59,7 +60,7 @@ import { RSC, } from '../client/components/app-router-headers' -const envConfig = require('../shared/lib/runtime-config.shared-runtime') +const envConfig = require('../shared/lib/runtime-config.external') ;(globalThis as any).__NEXT_DATA__ = { nextExport: true, @@ -96,6 +97,7 @@ interface ExportPageInput { incrementalCacheHandlerPath?: string fetchCacheKeyPrefix?: string nextConfigOutput?: NextConfigComplete['output'] + serverActions?: boolean } interface ExportPageResults { @@ -152,6 +154,7 @@ export default async function exportPage({ fetchCache, fetchCacheKeyPrefix, incrementalCacheHandlerPath, + serverActions, }: ExportPageInput): Promise { setHttpClientAndAgentOptions({ httpAgentOptions, @@ -168,6 +171,9 @@ export default async function exportPage({ if (renderOpts.deploymentId) { process.env.NEXT_DEPLOYMENT_ID = renderOpts.deploymentId } + if (serverActions) { + process.env.__NEXT_EXPERIMENTAL_REACT = 'true' + } const { query: originalQuery = {} } = pathMap const { page } = pathMap const pathname = normalizeAppPath(page) @@ -406,7 +412,7 @@ export default async function exportPage({ // functions during runtime just for prefetching const { renderToHTMLOrFlight } = - require('../server/app-render/app-render') as typeof import('../server/app-render/app-render') + require('../server/future/route-modules/app-page/module.compiled') as typeof import('../server/app-render/app-render') req.headers[RSC.toLowerCase()] = '1' req.headers[NEXT_URL.toLowerCase()] = path req.headers[NEXT_ROUTER_PREFETCH.toLowerCase()] = '1' diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index 65370408b0..92702f6231 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -138,7 +138,10 @@ const WEBPACK_LAYERS_NAMES = { * The layer for the server bundle for App Route handlers. */ appRouteHandler: 'app-route-handler', -} +} as const + +export type WebpackLayerName = + (typeof WEBPACK_LAYERS_NAMES)[keyof typeof WEBPACK_LAYERS_NAMES] export const WEBPACK_LAYERS = { ...WEBPACK_LAYERS_NAMES, diff --git a/packages/next/src/server/app-render/use-flight-response.tsx b/packages/next/src/server/app-render/use-flight-response.tsx index 0f09406806..a7d0318eeb 100644 --- a/packages/next/src/server/app-render/use-flight-response.tsx +++ b/packages/next/src/server/app-render/use-flight-response.tsx @@ -21,10 +21,9 @@ export function useFlightResponse( return flightResponseRef.current } // react-server-dom-webpack/client.edge must not be hoisted for require cache clearing to work correctly - const { createFromReadableStream } = process.env.NEXT_MINIMAL - ? // @ts-ignore - __non_webpack_require__(`react-server-dom-webpack/client.edge`) - : require(`react-server-dom-webpack/client.edge`) + const { + createFromReadableStream, + } = require(`react-server-dom-webpack/client.edge`) const [renderStream, forwardStream] = req.tee() const res = createFromReadableStream(renderStream, { diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 10bf0c1a9b..7b61af7e1f 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -51,7 +51,7 @@ import { } from '../shared/lib/constants' import { isDynamicRoute } from '../shared/lib/router/utils' import { checkIsOnDemandRevalidate } from './api-utils' -import { setConfig } from '../shared/lib/runtime-config.shared-runtime' +import { setConfig } from '../shared/lib/runtime-config.external' import { setRevalidateHeaders } from './send-payload/revalidate-headers' import { execOnce } from '../shared/lib/utils' diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts index cc870052e9..cd7d5305fa 100644 --- a/packages/next/src/server/dev/static-paths-worker.ts +++ b/packages/next/src/server/dev/static-paths-worker.ts @@ -58,7 +58,7 @@ export async function loadStaticPaths({ fallback?: boolean | 'blocking' }> { // update work memory runtime-config - require('../../shared/lib/runtime-config.shared-runtime').setConfig(config) + require('../../shared/lib/runtime-config.external').setConfig(config) setHttpClientAndAgentOptions({ httpAgentOptions, }) diff --git a/packages/next/src/server/esm-loader.mts b/packages/next/src/server/esm-loader.mts index 313bde8b39..3e66496070 100644 --- a/packages/next/src/server/esm-loader.mts +++ b/packages/next/src/server/esm-loader.mts @@ -3,13 +3,10 @@ import module from 'module' const require = module.createRequire(import.meta.url) export function resolve(specifier: string, context: any, nextResolve: any) { - const { overrideReact, hookPropertyMap } = require(process.env.NEXT_YARN_PNP + const { hookPropertyMap } = require(process.env.NEXT_YARN_PNP ? './import-overrides' : 'next/dist/server/import-overrides') as typeof import('./import-overrides') - // In case the environment variable is set after the module is loaded. - overrideReact() - const hookResolved = hookPropertyMap.get(specifier) if (hookResolved) { specifier = hookResolved diff --git a/packages/next/src/server/future/route-modules/app-page/module.compiled.ts b/packages/next/src/server/future/route-modules/app-page/module.compiled.ts index 78601739ac..f25b70992b 100644 --- a/packages/next/src/server/future/route-modules/app-page/module.compiled.ts +++ b/packages/next/src/server/future/route-modules/app-page/module.compiled.ts @@ -1,11 +1,21 @@ if (process.env.NEXT_RUNTIME === 'edge') { module.exports = require('next/dist/server/future/route-modules/app-page/module.js') } else { - if (process.env.NODE_ENV === 'development') { - module.exports = require('next/dist/compiled/next-server/app-page.runtime.dev.js') - } else if (process.env.TURBOPACK) { - module.exports = require('next/dist/compiled/next-server/app-page-turbo.runtime.prod.js') + if (process.env.__NEXT_EXPERIMENTAL_REACT) { + if (process.env.NODE_ENV === 'development') { + module.exports = require('next/dist/compiled/next-server/app-page-experimental.runtime.dev.js') + } else if (process.env.TURBOPACK) { + module.exports = require('next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js') + } else { + module.exports = require('next/dist/compiled/next-server/app-page-experimental.runtime.prod.js') + } } else { - module.exports = require('next/dist/compiled/next-server/app-page.runtime.prod.js') + if (process.env.NODE_ENV === 'development') { + module.exports = require('next/dist/compiled/next-server/app-page.runtime.dev.js') + } else if (process.env.TURBOPACK) { + module.exports = require('next/dist/compiled/next-server/app-page-turbo.runtime.prod.js') + } else { + module.exports = require('next/dist/compiled/next-server/app-page.runtime.prod.js') + } } } diff --git a/packages/next/src/server/future/route-modules/app-page/module.ts b/packages/next/src/server/future/route-modules/app-page/module.ts index daa0291a1c..5cc33c1a60 100644 --- a/packages/next/src/server/future/route-modules/app-page/module.ts +++ b/packages/next/src/server/future/route-modules/app-page/module.ts @@ -11,7 +11,18 @@ import { type RouteModuleOptions, type RouteModuleHandleContext, } from '../route-module' -import * as sharedModules from './shared-modules' +import * as vendoredContexts from './vendored/contexts/entrypoints' + +let vendoredReactRSC +let vendoredReactSSR +let vendoredReactShared + +// the vendored Reacts are loaded from their original source in the edge runtime +if (process.env.NEXT_RUNTIME !== 'edge') { + vendoredReactRSC = require('./vendored/rsc/entrypoints') + vendoredReactSSR = require('./vendored/ssr/entrypoints') + vendoredReactShared = require('./vendored/shared/entrypoints') +} type AppPageUserlandModule = { /** @@ -35,8 +46,6 @@ export class AppPageRouteModule extends RouteModule< AppPageRouteDefinition, AppPageUserlandModule > { - static readonly sharedModules = sharedModules - public render( req: IncomingMessage, res: ServerResponse, @@ -52,6 +61,13 @@ export class AppPageRouteModule extends RouteModule< } } -export { renderToHTMLOrFlight } +const vendored = { + 'react-rsc': vendoredReactRSC, + 'react-ssr': vendoredReactSSR, + 'react-shared': vendoredReactShared, + contexts: vendoredContexts, +} + +export { renderToHTMLOrFlight, vendored } export default AppPageRouteModule diff --git a/packages/next/src/server/future/route-modules/app-page/shared-modules.ts b/packages/next/src/server/future/route-modules/app-page/shared-modules.ts deleted file mode 100644 index e986c1bad3..0000000000 --- a/packages/next/src/server/future/route-modules/app-page/shared-modules.ts +++ /dev/null @@ -1,13 +0,0 @@ -// the name of the export has to be the camelCase version of the file name (without the extension) -export * as headManagerContext from '../../../../shared/lib/head-manager-context.shared-runtime' -export * as serverInsertedHtml from '../../../../shared/lib/server-inserted-html.shared-runtime' -export * as appRouterContext from '../../../../shared/lib/app-router-context.shared-runtime' -export * as hooksClientContext from '../../../../shared/lib/hooks-client-context.shared-runtime' -export * as routerContext from '../../../../shared/lib/router-context.shared-runtime' -export * as htmlContext from '../../../../shared/lib/html-context.shared-runtime' -export * as ampContext from '../../../../shared/lib/amp-context.shared-runtime' -export * as adapters from '../../../../shared/lib/router/adapters.shared-runtime' -export * as loadableContext from '../../../../shared/lib/loadable-context.shared-runtime' -export * as imageConfigContext from '../../../../shared/lib/image-config-context.shared-runtime' -export * as runtimeConfig from '../../../../shared/lib/runtime-config.shared-runtime' -export * as loadable from '../../../../shared/lib/loadable.shared-runtime' diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/amp-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/amp-context.ts new file mode 100644 index 0000000000..474e4f3877 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/amp-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].AmpContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/app-router-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/app-router-context.ts new file mode 100644 index 0000000000..a112b6b267 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/app-router-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].AppRouterContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/entrypoints.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/entrypoints.ts new file mode 100644 index 0000000000..52274993f0 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/entrypoints.ts @@ -0,0 +1,10 @@ +export * as HeadManagerContext from '../../../../../../shared/lib/head-manager-context.shared-runtime' +export * as ServerInsertedHtml from '../../../../../../shared/lib/server-inserted-html.shared-runtime' +export * as AppRouterContext from '../../../../../../shared/lib/app-router-context.shared-runtime' +export * as HooksClientContext from '../../../../../../shared/lib/hooks-client-context.shared-runtime' +export * as RouterContext from '../../../../../../shared/lib/router-context.shared-runtime' +export * as HtmlContext from '../../../../../../shared/lib/html-context.shared-runtime' +export * as AmpContext from '../../../../../../shared/lib/amp-context.shared-runtime' +export * as LoadableContext from '../../../../../../shared/lib/loadable-context.shared-runtime' +export * as ImageConfigContext from '../../../../../../shared/lib/image-config-context.shared-runtime' +export * as Loadable from '../../../../../../shared/lib/loadable.shared-runtime' diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/head-manager-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/head-manager-context.ts new file mode 100644 index 0000000000..b7f082e333 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/head-manager-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].HeadManagerContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/hooks-client-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/hooks-client-context.ts new file mode 100644 index 0000000000..29aa771033 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/hooks-client-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].HooksClientContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/html-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/html-context.ts new file mode 100644 index 0000000000..ae1126abb6 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/html-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].HtmlContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/image-config-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/image-config-context.ts new file mode 100644 index 0000000000..3537107ae9 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/image-config-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].ImageConfigContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable-context.ts new file mode 100644 index 0000000000..de9e07464c --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].LoadableContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable.ts new file mode 100644 index 0000000000..f67d05a251 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/loadable.ts @@ -0,0 +1 @@ +module.exports = require('../../module.compiled').vendored['contexts'].Loadable diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/router-context.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/router-context.ts new file mode 100644 index 0000000000..5a2f92e053 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/router-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].RouterContext diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/contexts/server-inserted-html.ts b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/server-inserted-html.ts new file mode 100644 index 0000000000..3c488b0db8 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/contexts/server-inserted-html.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].ServerInsertedHtml diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/rsc/entrypoints.ts b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/entrypoints.ts new file mode 100644 index 0000000000..1d1273b91e --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/entrypoints.ts @@ -0,0 +1,15 @@ +import * as React from 'react' + +import * as ReactDOM from 'react-dom/server-rendering-stub' + +// eslint-disable-next-line import/no-extraneous-dependencies +import * as ReactServerDOMWebpackServerNode from 'react-server-dom-webpack/server.node' +// eslint-disable-next-line import/no-extraneous-dependencies +import * as ReactServerDOMWebpackServerEdge from 'react-server-dom-webpack/server.edge' + +export { + React, + ReactDOM, + ReactServerDOMWebpackServerNode, + ReactServerDOMWebpackServerEdge, +} diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-dom.ts b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-dom.ts new file mode 100644 index 0000000000..5d369d4e33 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-dom.ts @@ -0,0 +1 @@ +module.exports = require('../../module.compiled').vendored['react-rsc'].ReactDOM diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-edge.ts b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-edge.ts new file mode 100644 index 0000000000..0ae9701b1e --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-edge.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'react-rsc' +].ReactServerDOMWebpackServerEdge diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-node.ts b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-node.ts new file mode 100644 index 0000000000..fbb7bd17d4 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react-server-dom-webpack-server-node.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'react-rsc' +].ReactServerDOMWebpackServerNode diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react.ts b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react.ts new file mode 100644 index 0000000000..41b4432877 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/rsc/react.ts @@ -0,0 +1 @@ +module.exports = require('../../module.compiled').vendored['react-rsc'].React diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/shared/entrypoints.ts b/packages/next/src/server/future/route-modules/app-page/vendored/shared/entrypoints.ts new file mode 100644 index 0000000000..b5a1bf1a3f --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/shared/entrypoints.ts @@ -0,0 +1,4 @@ +import * as ReactJsxDevRuntime from 'react/jsx-dev-runtime' +import * as ReactJsxRuntime from 'react/jsx-runtime' + +export { ReactJsxDevRuntime, ReactJsxRuntime } diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-dev-runtime.ts b/packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-dev-runtime.ts new file mode 100644 index 0000000000..9623bb4a90 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-dev-runtime.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'react-shared' +].ReactJsxDevRuntime diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-runtime.ts b/packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-runtime.ts new file mode 100644 index 0000000000..b7d24f304f --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/shared/react-jsx-runtime.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'react-shared' +].ReactJsxRuntime diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/ssr/entrypoints.ts b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/entrypoints.ts new file mode 100644 index 0000000000..aa071a9567 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/entrypoints.ts @@ -0,0 +1,9 @@ +import * as React from 'react' +import * as ReactDOM from 'react-dom/server-rendering-stub' + +// eslint-disable-next-line import/no-extraneous-dependencies +import * as ReactDOMServerEdge from 'react-dom/server.edge' +// eslint-disable-next-line import/no-extraneous-dependencies +import * as ReactServerDOMWebpackClientEdge from 'react-server-dom-webpack/client.edge' + +export { React, ReactDOM, ReactDOMServerEdge, ReactServerDOMWebpackClientEdge } diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom-server-edge.ts b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom-server-edge.ts new file mode 100644 index 0000000000..0908aefd95 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom-server-edge.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'react-ssr' +].ReactDOMServerEdge diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom.ts b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom.ts new file mode 100644 index 0000000000..378577fa80 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-dom.ts @@ -0,0 +1 @@ +module.exports = require('../../module.compiled').vendored['react-ssr'].ReactDOM diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-server-dom-webpack-client-edge.ts b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-server-dom-webpack-client-edge.ts new file mode 100644 index 0000000000..5398d7eecc --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react-server-dom-webpack-client-edge.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'react-ssr' +].ReactServerDOMWebpackClientEdge diff --git a/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react.ts b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react.ts new file mode 100644 index 0000000000..c294760147 --- /dev/null +++ b/packages/next/src/server/future/route-modules/app-page/vendored/ssr/react.ts @@ -0,0 +1 @@ +module.exports = require('../../module.compiled').vendored['react-ssr'].React diff --git a/packages/next/src/server/future/route-modules/pages/module.ts b/packages/next/src/server/future/route-modules/pages/module.ts index e2730ef668..3abbe1da06 100644 --- a/packages/next/src/server/future/route-modules/pages/module.ts +++ b/packages/next/src/server/future/route-modules/pages/module.ts @@ -18,7 +18,7 @@ import { type RouteModuleOptions, } from '../route-module' import { renderToHTMLImpl, renderToHTML } from '../../../render' -import * as sharedModules from './shared-modules' +import * as vendoredContexts from './vendored/contexts/entrypoints' /** * The userland module for a page. This is the module that is exported from the @@ -105,8 +105,6 @@ export class PagesRouteModule extends RouteModule< > { private readonly components: PagesComponents - static readonly sharedModules = sharedModules - constructor(options: PagesRouteModuleOptions) { super(options) @@ -132,7 +130,11 @@ export class PagesRouteModule extends RouteModule< } } +const vendored = { + contexts: vendoredContexts, +} + // needed for the static build -export { renderToHTML } +export { renderToHTML, vendored } export default PagesRouteModule diff --git a/packages/next/src/server/future/route-modules/pages/shared-modules.ts b/packages/next/src/server/future/route-modules/pages/shared-modules.ts deleted file mode 100644 index 55cdfbdeca..0000000000 --- a/packages/next/src/server/future/route-modules/pages/shared-modules.ts +++ /dev/null @@ -1,12 +0,0 @@ -// the name of the export has to be the camelCase version of the file name (without the extension) -export * as htmlContext from '../../../../shared/lib/html-context.shared-runtime' -export * as routerContext from '../../../../shared/lib/router-context.shared-runtime' -export * as ampContext from '../../../../shared/lib/amp-context.shared-runtime' -export * as headManagerContext from '../../../../shared/lib/head-manager-context.shared-runtime' -export * as adapters from '../../../../shared/lib/router/adapters.shared-runtime' -export * as loadableContext from '../../../../shared/lib/loadable-context.shared-runtime' -export * as appRouterContext from '../../../../shared/lib/app-router-context.shared-runtime' -export * as hooksClientContext from '../../../../shared/lib/hooks-client-context.shared-runtime' -export * as imageConfigContext from '../../../../shared/lib/image-config-context.shared-runtime' -export * as runtimeConfig from '../../../../shared/lib/runtime-config.shared-runtime' -export * as loadable from '../../../../shared/lib/loadable.shared-runtime' diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/amp-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/amp-context.ts new file mode 100644 index 0000000000..474e4f3877 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/amp-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].AmpContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/app-router-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/app-router-context.ts new file mode 100644 index 0000000000..a112b6b267 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/app-router-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].AppRouterContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/entrypoints.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/entrypoints.ts new file mode 100644 index 0000000000..3e244ec2d3 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/entrypoints.ts @@ -0,0 +1,10 @@ +export * as RouterContext from '../../../../../../shared/lib/router-context.shared-runtime' +export * as LoadableContext from '../../../../../../shared/lib/loadable-context.shared-runtime' +export * as Loadable from '../../../../../../shared/lib/loadable.shared-runtime' +export * as ImageConfigContext from '../../../../../../shared/lib/image-config-context.shared-runtime' +export * as HtmlContext from '../../../../../../shared/lib/html-context.shared-runtime' +export * as HooksClientContext from '../../../../../../shared/lib/hooks-client-context.shared-runtime' +export * as HeadManagerContext from '../../../../../../shared/lib/head-manager-context.shared-runtime' +export * as AppRouterContext from '../../../../../../shared/lib/app-router-context.shared-runtime' +export * as AmpContext from '../../../../../../shared/lib/amp-context.shared-runtime' +export * as ServerInsertedHtml from '../../../../../../shared/lib/server-inserted-html.shared-runtime' diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/head-manager-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/head-manager-context.ts new file mode 100644 index 0000000000..b7f082e333 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/head-manager-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].HeadManagerContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/hooks-client-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/hooks-client-context.ts new file mode 100644 index 0000000000..29aa771033 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/hooks-client-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].HooksClientContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/html-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/html-context.ts new file mode 100644 index 0000000000..ae1126abb6 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/html-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].HtmlContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/image-config-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/image-config-context.ts new file mode 100644 index 0000000000..3537107ae9 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/image-config-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].ImageConfigContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable-context.ts new file mode 100644 index 0000000000..de9e07464c --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].LoadableContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable.ts new file mode 100644 index 0000000000..f67d05a251 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/loadable.ts @@ -0,0 +1 @@ +module.exports = require('../../module.compiled').vendored['contexts'].Loadable diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/router-context.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/router-context.ts new file mode 100644 index 0000000000..5a2f92e053 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/router-context.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].RouterContext diff --git a/packages/next/src/server/future/route-modules/pages/vendored/contexts/server-inserted-html.ts b/packages/next/src/server/future/route-modules/pages/vendored/contexts/server-inserted-html.ts new file mode 100644 index 0000000000..3c488b0db8 --- /dev/null +++ b/packages/next/src/server/future/route-modules/pages/vendored/contexts/server-inserted-html.ts @@ -0,0 +1,3 @@ +module.exports = require('../../module.compiled').vendored[ + 'contexts' +].ServerInsertedHtml diff --git a/packages/next/src/server/import-overrides.ts b/packages/next/src/server/import-overrides.ts index 831609c866..c72f42612e 100644 --- a/packages/next/src/server/import-overrides.ts +++ b/packages/next/src/server/import-overrides.ts @@ -23,57 +23,6 @@ export const defaultOverrides = { : resolve('styled-jsx/style', nextPaths), } -export const baseOverrides = { - react: 'next/dist/compiled/react', - 'react/package.json': 'next/dist/compiled/react/package.json', - 'react/jsx-runtime': 'next/dist/compiled/react/jsx-runtime', - 'react/jsx-dev-runtime': 'next/dist/compiled/react/jsx-dev-runtime', - 'react-dom': 'next/dist/compiled/react-dom/server-rendering-stub', - 'react-dom/package.json': 'next/dist/compiled/react-dom/package.json', - 'react-dom/client': 'next/dist/compiled/react-dom/client', - 'react-dom/server': 'next/dist/compiled/react-dom/server', - // when the require hook applies, we want to use the edge version, for both app and pages - 'react-dom/server.browser': 'next/dist/compiled/react-dom/server.edge', - 'react-dom/server.edge': 'next/dist/compiled/react-dom/server.edge', - 'react-server-dom-webpack/client': - 'next/dist/compiled/react-server-dom-webpack/client', - 'react-server-dom-webpack/client.edge': - 'next/dist/compiled/react-server-dom-webpack/client.edge', - 'react-server-dom-webpack/server.edge': - 'next/dist/compiled/react-server-dom-webpack/server.edge', - 'react-server-dom-webpack/server.node': - 'next/dist/compiled/react-server-dom-webpack/server.node', -} - -export const experimentalOverrides = { - react: 'next/dist/compiled/react-experimental', - 'react/jsx-runtime': 'next/dist/compiled/react-experimental/jsx-runtime', - 'react/jsx-dev-runtime': - 'next/dist/compiled/react-experimental/jsx-dev-runtime', - 'react-dom': - 'next/dist/compiled/react-dom-experimental/server-rendering-stub', - 'react/package.json': 'next/dist/compiled/react-experimental/package.json', - 'react-dom/package.json': - 'next/dist/compiled/react-dom-experimental/package.json', - 'react-dom/client': 'next/dist/compiled/react-dom-experimental/client', - 'react-dom/server': 'next/dist/compiled/react-dom-experimental/server', - // when the require hook applies, we want to use the edge version, for both app and pages - 'react-dom/server.browser': - 'next/dist/compiled/react-dom-experimental/server.edge', - 'react-dom/server.edge': - 'next/dist/compiled/react-dom-experimental/server.edge', - 'react-server-dom-webpack/client': - 'next/dist/compiled/react-server-dom-webpack-experimental/client', - 'react-server-dom-webpack/client.edge': - 'next/dist/compiled/react-server-dom-webpack-experimental/client.edge', - 'react-server-dom-webpack/server.edge': - 'next/dist/compiled/react-server-dom-webpack-experimental/server.edge', - 'react-server-dom-webpack/server.node': - 'next/dist/compiled/react-server-dom-webpack-experimental/server.node', -} - -let aliasedPrebundledReact = false - const toResolveMap = (map: Record): [string, string][] => Object.entries(map).map(([key, value]) => [key, resolve(value, nextPaths)]) @@ -84,20 +33,3 @@ export function addHookAliases(aliases: [string, string][] = []) { } addHookAliases(toResolveMap(defaultOverrides)) - -// Override built-in React packages if necessary -export function overrideReact() { - if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT && !aliasedPrebundledReact) { - aliasedPrebundledReact = true - - // Require these modules with static paths to make sure they are tracked by - // NFT when building the app in standalone mode, as we are now conditionally - // aliasing them it's tricky to track them in build time. - if (process.env.__NEXT_PRIVATE_PREBUNDLED_REACT === 'experimental') { - addHookAliases(toResolveMap(experimentalOverrides)) - } else { - addHookAliases(toResolveMap(baseOverrides)) - } - } -} -overrideReact() 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 c1777d1312..8db318f8b2 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev.ts @@ -1729,6 +1729,7 @@ async function startWatcher(opts: SetupOpts) { isNodeServer, middlewareMatchers: undefined, previewModeId: undefined, + useServerActions: !!nextConfig.experimental.serverActions, }) Object.keys(plugin.definitions).forEach((key) => { diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 228ed91722..7df9637d22 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -91,7 +91,7 @@ import { adaptForAppRouterInstance, adaptForSearchParams, PathnameContextProviderAdapter, -} from '../shared/lib/router/adapters.shared-runtime' +} from '../shared/lib/router/adapters' import { AppRouterContext } from '../shared/lib/app-router-context.shared-runtime' import { SearchParamsContext } from '../shared/lib/hooks-client-context.shared-runtime' import { getTracer } from './lib/trace/tracer' diff --git a/packages/next/src/server/require-hook.js b/packages/next/src/server/require-hook.js index ce9e30e6e9..6e6c89ffc3 100644 --- a/packages/next/src/server/require-hook.js +++ b/packages/next/src/server/require-hook.js @@ -8,7 +8,8 @@ const mod = require('module') const originalRequire = mod.prototype.require const resolveFilename = mod._resolveFilename -const { overrideReact, hookPropertyMap } = require('./import-overrides') +const { hookPropertyMap } = require('./import-overrides') +const { PHASE_PRODUCTION_BUILD } = require('../shared/lib/constants') mod._resolveFilename = function ( originalResolveFilename, @@ -18,9 +19,6 @@ mod._resolveFilename = function ( isMain, options ) { - // In case the environment variable is set after the module is loaded. - overrideReact() - const hookResolved = requestMap.get(request) if (hookResolved) request = hookResolved @@ -32,21 +30,20 @@ mod._resolveFilename = function ( // This is a hack to make sure that if a user requires a Next.js module that wasn't bundled // that needs to point to the rendering runtime version, it will point to the correct one. // This can happen on `pages` when a user requires a dependency that uses next/image for example. -// This is only needed in production as in development we fallback to the external version. -if (process.env.NODE_ENV !== 'development' && !process.env.TURBOPACK) { - mod.prototype.require = function (request) { - if (request.endsWith('.shared-runtime')) { - const isAppRequire = process.env.__NEXT_PRIVATE_RUNTIME_TYPE === 'app' - const currentRuntime = `${ - isAppRequire - ? 'next/dist/compiled/next-server/app-page.runtime' - : 'next/dist/compiled/next-server/pages.runtime' - }.prod` - const base = path.basename(request, '.shared-runtime') - const camelized = base.replace(/-([a-z])/g, (g) => g[1].toUpperCase()) - const instance = originalRequire.call(this, currentRuntime) - return instance.default.sharedModules[camelized] - } - return originalRequire.call(this, request) +mod.prototype.require = function (request) { + if ( + (process.env.NEXT_PHASE !== PHASE_PRODUCTION_BUILD || + process.env.NEXT_IS_EXPORT_WORKER) && + request.endsWith('.shared-runtime') + ) { + return originalRequire.call( + this, + `next/dist/server/future/route-modules/pages/vendored/contexts/${path.basename( + request, + '.shared-runtime' + )}` + ) } + + return originalRequire.call(this, request) } diff --git a/packages/next/src/shared/lib/router/adapters.test.tsx b/packages/next/src/shared/lib/router/adapters.test.tsx index e47ce2174d..fa8e48f2fc 100644 --- a/packages/next/src/shared/lib/router/adapters.test.tsx +++ b/packages/next/src/shared/lib/router/adapters.test.tsx @@ -1,4 +1,4 @@ -import { adaptForAppRouterInstance } from './adapters.shared-runtime' +import { adaptForAppRouterInstance } from './adapters' import { NextRouter } from './router' describe('adaptForAppRouterInstance', () => { diff --git a/packages/next/src/shared/lib/router/adapters.shared-runtime.tsx b/packages/next/src/shared/lib/router/adapters.tsx similarity index 100% rename from packages/next/src/shared/lib/router/adapters.shared-runtime.tsx rename to packages/next/src/shared/lib/router/adapters.tsx diff --git a/packages/next/src/shared/lib/runtime-config.shared-runtime.ts b/packages/next/src/shared/lib/runtime-config.external.ts similarity index 100% rename from packages/next/src/shared/lib/runtime-config.shared-runtime.ts rename to packages/next/src/shared/lib/runtime-config.external.ts diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 476118fe16..30b28b90a9 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -2683,38 +2683,137 @@ export async function release(task) { await task.clear('dist').start('build') } -export async function next_bundle_prod(task, opts) { +export async function next_bundle_app_turbo(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + turbo: true, + bundleType: 'app', + }), + name: 'next-bundle-app-turbo', + }) +} + +export async function next_bundle_app_prod(task, opts) { await task.source('dist').webpack({ watch: opts.dev, config: require('./webpack.config')({ dev: false, + bundleType: 'app', }), - name: 'next-bundle-prod', + name: 'next-bundle-app-prod', }) } -export async function next_bundle_dev(task, opts) { +export async function next_bundle_app_dev(task, opts) { await task.source('dist').webpack({ watch: opts.dev, config: require('./webpack.config')({ dev: true, + bundleType: 'app', }), - name: 'next-bundle-dev', + name: 'next-bundle-app-dev', }) } -export async function next_bundle_turbo_prod(task, opts) { +export async function next_bundle_app_turbo_experimental(task, opts) { await task.source('dist').webpack({ watch: opts.dev, config: require('./webpack.config')({ turbo: true, + bundleType: 'app', + experimental: true, + }), + name: 'next-bundle-app-turbo-experimental', + }) +} + +export async function next_bundle_app_prod_experimental(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + dev: false, + bundleType: 'app', + experimental: true, }), - name: 'next-bundle-prod-turbo', + name: 'next-bundle-app-prod-experimental', }) } + +export async function next_bundle_app_dev_experimental(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + dev: true, + bundleType: 'app', + experimental: true, + }), + name: 'next-bundle-app-dev-experimental', + }) +} + +export async function next_bundle_pages_prod(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + dev: false, + bundleType: 'pages', + }), + name: 'next-bundle-pages-prod', + }) +} + +export async function next_bundle_pages_dev(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + dev: true, + bundleType: 'pages', + }), + name: 'next-bundle-pages-dev', + }) +} + +export async function next_bundle_pages_turbo(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + turbo: true, + bundleType: 'pages', + }), + name: 'next-bundle-pages-turbo', + }) +} + +export async function next_bundle_server(task, opts) { + await task.source('dist').webpack({ + watch: opts.dev, + config: require('./webpack.config')({ + dev: false, + bundleType: 'server', + }), + name: 'next-bundle-server', + }) +} + export async function next_bundle(task, opts) { await task.parallel( - ['next_bundle_prod', 'next_bundle_dev', 'next_bundle_turbo_prod'], + [ + // builds the app (route/page) bundles + 'next_bundle_app_turbo', + 'next_bundle_app_prod', + 'next_bundle_app_dev', + // builds the app (route/page) bundles with react experimental + 'next_bundle_app_turbo_experimental', + 'next_bundle_app_prod_experimental', + 'next_bundle_app_dev_experimental', + // builds the pages (page/api) bundles + 'next_bundle_pages_prod', + 'next_bundle_pages_dev', + 'next_bundle_pages_turbo', + // builds the minimal server + 'next_bundle_server', + ], opts ) } diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index b22d2bd295..e983122327 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -20,8 +20,13 @@ declare module 'next/dist/compiled/react-dom/server' declare module 'next/dist/compiled/react-dom/server.edge' declare module 'next/dist/compiled/react-dom/server.browser' declare module 'next/dist/compiled/browserslist' + declare module 'react-server-dom-webpack/client' declare module 'react-server-dom-webpack/server.edge' +declare module 'react-server-dom-webpack/server.node' +declare module 'react-server-dom-webpack/client.edge' + +declare module 'react-dom/server-rendering-stub' declare module 'react-dom/server.browser' declare module 'react-dom/server.edge' diff --git a/packages/next/webpack.config.js b/packages/next/webpack.config.js index 83fe9d3009..d03ff0cfb5 100644 --- a/packages/next/webpack.config.js +++ b/packages/next/webpack.config.js @@ -3,7 +3,7 @@ const path = require('path') const TerserPlugin = require('terser-webpack-plugin') const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') -const minimalExternals = [ +const pagesExternals = [ 'react', 'react/package.json', 'react/jsx-runtime', @@ -18,6 +18,33 @@ const minimalExternals = [ 'react-server-dom-webpack/client.edge', 'react-server-dom-webpack/server.edge', 'react-server-dom-webpack/server.node', +] + +function makeAppAliases(reactChannel = '') { + const alias = { + react$: `next/dist/compiled/react${reactChannel}`, + 'react/shared-subset$': `next/dist/compiled/react${reactChannel}/react.shared-subset`, + 'react-dom/server-rendering-stub$': `next/dist/compiled/react-dom${reactChannel}/server-rendering-stub`, + 'react-dom$': `next/dist/compiled/react-dom${reactChannel}/server-rendering-stub`, + 'react/jsx-runtime$': `next/dist/compiled/react${reactChannel}/jsx-runtime`, + 'react/jsx-dev-runtime$': `next/dist/compiled/react${reactChannel}/jsx-dev-runtime`, + 'react-dom/client$': `next/dist/compiled/react-dom${reactChannel}/client`, + 'react-dom/server$': `next/dist/compiled/react-dom${reactChannel}/server`, + 'react-dom/server.edge$': `next/dist/compiled/react-dom${reactChannel}/server.edge`, + 'react-dom/server.browser$': `next/dist/compiled/react-dom${reactChannel}/server.browser`, + 'react-server-dom-webpack/client$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/client`, + 'react-server-dom-webpack/client.edge$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/client.edge`, + 'react-server-dom-webpack/server.edge$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/server.edge`, + 'react-server-dom-webpack/server.node$': `next/dist/compiled/react-server-dom-webpack${reactChannel}/server.node`, + } + + return alias +} + +const appAliases = makeAppAliases() +const appExperimentalAliases = makeAppAliases('-experimental') + +const sharedExternals = [ 'styled-jsx', 'styled-jsx/style', '@opentelemetry/api', @@ -44,13 +71,36 @@ const externalsRegexMap = { '(.*)trace/tracer$': 'next/dist/server/lib/trace/tracer', } -module.exports = ({ dev, turbo }) => { +const bundleTypes = { + app: { + 'app-page': path.join( + __dirname, + 'dist/esm/server/future/route-modules/app-page/module.js' + ), + 'app-route': path.join( + __dirname, + 'dist/esm/server/future/route-modules/app-route/module.js' + ), + }, + pages: { + pages: path.join( + __dirname, + 'dist/esm/server/future/route-modules/pages/module.js' + ), + 'pages-api': path.join( + __dirname, + 'dist/esm/server/future/route-modules/pages-api/module.js' + ), + }, + server: { + server: path.join(__dirname, 'dist/esm/server/next-server.js'), + }, +} + +module.exports = ({ dev, turbo, bundleType, experimental }) => { const externalHandler = ({ context, request, getResolve }, callback) => { ;(async () => { - if ( - ((dev || turbo) && request.endsWith('.shared-runtime')) || - request.endsWith('.external') - ) { + if (request.endsWith('.external')) { const resolve = getResolve() const resolved = await resolve(context, request) const relative = path.relative( @@ -72,32 +122,14 @@ module.exports = ({ dev, turbo }) => { /** @type {webpack.Configuration} */ return { - entry: { - server: path.join(__dirname, 'dist/esm/server/next-server.js'), - 'app-page': path.join( - __dirname, - 'dist/esm/server/future/route-modules/app-page/module.js' - ), - 'app-route': path.join( - __dirname, - 'dist/esm/server/future/route-modules/app-route/module.js' - ), - pages: path.join( - __dirname, - 'dist/esm/server/future/route-modules/pages/module.js' - ), - 'pages-api': path.join( - __dirname, - 'dist/esm/server/future/route-modules/pages-api/module.js' - ), - }, + entry: bundleTypes[bundleType], target: 'node', mode: 'production', output: { path: path.join(__dirname, 'dist/compiled/next-server'), - filename: `[name]${turbo ? '-turbo' : ''}.runtime.${ - dev ? 'dev' : 'prod' - }.js`, + filename: `[name]${turbo ? '-turbo' : ''}${ + experimental ? '-experimental' : '' + }.runtime.${dev ? 'dev' : 'prod'}.js`, libraryTarget: 'commonjs2', }, optimization: { @@ -123,6 +155,7 @@ module.exports = ({ dev, turbo }) => { }, plugins: [ new webpack.DefinePlugin({ + 'typeof window': JSON.stringify('undefined'), 'process.env.NEXT_MINIMAL': JSON.stringify('true'), 'this.serverOptions.experimentalTestProxy': JSON.stringify(false), 'this.minimalMode': JSON.stringify(true), @@ -135,12 +168,81 @@ module.exports = ({ dev, turbo }) => { }), !!process.env.ANALYZE && new BundleAnalyzerPlugin({ - analyzerPort: 8888 + (dev ? 0 : 1) + (turbo ? 1 : 0), + analyzerPort: calculateUniquePort( + dev, + turbo, + experimental, + bundleType + ), + openAnalyzer: false, }), ].filter(Boolean), stats: { optimizationBailout: true, }, - externals: [...minimalExternals, externalsMap, externalHandler], + resolve: { + alias: + bundleType === 'app' + ? experimental + ? appExperimentalAliases + : appAliases + : {}, + }, + module: { + rules: [ + { + include: /vendored\/rsc\/entrypoints/, + resolve: { + conditionNames: ['react-server', '...'], + alias: { + react$: `next/dist/compiled/react${ + experimental ? '-experimental' : '' + }/react.shared-subset`, + }, + }, + layer: 'react-server', + }, + { + issuerLayer: 'react-server', + resolve: { + conditionNames: ['react-server', '...'], + alias: { + react$: `next/dist/compiled/react${ + experimental ? '-experimental' : '' + }/react.shared-subset`, + }, + }, + }, + ], + }, + externals: [ + ...sharedExternals, + ...(bundleType === 'pages' ? pagesExternals : []), + externalsMap, + externalHandler, + ], + experiments: { + layers: true, + }, } } + +function calculateUniquePort(dev, turbo, experimental, bundleType) { + const devOffset = dev ? 1000 : 0 + const turboOffset = turbo ? 200 : 0 + const experimentalOffset = experimental ? 40 : 0 + let bundleTypeOffset + + switch (bundleType) { + case 'app': + bundleTypeOffset = 1 + break + case 'pages': + bundleTypeOffset = 2 + break + default: + bundleTypeOffset = 3 + } + + return 8888 + devOffset + turboOffset + experimentalOffset + bundleTypeOffset +} diff --git a/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js b/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js index 1220ff4c11..2e5f208139 100644 --- a/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js +++ b/test/e2e/app-dir/rsc-basic/app/app-react/client-react.js @@ -2,7 +2,7 @@ import React from 'react' import ReactDOM from 'react-dom' -import ReactDOMServer from 'react-dom/server' +import ReactDOMServer from 'react-dom/server.edge' export default function ClientReact() { return ( diff --git a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts index 4a0ebc8763..a79048bbc9 100644 --- a/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic/rsc-basic.test.ts @@ -456,7 +456,7 @@ createNextDescribe( expect(await res.text()).toBe('Hello from import-test.js') }) - it('should use bundled react for pages with app', async () => { + it('should not use bundled react for pages with app', async () => { const ssrPaths = ['/pages-react', '/edge-pages-react'] const promises = ssrPaths.map(async (pathname) => { const resPages$ = await next.render$(pathname) @@ -467,7 +467,7 @@ createNextDescribe( ] ssrPagesReactVersions.forEach((version) => { - expect(version).toMatch('-canary-') + expect(version).not.toMatch('-canary-') }) }) await Promise.all(promises) @@ -502,10 +502,10 @@ createNextDescribe( `) browserPagesReactVersions.forEach((version) => - expect(version).toMatch('-canary-') + expect(version).not.toMatch('-canary-') ) browserEdgePagesReactVersions.forEach((version) => - expect(version).toMatch('-canary-') + expect(version).not.toMatch('-canary-') ) }) diff --git a/test/production/custom-server/custom-server.test.ts b/test/production/custom-server/custom-server.test.ts index cb643e015b..d3514ee0c1 100644 --- a/test/production/custom-server/custom-server.test.ts +++ b/test/production/custom-server/custom-server.test.ts @@ -21,10 +21,10 @@ createNextDescribe( expect($('body').text()).toMatch(/app: .+-canary/) }) - it('should render pages with react canary', async () => { + it('should not render pages with react canary', async () => { const $ = await next.render$(`/2`) expect($('body').text()).toMatch(/pages:/) - expect($('body').text()).toMatch(/canary/) + expect($('body').text()).not.toMatch(/canary/) }) }) }