Split into dev server and next.rs api mode (#53220)

### What?

* split into two dev modes
* split app-index into webpack and turbopack
* add two different entrypoints

### Why?

### How?
This commit is contained in:
Tobias Koppers 2023-07-26 21:21:49 +02:00 committed by GitHub
parent a1adaf89ef
commit 39fd9177ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 180 additions and 108 deletions

View file

@ -3,4 +3,10 @@
*/ */
import '../shims' import '../shims'
import 'next/dist/client/app-next' import { appBootstrap } from 'next/dist/client/app-bootstrap'
appBootstrap(() => {
require('./app-turbopack')
const { hydrate } = require('./app-index')
hydrate()
})

View file

@ -0,0 +1,9 @@
// @ts-expect-error
process.env.__NEXT_NEW_LINK_BEHAVIOR = true
// eslint-disable-next-line no-undef
self.__next_require__ = __turbopack_require__
// @ts-ignore
// eslint-disable-next-line no-undef
;(self as any).__next_chunk_load__ = __turbopack_load__

View file

@ -133,7 +133,7 @@ async fn next_client_transition(
next_config: Vc<NextConfig>, next_config: Vc<NextConfig>,
) -> Result<Vc<Box<dyn Transition>>> { ) -> Result<Vc<Box<dyn Transition>>> {
let ty: Value<ClientContextType> = Value::new(ClientContextType::App { app_dir }); let ty: Value<ClientContextType> = Value::new(ClientContextType::App { app_dir });
let mode = NextMode::Development; let mode = NextMode::DevServer;
let client_module_options_context = get_client_module_options_context( let client_module_options_context = get_client_module_options_context(
project_path, project_path,
execution_context, execution_context,
@ -170,7 +170,7 @@ fn next_ssr_client_module_transition(
server_addr: Vc<ServerAddr>, server_addr: Vc<ServerAddr>,
) -> Vc<Box<dyn Transition>> { ) -> Vc<Box<dyn Transition>> {
let ty = Value::new(ServerContextType::AppSSR { app_dir }); let ty = Value::new(ServerContextType::AppSSR { app_dir });
let mode = NextMode::Development; let mode = NextMode::DevServer;
Vc::upcast( Vc::upcast(
NextSSRClientModuleTransition { NextSSRClientModuleTransition {
ssr_module_options_context: get_server_module_options_context( ssr_module_options_context: get_server_module_options_context(
@ -202,7 +202,7 @@ fn next_edge_ssr_client_module_transition(
server_addr: Vc<ServerAddr>, server_addr: Vc<ServerAddr>,
) -> Vc<Box<dyn Transition>> { ) -> Vc<Box<dyn Transition>> {
let ty = Value::new(ServerContextType::AppSSR { app_dir }); let ty = Value::new(ServerContextType::AppSSR { app_dir });
let mode = NextMode::Development; let mode = NextMode::DevServer;
Vc::upcast( Vc::upcast(
NextSSRClientModuleTransition { NextSSRClientModuleTransition {
ssr_module_options_context: get_server_module_options_context( ssr_module_options_context: get_server_module_options_context(
@ -306,7 +306,7 @@ fn next_edge_route_transition(
output_path: Vc<FileSystemPath>, output_path: Vc<FileSystemPath>,
execution_context: Vc<ExecutionContext>, execution_context: Vc<ExecutionContext>,
) -> Vc<Box<dyn Transition>> { ) -> Vc<Box<dyn Transition>> {
let mode = NextMode::Development; let mode = NextMode::DevServer;
let server_ty = Value::new(ServerContextType::AppRoute { app_dir }); let server_ty = Value::new(ServerContextType::AppRoute { app_dir });
let edge_compile_time_info = get_edge_compile_time_info(project_path, server_addr); let edge_compile_time_info = get_edge_compile_time_info(project_path, server_addr);
@ -570,7 +570,7 @@ pub async fn create_app_source(
client_chunking_context, client_chunking_context,
client_compile_time_info, client_compile_time_info,
true, true,
NextMode::Development, NextMode::DevServer,
next_config, next_config,
server_addr, server_addr,
output_path, output_path,
@ -584,7 +584,7 @@ pub async fn create_app_source(
client_chunking_context, client_chunking_context,
client_compile_time_info, client_compile_time_info,
false, false,
NextMode::Development, NextMode::DevServer,
next_config, next_config,
server_addr, server_addr,
output_path, output_path,
@ -905,7 +905,7 @@ impl AppRenderer {
loader_tree, loader_tree,
context, context,
ServerComponentTransition::TransitionName(rsc_transition.to_string()), ServerComponentTransition::TransitionName(rsc_transition.to_string()),
NextMode::Development, NextMode::DevServer,
) )
.await?; .await?;

View file

@ -37,7 +37,7 @@ pub async fn get_fallback_page(
next_config: Vc<NextConfig>, next_config: Vc<NextConfig>,
) -> Result<Vc<DevHtmlAsset>> { ) -> Result<Vc<DevHtmlAsset>> {
let ty = Value::new(ClientContextType::Fallback); let ty = Value::new(ClientContextType::Fallback);
let mode = NextMode::Development; let mode = NextMode::DevServer;
let resolve_options_context = let resolve_options_context =
get_client_resolve_options_context(project_path, ty, mode, next_config, execution_context); get_client_resolve_options_context(project_path, ty, mode, next_config, execution_context);
let module_options_context = get_client_module_options_context( let module_options_context = get_client_module_options_context(

View file

@ -107,7 +107,7 @@ impl LoaderTreeBuilder {
let identifier = magic_identifier::mangle(&format!("{name} #{i}")); let identifier = magic_identifier::mangle(&format!("{name} #{i}"));
match self.mode { match self.mode {
NextMode::Development => { NextMode::Development | NextMode::DevServer => {
let chunks_identifier = let chunks_identifier =
magic_identifier::mangle(&format!("chunks of {name} #{i}")); magic_identifier::mangle(&format!("chunks of {name} #{i}"));
writeln!( writeln!(

View file

@ -18,7 +18,9 @@ use turbo_tasks::{debug::ValueDebugFormat, trace::TraceRawVcs, TaskInput};
ValueDebugFormat, ValueDebugFormat,
)] )]
pub enum NextMode { pub enum NextMode {
/// `next dev` /// `next dev --turbo`
DevServer,
/// `next dev --experimental-turbo`
Development, Development,
/// `next build` /// `next build`
Build, Build,
@ -28,7 +30,7 @@ impl NextMode {
/// Returns the NODE_ENV value for the current mode. /// Returns the NODE_ENV value for the current mode.
pub fn node_env(&self) -> &'static str { pub fn node_env(&self) -> &'static str {
match self { match self {
NextMode::Development => "development", NextMode::Development | NextMode::DevServer => "development",
NextMode::Build => "production", NextMode::Build => "production",
} }
} }
@ -36,7 +38,7 @@ impl NextMode {
/// Returns true if the development React runtime should be used. /// Returns true if the development React runtime should be used.
pub fn is_react_development(&self) -> bool { pub fn is_react_development(&self) -> bool {
match self { match self {
NextMode::Development => true, NextMode::Development | NextMode::DevServer => true,
NextMode::Build => false, NextMode::Build => false,
} }
} }

View file

@ -291,7 +291,10 @@ pub fn get_client_chunking_context(
); );
let builder = match mode { let builder = match mode {
NextMode::Development => builder.hot_module_replacement(), NextMode::DevServer => builder.hot_module_replacement(),
NextMode::Development => builder
.hot_module_replacement()
.chunk_base_path(Vc::cell(Some("_next/".to_string()))),
NextMode::Build => builder.chunk_base_path(Vc::cell(Some("_next/".to_string()))), NextMode::Build => builder.chunk_base_path(Vc::cell(Some("_next/".to_string()))),
}; };
@ -328,6 +331,27 @@ pub async fn get_client_runtime_entries(
} }
match mode { match mode {
NextMode::DevServer => {
let resolve_options_context = get_client_resolve_options_context(
project_root,
ty,
mode,
next_config,
execution_context,
);
let enable_react_refresh =
assert_can_resolve_react_refresh(project_root, resolve_options_context)
.await?
.as_request();
// It's important that React Refresh come before the regular bootstrap file,
// because the bootstrap contains JSX which requires Refresh's global
// functions to be available.
if let Some(request) = enable_react_refresh {
runtime_entries
.push(RuntimeEntry::Request(request, project_root.join("_".to_string())).cell())
};
}
NextMode::Development => { NextMode::Development => {
let resolve_options_context = get_client_resolve_options_context( let resolve_options_context = get_client_resolve_options_context(
project_root, project_root,
@ -348,6 +372,18 @@ pub async fn get_client_runtime_entries(
runtime_entries runtime_entries
.push(RuntimeEntry::Request(request, project_root.join("_".to_string())).cell()) .push(RuntimeEntry::Request(request, project_root.join("_".to_string())).cell())
}; };
if matches!(*ty, ClientContextType::App { .. },) {
runtime_entries.push(
RuntimeEntry::Request(
Request::parse(Value::new(Pattern::Constant(
"next/dist/client/app-next-dev-turbopack.js".to_string(),
))),
project_root.join("_".to_string()),
)
.cell(),
);
}
} }
NextMode::Build => match *ty { NextMode::Build => match *ty {
ClientContextType::App { .. } => { ClientContextType::App { .. } => {

View file

@ -391,7 +391,7 @@ pub async fn insert_next_server_special_aliases(
// In development, we *always* use the bundled version of React, even in // In development, we *always* use the bundled version of React, even in
// SSR, since we're bundling Next.js alongside it. // SSR, since we're bundling Next.js alongside it.
( (
NextMode::Development, NextMode::DevServer,
ServerContextType::AppSSR { app_dir } ServerContextType::AppSSR { app_dir }
| ServerContextType::AppRSC { app_dir, .. } | ServerContextType::AppRSC { app_dir, .. }
| ServerContextType::AppRoute { app_dir }, | ServerContextType::AppRoute { app_dir },
@ -429,7 +429,7 @@ pub async fn insert_next_server_special_aliases(
// NOTE(alexkirsz) This logic maps loosely to // NOTE(alexkirsz) This logic maps loosely to
// `next.js/packages/next/src/build/webpack-config.ts`, where: // `next.js/packages/next/src/build/webpack-config.ts`, where:
// //
// ## RSC (Build) // ## RSC
// //
// * always bundles // * always bundles
// * maps react -> react/shared-subset (through the "react-server" exports condition) // * maps react -> react/shared-subset (through the "react-server" exports condition)
@ -437,7 +437,7 @@ pub async fn insert_next_server_special_aliases(
// * passes through (react|react-dom|react-server-dom-webpack)/(.*) to // * passes through (react|react-dom|react-server-dom-webpack)/(.*) to
// next/dist/compiled/$1/$2 // next/dist/compiled/$1/$2
( (
NextMode::Build, NextMode::Build | NextMode::Development,
ServerContextType::AppRSC { app_dir, .. } | ServerContextType::AppRoute { app_dir }, ServerContextType::AppRSC { app_dir, .. } | ServerContextType::AppRoute { app_dir },
) => { ) => {
import_map.insert_exact_alias( import_map.insert_exact_alias(
@ -471,14 +471,14 @@ pub async fn insert_next_server_special_aliases(
); );
} }
} }
// ## SSR (Build) // ## SSR
// //
// * always uses externals, to ensure we're using the same React instance as the Next.js // * always uses externals, to ensure we're using the same React instance as the Next.js
// runtime // runtime
// * maps react-dom -> react-dom/server-rendering-stub // * maps react-dom -> react-dom/server-rendering-stub
// * passes through react and (react|react-dom|react-server-dom-webpack)/(.*) to // * passes through react and (react|react-dom|react-server-dom-webpack)/(.*) to
// next/dist/compiled/react and next/dist/compiled/$1/$2 resp. // next/dist/compiled/react and next/dist/compiled/$1/$2 resp.
(NextMode::Build, ServerContextType::AppSSR { .. }) => { (NextMode::Build | NextMode::Development, ServerContextType::AppSSR { .. }) => {
import_map.insert_exact_alias( import_map.insert_exact_alias(
"react", "react",
external_request_to_import_mapping("next/dist/compiled/react"), external_request_to_import_mapping("next/dist/compiled/react"),

View file

@ -643,6 +643,7 @@ pub fn get_server_runtime_entries(
match mode { match mode {
NextMode::Development => {} NextMode::Development => {}
NextMode::DevServer => {}
NextMode::Build => { NextMode::Build => {
if let ServerContextType::AppRSC { .. } = ty.into_value() { if let ServerContextType::AppRSC { .. } = ty.into_value() {
runtime_entries.push( runtime_entries.push(

View file

@ -60,14 +60,14 @@ impl CustomTransformer for NextJsDynamic {
let p = std::mem::replace(program, Program::Module(Module::dummy())); let p = std::mem::replace(program, Program::Module(Module::dummy()));
*program = p.fold_with(&mut next_dynamic( *program = p.fold_with(&mut next_dynamic(
match self.mode { match self.mode {
NextMode::Development => true, NextMode::Development | NextMode::DevServer => true,
NextMode::Build => false, NextMode::Build => false,
}, },
self.is_server, self.is_server,
self.is_server_components, self.is_server_components,
NextDynamicMode::Turbopack { NextDynamicMode::Turbopack {
dynamic_transition_name: match self.mode { dynamic_transition_name: match self.mode {
NextMode::Development => "next-client-chunks".to_string(), NextMode::Development | NextMode::DevServer => "next-client-chunks".to_string(),
NextMode::Build => "next-dynamic".to_string(), NextMode::Build => "next-dynamic".to_string(),
}, },
}, },

View file

@ -96,7 +96,7 @@ pub async fn create_page_source(
project_root.join("pages".to_string()) project_root.join("pages".to_string())
}; };
let mode = NextMode::Development; let mode = NextMode::DevServer;
let client_ty = Value::new(ClientContextType::Pages { pages_dir }); let client_ty = Value::new(ClientContextType::Pages { pages_dir });
let server_ty = Value::new(ServerContextType::Pages { pages_dir }); let server_ty = Value::new(ServerContextType::Pages { pages_dir });
let server_data_ty = Value::new(ServerContextType::PagesData { pages_dir }); let server_data_ty = Value::new(ServerContextType::PagesData { pages_dir });
@ -344,7 +344,7 @@ async fn create_page_source_for_file(
node_root: Vc<FileSystemPath>, node_root: Vc<FileSystemPath>,
render_data: Vc<JsonValue>, render_data: Vc<JsonValue>,
) -> Result<Vc<Box<dyn ContentSource>>> { ) -> Result<Vc<Box<dyn ContentSource>>> {
let mode = NextMode::Development; let mode = NextMode::DevServer;
let server_chunking_context = Vc::upcast( let server_chunking_context = Vc::upcast(
DevChunkingContext::builder( DevChunkingContext::builder(

View file

@ -231,7 +231,7 @@ fn edge_transition_map(
next_config: Vc<NextConfig>, next_config: Vc<NextConfig>,
execution_context: Vc<ExecutionContext>, execution_context: Vc<ExecutionContext>,
) -> Vc<TransitionsByName> { ) -> Vc<TransitionsByName> {
let mode = NextMode::Development; let mode = NextMode::DevServer;
let edge_compile_time_info = get_edge_compile_time_info(project_path, server_addr); let edge_compile_time_info = get_edge_compile_time_info(project_path, server_addr);

View file

@ -175,7 +175,7 @@ pub async fn create_web_entry_source(
next_config: Vc<NextConfig>, next_config: Vc<NextConfig>,
) -> Result<Vc<Box<dyn ContentSource>>> { ) -> Result<Vc<Box<dyn ContentSource>>> {
let ty = Value::new(ClientContextType::Other); let ty = Value::new(ClientContextType::Other);
let mode = NextMode::Development; let mode = NextMode::DevServer;
let compile_time_info = get_compile_time_info(browserslist_query); let compile_time_info = get_compile_time_info(browserslist_query);
let context = get_web_client_asset_context( let context = get_web_client_asset_context(
project_root, project_root,

View file

@ -337,7 +337,7 @@ async fn source(
let execution_context = let execution_context =
ExecutionContext::new(project_path, Vc::upcast(build_chunking_context), env); ExecutionContext::new(project_path, Vc::upcast(build_chunking_context), env);
let mode = NextMode::Development; let mode = NextMode::DevServer;
let next_config_execution_context = execution_context.with_layer("next_config".to_string()); let next_config_execution_context = execution_context.with_layer("next_config".to_string());
let next_config = load_next_config(next_config_execution_context); let next_config = load_next_config(next_config_execution_context);
let rewrites = load_rewrites(next_config_execution_context); let rewrites = load_rewrites(next_config_execution_context);

View file

@ -31,88 +31,6 @@ window.addEventListener('error', (ev: WindowEventMap['error']): void => {
/// <reference types="react-dom/experimental" /> /// <reference types="react-dom/experimental" />
// Override chunk URL mapping in the webpack runtime
// https://github.com/webpack/webpack/blob/2738eebc7880835d88c727d364ad37f3ec557593/lib/RuntimeGlobals.js#L204
declare global {
const __webpack_require__: any
}
const addChunkSuffix =
(getOriginalChunk: (chunkId: any) => string) => (chunkId: any) => {
return (
getOriginalChunk(chunkId) +
`${
process.env.NEXT_DEPLOYMENT_ID
? `?dpl=${process.env.NEXT_DEPLOYMENT_ID}`
: ''
}`
)
}
// eslint-disable-next-line no-undef
const getChunkScriptFilename = __webpack_require__.u
const chunkFilenameMap: any = {}
// eslint-disable-next-line no-undef
__webpack_require__.u = addChunkSuffix((chunkId) =>
encodeURI(chunkFilenameMap[chunkId] || getChunkScriptFilename(chunkId))
)
// eslint-disable-next-line no-undef
const getChunkCssFilename = __webpack_require__.k
// eslint-disable-next-line no-undef
__webpack_require__.k = addChunkSuffix(getChunkCssFilename)
// eslint-disable-next-line no-undef
const getMiniCssFilename = __webpack_require__.miniCssF
// eslint-disable-next-line no-undef
__webpack_require__.miniCssF = addChunkSuffix(getMiniCssFilename)
// @ts-ignore
// eslint-disable-next-line no-undef
if (process.turbopack) {
// eslint-disable-next-line no-undef
// @ts-expect-error TODO: fix type
self.__next_require__ = __turbopack_require__
// @ts-ignore
// eslint-disable-next-line no-undef
;(self as any).__next_chunk_load__ = __turbopack_load__
} else {
// Ignore the module ID transform in client.
// eslint-disable-next-line no-undef
// @ts-expect-error TODO: fix type
self.__next_require__ =
process.env.NODE_ENV !== 'production'
? (id: string) => {
const mod = __webpack_require__(id)
if (typeof mod === 'object') {
// Return a proxy to flight client to make sure it's always getting
// the latest module, instead of being cached.
return new Proxy(mod, {
get(_target, prop) {
return __webpack_require__(id)[prop]
},
})
}
return mod
}
: __webpack_require__
// eslint-disable-next-line no-undef
;(self as any).__next_chunk_load__ = (chunk: string) => {
if (!chunk) return Promise.resolve()
const [chunkId, chunkFilePath] = chunk.split(':')
chunkFilenameMap[chunkId] = chunkFilePath
// @ts-ignore
// eslint-disable-next-line no-undef
return __webpack_chunk_load__(chunkId)
}
}
const appElement: HTMLElement | Document | null = document const appElement: HTMLElement | Document | null = document
const getCacheKey = () => { const getCacheKey = () => {

View file

@ -0,0 +1,13 @@
// TODO-APP: hydration warning
import { appBootstrap } from './app-bootstrap'
window.next.version += '-turbo'
appBootstrap(() => {
require('./app-turbopack')
const { hydrate } = require('./app-index')
hydrate()
})
// TODO-APP: build indicator

View file

@ -3,6 +3,7 @@
import { appBootstrap } from './app-bootstrap' import { appBootstrap } from './app-bootstrap'
appBootstrap(() => { appBootstrap(() => {
require('./app-webpack')
const { hydrate } = require('./app-index') const { hydrate } = require('./app-index')
hydrate() hydrate()
}) })

View file

@ -4,6 +4,7 @@ appBootstrap(() => {
// Include app-router and layout-router in the main chunk // Include app-router and layout-router in the main chunk
require('next/dist/client/components/app-router') require('next/dist/client/components/app-router')
require('next/dist/client/components/layout-router') require('next/dist/client/components/layout-router')
require('./app-webpack')
const { hydrate } = require('./app-index') const { hydrate } = require('./app-index')
hydrate() hydrate()
}) })

View file

@ -0,0 +1,13 @@
declare let __turbopack_require__: any
// @ts-expect-error
process.env.__NEXT_NEW_LINK_BEHAVIOR = true
// eslint-disable-next-line no-undef
;(self as any).__next_require__ = __turbopack_require__
// @ts-ignore
// eslint-disable-next-line no-undef
;(self as any).__next_chunk_load__ = __turbopack_load__
export {}

View file

@ -0,0 +1,69 @@
// Override chunk URL mapping in the webpack runtime
// https://github.com/webpack/webpack/blob/2738eebc7880835d88c727d364ad37f3ec557593/lib/RuntimeGlobals.js#L204
declare const __webpack_require__: any
const addChunkSuffix =
(getOriginalChunk: (chunkId: any) => string) => (chunkId: any) => {
return (
getOriginalChunk(chunkId) +
`${
process.env.NEXT_DEPLOYMENT_ID
? `?dpl=${process.env.NEXT_DEPLOYMENT_ID}`
: ''
}`
)
}
// eslint-disable-next-line no-undef
const getChunkScriptFilename = __webpack_require__.u
const chunkFilenameMap: any = {}
// eslint-disable-next-line no-undef
__webpack_require__.u = addChunkSuffix((chunkId) =>
encodeURI(chunkFilenameMap[chunkId] || getChunkScriptFilename(chunkId))
)
// eslint-disable-next-line no-undef
const getChunkCssFilename = __webpack_require__.k
// eslint-disable-next-line no-undef
__webpack_require__.k = addChunkSuffix(getChunkCssFilename)
// eslint-disable-next-line no-undef
const getMiniCssFilename = __webpack_require__.miniCssF
// eslint-disable-next-line no-undef
__webpack_require__.miniCssF = addChunkSuffix(getMiniCssFilename)
// Ignore the module ID transform in client.
// eslint-disable-next-line no-undef
// @ts-expect-error TODO: fix type
self.__next_require__ =
process.env.NODE_ENV !== 'production'
? (id: string) => {
const mod = __webpack_require__(id)
if (typeof mod === 'object') {
// Return a proxy to flight client to make sure it's always getting
// the latest module, instead of being cached.
return new Proxy(mod, {
get(_target, prop) {
return __webpack_require__(id)[prop]
},
})
}
return mod
}
: __webpack_require__
// eslint-disable-next-line no-undef
;(self as any).__next_chunk_load__ = (chunk: string) => {
if (!chunk) return Promise.resolve()
const [chunkId, chunkFilePath] = chunk.split(':')
chunkFilenameMap[chunkId] = chunkFilePath
// @ts-ignore
// eslint-disable-next-line no-undef
return __webpack_chunk_load__(chunkId)
}
export {}

View file

@ -1,3 +1,4 @@
declare const __webpack_require__: any
declare let __webpack_public_path__: string declare let __webpack_public_path__: string
const addChunkSuffix = const addChunkSuffix =
@ -34,3 +35,5 @@ __webpack_require__.miniCssF = addChunkSuffix(getMiniCssFilename)
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
__webpack_public_path__ = path __webpack_public_path__ = path
} }
export {}