Skip to content

Commit bd70553

Browse files
feedthejimijjk
andauthored
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 <[email protected]>
1 parent e191ae3 commit bd70553

File tree

73 files changed

+898
-467
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+898
-467
lines changed

packages/next-swc/crates/next-core/src/next_import_map.rs

Lines changed: 192 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,7 @@ pub async fn get_next_server_import_map(
246246
| ServerContextType::AppRSC { .. }
247247
| ServerContextType::AppRoute { .. } => {
248248
match mode {
249-
NextMode::Build => {
250-
import_map.insert_wildcard_alias("next/dist/server/", external);
251-
import_map.insert_wildcard_alias("next/dist/shared/", external);
252-
}
249+
NextMode::Build => {}
253250
NextMode::DevServer => {
254251
// The sandbox can't be bundled and needs to be external
255252
import_map.insert_exact_alias("next/dist/server/web/sandbox", external);
@@ -431,61 +428,138 @@ async fn insert_next_server_special_aliases(
431428
);
432429
}
433430
(_, ServerContextType::PagesData { .. }) => {}
434-
// In development, we *always* use the bundled version of React, even in
435-
// SSR, since we're bundling Next.js alongside it.
436-
(NextMode::DevServer, ServerContextType::AppSSR { app_dir }) => {
431+
// the logic closely follows the one in createRSCAliases in webpack-config.ts
432+
(
433+
NextMode::DevServer | NextMode::Build | NextMode::Development,
434+
ServerContextType::AppSSR { app_dir },
435+
) => {
437436
import_map.insert_exact_alias(
438437
"@opentelemetry/api",
439438
// TODO(WEB-625) this actually need to prefer the local version of
440439
// @opentelemetry/api
441440
request_to_import_mapping(app_dir, "next/dist/compiled/@opentelemetry/api"),
442441
);
443442
import_map.insert_exact_alias(
444-
"react",
445-
passthrough_external_if_node(app_dir, "next/dist/compiled/react"),
443+
"styled-jsx",
444+
passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx"),
446445
);
447446
import_map.insert_wildcard_alias(
448-
"react/",
449-
passthrough_external_if_node(app_dir, "next/dist/compiled/react/*"),
447+
"styled-jsx/",
448+
passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx/*"),
449+
);
450+
import_map.insert_exact_alias(
451+
"react/jsx-runtime",
452+
request_to_import_mapping(
453+
app_dir,
454+
match runtime {
455+
NextRuntime::Edge => "next/dist/compiled/react/jsx-runtime",
456+
NextRuntime::NodeJs => {
457+
"next/dist/server/future/route-modules/app-page/vendored/shared/\
458+
react-jsx-runtime"
459+
}
460+
},
461+
),
462+
);
463+
import_map.insert_exact_alias(
464+
"react/jsx-dev-runtime",
465+
request_to_import_mapping(
466+
app_dir,
467+
match runtime {
468+
NextRuntime::Edge => "next/dist/compiled/react/jsx-dev-runtime",
469+
NextRuntime::NodeJs => {
470+
"next/dist/server/future/route-modules/app-page/vendored/shared/\
471+
react-jsx-dev-runtime"
472+
}
473+
},
474+
),
475+
);
476+
import_map.insert_exact_alias(
477+
"react",
478+
request_to_import_mapping(
479+
app_dir,
480+
match runtime {
481+
NextRuntime::Edge => "next/dist/compiled/react",
482+
NextRuntime::NodeJs => {
483+
"next/dist/server/future/route-modules/app-page/vendored/ssr/react"
484+
}
485+
},
486+
),
450487
);
451488
import_map.insert_exact_alias(
452489
"react-dom",
453-
passthrough_external_if_node(
490+
request_to_import_mapping(
454491
app_dir,
455-
"next/dist/compiled/react-dom/server-rendering-stub.js",
492+
match runtime {
493+
NextRuntime::Edge => "next/dist/compiled/react-dom",
494+
NextRuntime::NodeJs => {
495+
"next/dist/server/future/route-modules/app-page/vendored/ssr/react-dom"
496+
}
497+
},
456498
),
457499
);
458-
import_map.insert_wildcard_alias(
459-
"react-dom/",
460-
passthrough_external_if_node(app_dir, "next/dist/compiled/react-dom/*"),
500+
import_map.insert_exact_alias(
501+
"react-server-dom-webpack/client.edge",
502+
request_to_import_mapping(
503+
app_dir,
504+
match runtime {
505+
NextRuntime::Edge => {
506+
"next/dist/compiled/react-server-dom-webpack/client.edge"
507+
}
508+
NextRuntime::NodeJs => {
509+
"next/dist/server/future/route-modules/app-page/vendored/ssr/\
510+
react-server-dom-webpack-client-edge"
511+
}
512+
},
513+
),
461514
);
515+
// some code also imports react-server-dom-webpack/client on the server
516+
// it should never run so it's fine to just point it to the same place as
517+
// react-server-dom-webpack/client.edge
462518
import_map.insert_exact_alias(
463-
"styled-jsx",
464-
passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx"),
519+
"react-server-dom-webpack/client",
520+
request_to_import_mapping(
521+
app_dir,
522+
match runtime {
523+
NextRuntime::Edge => {
524+
"next/dist/compiled/react-server-dom-webpack/client.edge"
525+
}
526+
NextRuntime::NodeJs => {
527+
"next/dist/server/future/route-modules/app-page/vendored/ssr/\
528+
react-server-dom-webpack-client-edge"
529+
}
530+
},
531+
),
465532
);
466-
import_map.insert_wildcard_alias(
467-
"styled-jsx/",
468-
passthrough_external_if_node(app_dir, "next/dist/compiled/styled-jsx/*"),
533+
// not essential but we're providing this alias for people who might use it.
534+
// A note here is that this will point toward the ReactDOMServer on the SSR
535+
// layer TODO: add the rests
536+
import_map.insert_exact_alias(
537+
"react-dom/server",
538+
request_to_import_mapping(
539+
app_dir,
540+
match runtime {
541+
NextRuntime::Edge => "next/dist/compiled/react-dom/server.edge",
542+
NextRuntime::NodeJs => {
543+
"next/dist/server/future/route-modules/app-page/vendored/ssr/\
544+
react-dom-server-edge"
545+
}
546+
},
547+
),
469548
);
470-
import_map.insert_wildcard_alias(
471-
"react-server-dom-webpack/",
472-
passthrough_external_if_node(
549+
import_map.insert_exact_alias(
550+
"react-dom/server.edge",
551+
request_to_import_mapping(
473552
app_dir,
474-
"next/dist/compiled/react-server-dom-webpack/*",
553+
match runtime {
554+
NextRuntime::Edge => "next/dist/compiled/react-dom/server.edge",
555+
NextRuntime::NodeJs => {
556+
"next/dist/server/future/route-modules/app-page/vendored/ssr/\
557+
react-dom-server-edge"
558+
}
559+
},
475560
),
476561
);
477562
}
478-
479-
// NOTE(alexkirsz) This logic maps loosely to
480-
// `next.js/packages/next/src/build/webpack-config.ts`, where:
481-
//
482-
// ## RSC
483-
//
484-
// * always bundles
485-
// * maps react -> react/shared-subset (through the "react-server" exports condition)
486-
// * maps react-dom -> react-dom/server-rendering-stub
487-
// * passes through (react|react-dom|react-server-dom-webpack)/(.*) to
488-
// next/dist/compiled/$1/$2
489563
(
490564
NextMode::Build | NextMode::Development | NextMode::DevServer,
491565
ServerContextType::AppRSC { app_dir, .. } | ServerContextType::AppRoute { app_dir },
@@ -496,72 +570,103 @@ async fn insert_next_server_special_aliases(
496570
// @opentelemetry/api
497571
request_to_import_mapping(app_dir, "next/dist/compiled/@opentelemetry/api"),
498572
);
499-
if matches!(ty, ServerContextType::AppRSC { .. }) {
500-
import_map.insert_exact_alias(
501-
"react",
502-
request_to_import_mapping(
503-
app_dir,
504-
"next/dist/compiled/react/react.shared-subset",
505-
),
506-
);
507-
} else {
508-
import_map.insert_exact_alias(
509-
"react",
510-
request_to_import_mapping(app_dir, "next/dist/compiled/react"),
511-
);
512-
}
573+
513574
import_map.insert_exact_alias(
514-
"react-dom",
575+
"react/jsx-runtime",
515576
request_to_import_mapping(
516577
app_dir,
517-
"next/dist/compiled/react-dom/server-rendering-stub",
578+
match runtime {
579+
NextRuntime::Edge => "next/dist/compiled/react/jsx-runtime",
580+
NextRuntime::NodeJs => {
581+
"next/dist/server/future/route-modules/app-page/vendored/shared/\
582+
react-jsx-runtime"
583+
}
584+
},
518585
),
519586
);
520-
for (wildcard_alias, request) in [
521-
("react/", "next/dist/compiled/react/*"),
522-
("react-dom/", "next/dist/compiled/react-dom/*"),
523-
(
524-
"react-server-dom-webpack/",
525-
"next/dist/compiled/react-server-dom-webpack/*",
587+
import_map.insert_exact_alias(
588+
"react/jsx-dev-runtime",
589+
request_to_import_mapping(
590+
app_dir,
591+
match runtime {
592+
NextRuntime::Edge => "next/dist/compiled/react/jsx-dev-runtime",
593+
NextRuntime::NodeJs => {
594+
"next/dist/server/future/route-modules/app-page/vendored/shared/\
595+
react-jsx-dev-runtime"
596+
}
597+
},
526598
),
527-
] {
528-
import_map.insert_wildcard_alias(
529-
wildcard_alias,
530-
request_to_import_mapping(app_dir, request),
531-
);
532-
}
533-
}
534-
// ## SSR
535-
//
536-
// * always uses externals, to ensure we're using the same React instance as the Next.js
537-
// runtime
538-
// * maps react-dom -> react-dom/server-rendering-stub
539-
// * passes through react and (react|react-dom|react-server-dom-webpack)/(.*) to
540-
// next/dist/compiled/react and next/dist/compiled/$1/$2 resp.
541-
(NextMode::Build | NextMode::Development, ServerContextType::AppSSR { app_dir }) => {
599+
);
542600
import_map.insert_exact_alias(
543601
"react",
544-
external_if_node(app_dir, "next/dist/compiled/react"),
602+
request_to_import_mapping(
603+
app_dir,
604+
match runtime {
605+
NextRuntime::Edge => "next/dist/compiled/react",
606+
NextRuntime::NodeJs => {
607+
"next/dist/server/future/route-modules/app-page/vendored/rsc/react"
608+
}
609+
},
610+
),
545611
);
546612
import_map.insert_exact_alias(
547613
"react-dom",
548-
external_if_node(
614+
request_to_import_mapping(
549615
app_dir,
550-
"next/dist/compiled/react-dom/server-rendering-stub",
616+
match runtime {
617+
NextRuntime::Edge => "next/dist/compiled/react-dom",
618+
NextRuntime::NodeJs => {
619+
"next/dist/server/future/route-modules/app-page/vendored/rsc/react-dom"
620+
}
621+
},
551622
),
552623
);
553-
554-
for (wildcard_alias, request) in [
555-
("react/", "next/dist/compiled/react/*"),
556-
("react-dom/", "next/dist/compiled/react-dom/*"),
557-
(
558-
"react-server-dom-webpack/",
559-
"next/dist/compiled/react-server-dom-webpack/*",
624+
import_map.insert_exact_alias(
625+
"react-server-dom-webpack/server.edge",
626+
request_to_import_mapping(
627+
app_dir,
628+
match runtime {
629+
NextRuntime::Edge => {
630+
"next/dist/compiled/react-server-dom-webpack/server.edge"
631+
}
632+
NextRuntime::NodeJs => {
633+
"next/dist/server/future/route-modules/app-page/vendored/rsc/\
634+
react-server-dom-webpack-server-edge"
635+
}
636+
},
560637
),
561-
] {
562-
let import_mapping = external_if_node(app_dir, request);
563-
import_map.insert_wildcard_alias(wildcard_alias, import_mapping);
564-
}
638+
);
639+
import_map.insert_exact_alias(
640+
"react-server-dom-webpack/server.node",
641+
request_to_import_mapping(
642+
app_dir,
643+
match runtime {
644+
NextRuntime::Edge => {
645+
"next/dist/compiled/react-server-dom-webpack/server.node"
646+
}
647+
NextRuntime::NodeJs => {
648+
"next/dist/server/future/route-modules/app-page/vendored/rsc/\
649+
react-server-dom-webpack-server-node"
650+
}
651+
},
652+
),
653+
);
654+
// not essential but we're providing this alias for people who might use it.
655+
// A note here is that this will point toward the ReactDOMServer on the SSR
656+
// layer TODO: add the rests
657+
import_map.insert_exact_alias(
658+
"react-dom/server.edge",
659+
request_to_import_mapping(
660+
app_dir,
661+
match runtime {
662+
NextRuntime::Edge => "next/dist/compiled/react-dom/server.edge",
663+
NextRuntime::NodeJs => {
664+
"next/dist/server/future/route-modules/app-page/vendored/ssr/\
665+
react-dom-server-edge"
666+
}
667+
},
668+
),
669+
);
565670
}
566671
(_, ServerContextType::Middleware) => {}
567672
}

packages/next-swc/crates/next-core/src/next_server/context.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use crate::{
4848
next_shared::{
4949
resolve::{
5050
ModuleFeatureReportResolvePlugin, NextExternalResolvePlugin,
51-
UnsupportedModulesResolvePlugin,
51+
NextNodeSharedRuntimeResolvePlugin, UnsupportedModulesResolvePlugin,
5252
},
5353
transforms::{
5454
emotion::get_emotion_transform_plugin, get_relay_transform_plugin,
@@ -125,6 +125,8 @@ pub async fn get_server_resolve_options_context(
125125
);
126126

127127
let next_external_plugin = NextExternalResolvePlugin::new(project_path);
128+
let next_node_shared_runtime_plugin =
129+
NextNodeSharedRuntimeResolvePlugin::new(project_path, Value::new(ty));
128130

129131
let plugins = match ty {
130132
ServerContextType::Pages { .. } | ServerContextType::PagesData { .. } => {
@@ -133,6 +135,7 @@ pub async fn get_server_resolve_options_context(
133135
Vc::upcast(external_cjs_modules_plugin),
134136
Vc::upcast(unsupported_modules_resolve_plugin),
135137
Vc::upcast(next_external_plugin),
138+
Vc::upcast(next_node_shared_runtime_plugin),
136139
]
137140
}
138141
ServerContextType::AppSSR { .. }
@@ -144,6 +147,7 @@ pub async fn get_server_resolve_options_context(
144147
Vc::upcast(server_component_externals_plugin),
145148
Vc::upcast(unsupported_modules_resolve_plugin),
146149
Vc::upcast(next_external_plugin),
150+
Vc::upcast(next_node_shared_runtime_plugin),
147151
]
148152
}
149153
};
@@ -175,7 +179,8 @@ fn defines(mode: NextMode) -> CompileTimeDefines {
175179
process.turbopack = true,
176180
process.env.NODE_ENV = mode.node_env(),
177181
process.env.__NEXT_CLIENT_ROUTER_FILTER_ENABLED = false,
178-
process.env.NEXT_RUNTIME = "nodejs"
182+
process.env.NEXT_RUNTIME = "nodejs",
183+
process.env.__NEXT_EXPERIMENTAL_REACT = false,
179184
)
180185
// TODO(WEB-937) there are more defines needed, see
181186
// packages/next/src/build/webpack-config.ts

0 commit comments

Comments
 (0)