Reland "Middleware to use react-server condition" (#66534)
This commit is contained in:
parent
e4d107cc93
commit
ce69888af1
50 changed files with 723 additions and 136 deletions
|
@ -59,7 +59,7 @@ use turbopack_binding::{
|
|||
turbopack::{
|
||||
module_options::ModuleOptionsContext,
|
||||
resolve_options_context::ResolveOptionsContext,
|
||||
transition::{ContextTransition, FullContextTransition},
|
||||
transition::{ContextTransition, FullContextTransition, Transition},
|
||||
ModuleAssetContext,
|
||||
},
|
||||
},
|
||||
|
@ -118,7 +118,7 @@ impl AppProject {
|
|||
}
|
||||
}
|
||||
|
||||
const ECMASCRIPT_CLIENT_TRANSITION_NAME: &str = "next-ecmascript-client-reference";
|
||||
pub(crate) const ECMASCRIPT_CLIENT_TRANSITION_NAME: &str = "next-ecmascript-client-reference";
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
impl AppProject {
|
||||
|
@ -166,7 +166,7 @@ impl AppProject {
|
|||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
fn client_transition_name(self: Vc<Self>) -> Vc<RcStr> {
|
||||
pub(crate) fn client_transition_name(self: Vc<Self>) -> Vc<RcStr> {
|
||||
Vc::cell(ECMASCRIPT_CLIENT_TRANSITION_NAME.into())
|
||||
}
|
||||
|
||||
|
@ -270,15 +270,28 @@ impl AppProject {
|
|||
))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub fn client_reference_transition(self: Vc<Self>) -> Vc<Box<dyn Transition>> {
|
||||
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
|
||||
Vc::upcast(self.client_transition()),
|
||||
self.ssr_transition(),
|
||||
))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
pub fn edge_client_reference_transition(self: Vc<Self>) -> Vc<Box<dyn Transition>> {
|
||||
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
|
||||
Vc::upcast(self.client_transition()),
|
||||
self.edge_ssr_transition(),
|
||||
))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
fn rsc_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
|
||||
let transitions = [
|
||||
(
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
|
||||
Vc::upcast(self.client_transition()),
|
||||
self.ssr_transition(),
|
||||
)),
|
||||
self.client_reference_transition(),
|
||||
),
|
||||
(
|
||||
"next-dynamic".into(),
|
||||
|
@ -305,10 +318,7 @@ impl AppProject {
|
|||
let transitions = [
|
||||
(
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
|
||||
Vc::upcast(self.client_transition()),
|
||||
self.edge_ssr_transition(),
|
||||
)),
|
||||
self.edge_client_reference_transition(),
|
||||
),
|
||||
(
|
||||
"next-dynamic".into(),
|
||||
|
@ -338,10 +348,7 @@ impl AppProject {
|
|||
let transitions = [
|
||||
(
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
|
||||
Vc::upcast(self.client_transition()),
|
||||
self.ssr_transition(),
|
||||
)),
|
||||
self.client_reference_transition(),
|
||||
),
|
||||
(
|
||||
"next-dynamic".into(),
|
||||
|
@ -369,10 +376,7 @@ impl AppProject {
|
|||
let transitions = [
|
||||
(
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
|
||||
Vc::upcast(self.client_transition()),
|
||||
self.edge_ssr_transition(),
|
||||
)),
|
||||
self.edge_client_reference_transition(),
|
||||
),
|
||||
(
|
||||
"next-dynamic".into(),
|
||||
|
|
|
@ -6,9 +6,9 @@ use next_core::{
|
|||
next_server::{get_server_runtime_entries, ServerContextType},
|
||||
};
|
||||
use tracing::Instrument;
|
||||
use turbo_tasks::{Completion, Value, Vc};
|
||||
use turbo_tasks::{Completion, RcStr, Value, Vc};
|
||||
use turbopack_binding::{
|
||||
turbo::tasks_fs::{File, FileContent},
|
||||
turbo::tasks_fs::{File, FileContent, FileSystemPath},
|
||||
turbopack::{
|
||||
core::{
|
||||
asset::AssetContent,
|
||||
|
@ -41,6 +41,9 @@ pub struct InstrumentationEndpoint {
|
|||
context: Vc<Box<dyn AssetContext>>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
is_edge: bool,
|
||||
|
||||
app_dir: Option<Vc<FileSystemPath>>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
|
@ -51,12 +54,16 @@ impl InstrumentationEndpoint {
|
|||
context: Vc<Box<dyn AssetContext>>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
is_edge: bool,
|
||||
app_dir: Option<Vc<FileSystemPath>>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
) -> Vc<Self> {
|
||||
Self {
|
||||
project,
|
||||
context,
|
||||
source,
|
||||
is_edge,
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}
|
||||
.cell()
|
||||
}
|
||||
|
@ -79,7 +86,11 @@ impl InstrumentationEndpoint {
|
|||
);
|
||||
|
||||
let mut evaluatable_assets = get_server_runtime_entries(
|
||||
Value::new(ServerContextType::Middleware),
|
||||
Value::new(ServerContextType::Instrumentation {
|
||||
app_dir: self.app_dir,
|
||||
ecmascript_client_reference_transition_name: self
|
||||
.ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.project.next_mode(),
|
||||
)
|
||||
.resolve_entries(self.context)
|
||||
|
@ -131,7 +142,11 @@ impl InstrumentationEndpoint {
|
|||
.join("server/instrumentation.js".into()),
|
||||
module,
|
||||
get_server_runtime_entries(
|
||||
Value::new(ServerContextType::Instrumentation),
|
||||
Value::new(ServerContextType::Instrumentation {
|
||||
app_dir: self.app_dir,
|
||||
ecmascript_client_reference_transition_name: self
|
||||
.ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.project.next_mode(),
|
||||
)
|
||||
.resolve_entries(self.context),
|
||||
|
|
|
@ -8,9 +8,9 @@ use next_core::{
|
|||
util::{parse_config_from_source, MiddlewareMatcherKind},
|
||||
};
|
||||
use tracing::Instrument;
|
||||
use turbo_tasks::{Completion, Value, Vc};
|
||||
use turbo_tasks::{Completion, RcStr, Value, Vc};
|
||||
use turbopack_binding::{
|
||||
turbo::tasks_fs::{File, FileContent},
|
||||
turbo::tasks_fs::{File, FileContent, FileSystemPath},
|
||||
turbopack::{
|
||||
core::{
|
||||
asset::AssetContent,
|
||||
|
@ -40,6 +40,8 @@ pub struct MiddlewareEndpoint {
|
|||
project: Vc<Project>,
|
||||
context: Vc<Box<dyn AssetContext>>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
app_dir: Option<Vc<FileSystemPath>>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
}
|
||||
|
||||
#[turbo_tasks::value_impl]
|
||||
|
@ -49,11 +51,15 @@ impl MiddlewareEndpoint {
|
|||
project: Vc<Project>,
|
||||
context: Vc<Box<dyn AssetContext>>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
app_dir: Option<Vc<FileSystemPath>>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
) -> Vc<Self> {
|
||||
Self {
|
||||
project,
|
||||
context,
|
||||
source,
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}
|
||||
.cell()
|
||||
}
|
||||
|
@ -79,7 +85,11 @@ impl MiddlewareEndpoint {
|
|||
);
|
||||
|
||||
let mut evaluatable_assets = get_server_runtime_entries(
|
||||
Value::new(ServerContextType::Middleware),
|
||||
Value::new(ServerContextType::Middleware {
|
||||
app_dir: self.app_dir,
|
||||
ecmascript_client_reference_transition_name: self
|
||||
.ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.project.next_mode(),
|
||||
)
|
||||
.resolve_entries(self.context)
|
||||
|
|
|
@ -57,7 +57,7 @@ use turbopack_binding::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
app::{AppProject, OptionAppProject},
|
||||
app::{AppProject, OptionAppProject, ECMASCRIPT_CLIENT_TRANSITION_NAME},
|
||||
build,
|
||||
entrypoints::Entrypoints,
|
||||
instrumentation::InstrumentationEndpoint,
|
||||
|
@ -875,27 +875,49 @@ impl Project {
|
|||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
fn middleware_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
|
||||
Vc::upcast(ModuleAssetContext::new(
|
||||
Default::default(),
|
||||
async fn middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
|
||||
let mut transitions = vec![];
|
||||
|
||||
let app_dir = *find_app_dir(self.project_path()).await?;
|
||||
let app_project = *self.app_project().await?;
|
||||
|
||||
let ecmascript_client_reference_transition_name = app_project
|
||||
.as_ref()
|
||||
.map(|app_project| app_project.client_transition_name());
|
||||
|
||||
if let Some(app_project) = app_project {
|
||||
transitions.push((
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
app_project.edge_client_reference_transition(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Vc::upcast(ModuleAssetContext::new(
|
||||
Vc::cell(transitions.into_iter().collect()),
|
||||
self.edge_compile_time_info(),
|
||||
get_server_module_options_context(
|
||||
self.project_path(),
|
||||
self.execution_context(),
|
||||
Value::new(ServerContextType::Middleware),
|
||||
Value::new(ServerContextType::Middleware {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.next_mode(),
|
||||
self.next_config(),
|
||||
NextRuntime::Edge,
|
||||
),
|
||||
get_edge_resolve_options_context(
|
||||
self.project_path(),
|
||||
Value::new(ServerContextType::Middleware),
|
||||
Value::new(ServerContextType::Middleware {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.next_mode(),
|
||||
self.next_config(),
|
||||
self.execution_context(),
|
||||
),
|
||||
Vc::cell("middleware".into()),
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
|
@ -903,48 +925,139 @@ impl Project {
|
|||
self: Vc<Self>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
) -> Result<Vc<MiddlewareEndpoint>> {
|
||||
let app_dir = *find_app_dir(self.project_path()).await?;
|
||||
let ecmascript_client_reference_transition_name = (*self.app_project().await?)
|
||||
.as_ref()
|
||||
.map(|app_project| app_project.client_transition_name());
|
||||
|
||||
let context = self.middleware_context();
|
||||
|
||||
Ok(MiddlewareEndpoint::new(self, context, source))
|
||||
Ok(MiddlewareEndpoint::new(
|
||||
self,
|
||||
context,
|
||||
source,
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
fn node_instrumentation_context(self: Vc<Self>) -> Vc<Box<dyn AssetContext>> {
|
||||
Vc::upcast(ModuleAssetContext::new(
|
||||
Default::default(),
|
||||
async fn node_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
|
||||
let mut transitions = vec![];
|
||||
|
||||
let app_dir = *find_app_dir(self.project_path()).await?;
|
||||
let app_project = &*self.app_project().await?;
|
||||
|
||||
let ecmascript_client_reference_transition_name = app_project
|
||||
.as_ref()
|
||||
.map(|app_project| app_project.client_transition_name());
|
||||
|
||||
if let Some(app_project) = app_project {
|
||||
transitions.push((
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
app_project.client_reference_transition(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Vc::upcast(ModuleAssetContext::new(
|
||||
Vc::cell(transitions.into_iter().collect()),
|
||||
self.server_compile_time_info(),
|
||||
get_server_module_options_context(
|
||||
self.project_path(),
|
||||
self.execution_context(),
|
||||
Value::new(ServerContextType::Instrumentation),
|
||||
Value::new(ServerContextType::Instrumentation {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.next_mode(),
|
||||
self.next_config(),
|
||||
NextRuntime::NodeJs,
|
||||
),
|
||||
get_server_resolve_options_context(
|
||||
self.project_path(),
|
||||
Value::new(ServerContextType::Instrumentation),
|
||||
Value::new(ServerContextType::Instrumentation {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.next_mode(),
|
||||
self.next_config(),
|
||||
self.execution_context(),
|
||||
),
|
||||
Vc::cell("instrumentation-edge".into()),
|
||||
)))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
async fn edge_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
|
||||
let mut transitions = vec![];
|
||||
|
||||
let app_dir = *find_app_dir(self.project_path()).await?;
|
||||
let app_project = &*self.app_project().await?;
|
||||
|
||||
let ecmascript_client_reference_transition_name = app_project
|
||||
.as_ref()
|
||||
.map(|app_project| app_project.client_transition_name());
|
||||
|
||||
if let Some(app_project) = app_project {
|
||||
transitions.push((
|
||||
ECMASCRIPT_CLIENT_TRANSITION_NAME.into(),
|
||||
app_project.edge_client_reference_transition(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Vc::upcast(ModuleAssetContext::new(
|
||||
Vc::cell(transitions.into_iter().collect()),
|
||||
self.edge_compile_time_info(),
|
||||
get_server_module_options_context(
|
||||
self.project_path(),
|
||||
self.execution_context(),
|
||||
Value::new(ServerContextType::Instrumentation {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.next_mode(),
|
||||
self.next_config(),
|
||||
NextRuntime::Edge,
|
||||
),
|
||||
get_edge_resolve_options_context(
|
||||
self.project_path(),
|
||||
Value::new(ServerContextType::Instrumentation {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}),
|
||||
self.next_mode(),
|
||||
self.next_config(),
|
||||
self.execution_context(),
|
||||
),
|
||||
Vc::cell("instrumentation".into()),
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
fn instrumentation_endpoint(
|
||||
async fn instrumentation_endpoint(
|
||||
self: Vc<Self>,
|
||||
source: Vc<Box<dyn Source>>,
|
||||
is_edge: bool,
|
||||
) -> Vc<InstrumentationEndpoint> {
|
||||
) -> Result<Vc<InstrumentationEndpoint>> {
|
||||
let app_dir = *find_app_dir(self.project_path()).await?;
|
||||
let ecmascript_client_reference_transition_name = (*self.app_project().await?)
|
||||
.as_ref()
|
||||
.map(|app_project| app_project.client_transition_name());
|
||||
|
||||
let context = if is_edge {
|
||||
self.middleware_context()
|
||||
self.edge_instrumentation_context()
|
||||
} else {
|
||||
self.node_instrumentation_context()
|
||||
};
|
||||
|
||||
InstrumentationEndpoint::new(self, context, source, is_edge)
|
||||
Ok(InstrumentationEndpoint::new(
|
||||
self,
|
||||
context,
|
||||
source,
|
||||
is_edge,
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
))
|
||||
}
|
||||
|
||||
#[turbo_tasks::function]
|
||||
|
|
|
@ -98,7 +98,7 @@ pub async fn get_edge_resolve_options_context(
|
|||
let next_edge_import_map =
|
||||
get_next_edge_import_map(project_path, ty, next_config, execution_context);
|
||||
|
||||
let ty = ty.into_value();
|
||||
let ty: ServerContextType = ty.into_value();
|
||||
|
||||
let mut before_resolve_plugins = vec![Vc::upcast(ModuleFeatureReportResolvePlugin::new(
|
||||
project_path,
|
||||
|
@ -110,7 +110,7 @@ pub async fn get_edge_resolve_options_context(
|
|||
| ServerContextType::AppRSC { .. }
|
||||
) {
|
||||
before_resolve_plugins.push(Vc::upcast(NextFontLocalResolvePlugin::new(project_path)));
|
||||
}
|
||||
};
|
||||
|
||||
if matches!(
|
||||
ty,
|
||||
|
@ -118,7 +118,7 @@ pub async fn get_edge_resolve_options_context(
|
|||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::PagesData { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation
|
||||
| ServerContextType::Instrumentation { .. }
|
||||
) {
|
||||
before_resolve_plugins.push(Vc::upcast(get_invalid_client_only_resolve_plugin(
|
||||
project_path,
|
||||
|
|
|
@ -345,7 +345,7 @@ pub async fn get_next_server_import_map(
|
|||
request_to_import_mapping(project_path, "next/dist/shared/lib/app-dynamic"),
|
||||
);
|
||||
}
|
||||
ServerContextType::Middleware | ServerContextType::Instrumentation => {}
|
||||
ServerContextType::Middleware { .. } | ServerContextType::Instrumentation { .. } => {}
|
||||
}
|
||||
|
||||
insert_next_server_special_aliases(
|
||||
|
@ -433,7 +433,9 @@ pub async fn get_next_edge_import_map(
|
|||
match ty {
|
||||
ServerContextType::Pages { .. }
|
||||
| ServerContextType::PagesData { .. }
|
||||
| ServerContextType::PagesApi { .. } => {}
|
||||
| ServerContextType::PagesApi { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation { .. } => {}
|
||||
ServerContextType::AppSSR { .. }
|
||||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. } => {
|
||||
|
@ -446,7 +448,6 @@ pub async fn get_next_edge_import_map(
|
|||
request_to_import_mapping(project_path, "next/dist/shared/lib/app-dynamic"),
|
||||
);
|
||||
}
|
||||
ServerContextType::Middleware | ServerContextType::Instrumentation => {}
|
||||
}
|
||||
|
||||
insert_next_server_special_aliases(
|
||||
|
@ -465,6 +466,7 @@ pub async fn get_next_edge_import_map(
|
|||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation { .. }
|
||||
| ServerContextType::Pages { .. }
|
||||
| ServerContextType::PagesData { .. }
|
||||
| ServerContextType::PagesApi { .. } => {
|
||||
|
@ -474,7 +476,6 @@ pub async fn get_next_edge_import_map(
|
|||
execution_context,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(import_map.cell())
|
||||
|
@ -576,10 +577,9 @@ async fn insert_next_server_special_aliases(
|
|||
|
||||
rsc_aliases(import_map, project_path, ty, runtime, next_config).await?;
|
||||
}
|
||||
ServerContextType::Middleware => {
|
||||
ServerContextType::Middleware { .. } | ServerContextType::Instrumentation { .. } => {
|
||||
rsc_aliases(import_map, project_path, ty, runtime, next_config).await?;
|
||||
}
|
||||
ServerContextType::Instrumentation => {}
|
||||
}
|
||||
|
||||
// see https://github.com/vercel/next.js/blob/8013ef7372fc545d49dbd060461224ceb563b454/packages/next/src/build/webpack-config.ts#L1449-L1531
|
||||
|
@ -605,7 +605,7 @@ async fn insert_next_server_special_aliases(
|
|||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation => {
|
||||
| ServerContextType::Instrumentation { .. } => {
|
||||
insert_exact_alias_map(
|
||||
import_map,
|
||||
project_path,
|
||||
|
@ -650,19 +650,29 @@ async fn rsc_aliases(
|
|||
let taint = *next_config.enable_taint().await?;
|
||||
let react_channel = if ppr || taint { "-experimental" } else { "" };
|
||||
|
||||
let mut alias = indexmap! {
|
||||
"react" => format!("next/dist/compiled/react{react_channel}"),
|
||||
"react-dom" => format!("next/dist/compiled/react-dom{react_channel}"),
|
||||
"react/jsx-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-runtime"),
|
||||
"react/jsx-dev-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-dev-runtime"),
|
||||
"react/compiler-runtime" => format!("next/dist/compiled/react{react_channel}/compiler-runtime"),
|
||||
"react-dom/client" => format!("next/dist/compiled/react-dom{react_channel}/client"),
|
||||
"react-dom/static" => format!("next/dist/compiled/react-dom-experimental/static"),
|
||||
"react-dom/static.edge" => format!("next/dist/compiled/react-dom-experimental/static.edge"),
|
||||
"react-dom/static.browser" => format!("next/dist/compiled/react-dom-experimental/static.browser"),
|
||||
"react-dom/server" => format!("next/dist/compiled/react-dom{react_channel}/server"),
|
||||
"react-dom/server.edge" => format!("next/dist/compiled/react-dom{react_channel}/server.edge"),
|
||||
"react-dom/server.browser" => format!("next/dist/compiled/react-dom{react_channel}/server.browser"),
|
||||
let mut alias = IndexMap::new();
|
||||
if matches!(
|
||||
ty,
|
||||
ServerContextType::AppSSR { .. }
|
||||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. }
|
||||
) {
|
||||
alias.extend(indexmap! {
|
||||
"react" => format!("next/dist/compiled/react{react_channel}"),
|
||||
"react-dom" => format!("next/dist/compiled/react-dom{react_channel}"),
|
||||
"react/jsx-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-runtime"),
|
||||
"react/jsx-dev-runtime" => format!("next/dist/compiled/react{react_channel}/jsx-dev-runtime"),
|
||||
"react/compiler-runtime" => format!("next/dist/compiled/react{react_channel}/compiler-runtime"),
|
||||
"react-dom/client" => format!("next/dist/compiled/react-dom{react_channel}/client"),
|
||||
"react-dom/static" => format!("next/dist/compiled/react-dom-experimental/static"),
|
||||
"react-dom/static.edge" => format!("next/dist/compiled/react-dom-experimental/static.edge"),
|
||||
"react-dom/static.browser" => format!("next/dist/compiled/react-dom-experimental/static.browser"),
|
||||
"react-dom/server" => format!("next/dist/compiled/react-dom{react_channel}/server"),
|
||||
"react-dom/server.edge" => format!("next/dist/compiled/react-dom{react_channel}/server.edge"),
|
||||
"react-dom/server.browser" => format!("next/dist/compiled/react-dom{react_channel}/server.browser"),
|
||||
});
|
||||
}
|
||||
alias.extend(indexmap! {
|
||||
"react-server-dom-webpack/client" => format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client"),
|
||||
"react-server-dom-webpack/client.edge" => format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.edge"),
|
||||
"react-server-dom-webpack/server.edge" => format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.edge"),
|
||||
|
@ -671,7 +681,7 @@ async fn rsc_aliases(
|
|||
"react-server-dom-turbopack/client.edge" => format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/client.edge"),
|
||||
"react-server-dom-turbopack/server.edge" => format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.edge"),
|
||||
"react-server-dom-turbopack/server.node" => format!("next/dist/compiled/react-server-dom-turbopack{react_channel}/server.node"),
|
||||
};
|
||||
});
|
||||
|
||||
if runtime == NextRuntime::NodeJs {
|
||||
match ty {
|
||||
|
@ -684,9 +694,12 @@ async fn rsc_aliases(
|
|||
"react-dom" => format!("next/dist/server/route-modules/app-page/vendored/ssr/react-dom"),
|
||||
"react-server-dom-webpack/client.edge" => format!("next/dist/server/route-modules/app-page/vendored/ssr/react-server-dom-turbopack-client-edge"),
|
||||
"react-server-dom-turbopack/client.edge" => format!("next/dist/server/route-modules/app-page/vendored/ssr/react-server-dom-turbopack-client-edge"),
|
||||
})
|
||||
});
|
||||
}
|
||||
ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => {
|
||||
ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation { .. } => {
|
||||
alias.extend(indexmap! {
|
||||
"react/jsx-runtime" => format!("next/dist/server/route-modules/app-page/vendored/rsc/react-jsx-runtime"),
|
||||
"react/jsx-dev-runtime" => format!("next/dist/server/route-modules/app-page/vendored/rsc/react-jsx-dev-runtime"),
|
||||
|
@ -701,7 +714,7 @@ async fn rsc_aliases(
|
|||
|
||||
// Needed to make `react-dom/server` work.
|
||||
"next/dist/compiled/react" => format!("next/dist/compiled/react/index.js"),
|
||||
})
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@ use turbopack_binding::{
|
|||
free_var_references,
|
||||
},
|
||||
ecmascript::{references::esm::UrlRewriteBehavior, TreeShakingMode},
|
||||
ecmascript_plugin::transform::directives::client::ClientDirectiveTransformer,
|
||||
ecmascript_plugin::transform::directives::{
|
||||
client::ClientDirectiveTransformer,
|
||||
client_disallowed::ClientDisallowedDirectiveTransformer,
|
||||
},
|
||||
node::{
|
||||
execution_context::ExecutionContext,
|
||||
transforms::postcss::{PostCssConfigLocation, PostCssTransformOptions},
|
||||
|
@ -96,8 +99,14 @@ pub enum ServerContextType {
|
|||
app_dir: Vc<FileSystemPath>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
},
|
||||
Middleware,
|
||||
Instrumentation,
|
||||
Middleware {
|
||||
app_dir: Option<Vc<FileSystemPath>>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
},
|
||||
Instrumentation {
|
||||
app_dir: Option<Vc<FileSystemPath>>,
|
||||
ecmascript_client_reference_transition_name: Option<Vc<RcStr>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ServerContextType {
|
||||
|
@ -108,6 +117,7 @@ impl ServerContextType {
|
|||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::PagesApi { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation { .. }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +224,7 @@ pub async fn get_server_resolve_options_context(
|
|||
| ServerContextType::PagesApi { .. }
|
||||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation => {
|
||||
| ServerContextType::Instrumentation { .. } => {
|
||||
vec![Vc::upcast(module_feature_report_resolve_plugin)]
|
||||
}
|
||||
};
|
||||
|
@ -264,7 +274,7 @@ pub async fn get_server_resolve_options_context(
|
|||
| ServerContextType::AppRSC { .. }
|
||||
| ServerContextType::AppRoute { .. }
|
||||
| ServerContextType::Middleware { .. }
|
||||
| ServerContextType::Instrumentation => {
|
||||
| ServerContextType::Instrumentation { .. } => {
|
||||
before_resolve_plugins.push(Vc::upcast(invalid_client_only_resolve_plugin));
|
||||
before_resolve_plugins.push(Vc::upcast(invalid_styled_jsx_client_only_resolve_plugin));
|
||||
}
|
||||
|
@ -731,13 +741,46 @@ pub async fn get_server_module_options_context(
|
|||
..module_options_context
|
||||
}
|
||||
}
|
||||
ServerContextType::Middleware | ServerContextType::Instrumentation => {
|
||||
let custom_source_transform_rules: Vec<ModuleRule> =
|
||||
ServerContextType::Middleware {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
}
|
||||
| ServerContextType::Instrumentation {
|
||||
app_dir,
|
||||
ecmascript_client_reference_transition_name,
|
||||
} => {
|
||||
let mut custom_source_transform_rules: Vec<ModuleRule> =
|
||||
vec![styled_components_transform_rule, styled_jsx_transform_rule]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if let Some(ecmascript_client_reference_transition_name) =
|
||||
ecmascript_client_reference_transition_name
|
||||
{
|
||||
custom_source_transform_rules.push(get_ecma_transform_rule(
|
||||
Box::new(ClientDirectiveTransformer::new(
|
||||
ecmascript_client_reference_transition_name,
|
||||
)),
|
||||
enable_mdx_rs.is_some(),
|
||||
true,
|
||||
));
|
||||
} else {
|
||||
custom_source_transform_rules.push(get_ecma_transform_rule(
|
||||
Box::new(ClientDisallowedDirectiveTransformer::new(
|
||||
"next/dist/client/use-client-disallowed.js".to_string(),
|
||||
)),
|
||||
enable_mdx_rs.is_some(),
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
custom_source_transform_rules.push(
|
||||
get_next_react_server_components_transform_rule(next_config, true, app_dir).await?,
|
||||
);
|
||||
|
||||
internal_custom_rules.extend(custom_source_transform_rules.iter().cloned());
|
||||
|
||||
next_server_rules.extend(custom_source_transform_rules);
|
||||
next_server_rules.extend(source_transform_rules);
|
||||
|
||||
|
|
|
@ -293,10 +293,10 @@ impl AfterResolvePlugin for NextNodeSharedRuntimeResolvePlugin {
|
|||
let resource_request = format!(
|
||||
"next/dist/server/route-modules/{}/vendored/contexts/{}.js",
|
||||
match self.context {
|
||||
ServerContextType::Pages { .. } => "pages",
|
||||
ServerContextType::AppRoute { .. } => "app-route",
|
||||
ServerContextType::AppSSR { .. } | ServerContextType::AppRSC { .. } => "app-page",
|
||||
_ => "unknown",
|
||||
// Use default pages context for all other contexts.
|
||||
_ => "pages",
|
||||
},
|
||||
stem
|
||||
);
|
||||
|
|
|
@ -235,6 +235,16 @@ export function createAppRouterApiAliases(isServerOnlyLayer: boolean) {
|
|||
return aliasMap
|
||||
}
|
||||
|
||||
export function createRSCRendererAliases(bundledReactChannel: string) {
|
||||
return {
|
||||
// react-server-dom-webpack alias
|
||||
'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`,
|
||||
}
|
||||
}
|
||||
|
||||
export function createRSCAliases(
|
||||
bundledReactChannel: string,
|
||||
{
|
||||
|
@ -262,10 +272,7 @@ export function createRSCAliases(
|
|||
'react-dom/server.edge$': `next/dist/build/webpack/alias/react-dom-server-edge${bundledReactChannel}.js`,
|
||||
'react-dom/server.browser$': `next/dist/build/webpack/alias/react-dom-server-browser${bundledReactChannel}.js`,
|
||||
// react-server-dom-webpack alias
|
||||
'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`,
|
||||
...createRSCRendererAliases(bundledReactChannel),
|
||||
}
|
||||
|
||||
if (!isEdgeServer) {
|
||||
|
|
|
@ -838,8 +838,9 @@ export function finalizeEntrypoint({
|
|||
}
|
||||
case COMPILER_NAMES.edgeServer: {
|
||||
return {
|
||||
layer:
|
||||
isMiddlewareFilename(name) || isApi || isInstrumentation
|
||||
layer: isApi
|
||||
? WEBPACK_LAYERS.api
|
||||
: isMiddlewareFilename(name) || isInstrumentation
|
||||
? WEBPACK_LAYERS.middleware
|
||||
: undefined,
|
||||
library: { name: ['_ENTRIES', `middleware_[name]`], type: 'assign' },
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
NODE_ESM_RESOLVE_OPTIONS,
|
||||
NODE_RESOLVE_OPTIONS,
|
||||
} from './webpack-config'
|
||||
import { isWebpackAppLayer, isWebpackServerOnlyLayer } from './utils'
|
||||
import { isWebpackBundledLayer, isWebpackServerOnlyLayer } from './utils'
|
||||
import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep'
|
||||
const reactPackagesRegex = /^(react|react-dom|react-server-dom-webpack)($|\/)/
|
||||
|
||||
|
@ -174,7 +174,7 @@ export function makeExternalHandler({
|
|||
return `commonjs next/dist/lib/import-next-warning`
|
||||
}
|
||||
|
||||
const isAppLayer = isWebpackAppLayer(layer)
|
||||
const isAppLayer = isWebpackBundledLayer(layer)
|
||||
|
||||
// Relative requires don't need custom resolution, because they
|
||||
// are relative to requests we've already resolved here.
|
||||
|
|
|
@ -2276,8 +2276,8 @@ export function isWebpackDefaultLayer(
|
|||
return layer === null || layer === undefined
|
||||
}
|
||||
|
||||
export function isWebpackAppLayer(
|
||||
export function isWebpackBundledLayer(
|
||||
layer: WebpackLayerName | null | undefined
|
||||
): boolean {
|
||||
return Boolean(layer && WEBPACK_LAYERS.GROUP.app.includes(layer as any))
|
||||
return Boolean(layer && WEBPACK_LAYERS.GROUP.bundled.includes(layer as any))
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { escapeStringRegexp } from '../shared/lib/escape-regexp'
|
|||
import { WEBPACK_LAYERS, WEBPACK_RESOURCE_QUERIES } from '../lib/constants'
|
||||
import type { WebpackLayerName } from '../lib/constants'
|
||||
import {
|
||||
isWebpackAppLayer,
|
||||
isWebpackBundledLayer,
|
||||
isWebpackClientOnlyLayer,
|
||||
isWebpackDefaultLayer,
|
||||
isWebpackServerOnlyLayer,
|
||||
|
@ -81,6 +81,7 @@ import {
|
|||
createRSCAliases,
|
||||
createNextApiEsmAliases,
|
||||
createAppRouterApiAliases,
|
||||
createRSCRendererAliases,
|
||||
} from './create-compiler-aliases'
|
||||
import { hasCustomExportOutput } from '../export/utils'
|
||||
import { CssChunkingPlugin } from './webpack/plugins/css-chunking-plugin'
|
||||
|
@ -529,6 +530,7 @@ export default async function getBaseWebpackConfig(
|
|||
: []
|
||||
|
||||
const instrumentLayerLoaders = [
|
||||
'next-flight-loader',
|
||||
// When using Babel, we will have to add the SWC loader
|
||||
// as an additional pass to handle RSC correctly.
|
||||
// This will cause some performance overhead but
|
||||
|
@ -538,12 +540,13 @@ export default async function getBaseWebpackConfig(
|
|||
].filter(Boolean)
|
||||
|
||||
const middlewareLayerLoaders = [
|
||||
'next-flight-loader',
|
||||
// When using Babel, we will have to use SWC to do the optimization
|
||||
// for middleware to tree shake the unused default optimized imports like "next/server".
|
||||
// This will cause some performance overhead but
|
||||
// acceptable as Babel will not be recommended.
|
||||
getSwcLoader({
|
||||
serverComponents: false,
|
||||
serverComponents: true,
|
||||
bundleLayer: WEBPACK_LAYERS.middleware,
|
||||
}),
|
||||
babelLoader,
|
||||
|
@ -592,13 +595,12 @@ export default async function getBaseWebpackConfig(
|
|||
// Loader for API routes needs to be differently configured as it shouldn't
|
||||
// have RSC transpiler enabled, so syntax checks such as invalid imports won't
|
||||
// be performed.
|
||||
const apiRoutesLayerLoaders =
|
||||
hasAppDir && useSWCLoader
|
||||
? getSwcLoader({
|
||||
serverComponents: false,
|
||||
bundleLayer: WEBPACK_LAYERS.api,
|
||||
})
|
||||
: defaultLoaders.babel
|
||||
const apiRoutesLayerLoaders = useSWCLoader
|
||||
? getSwcLoader({
|
||||
serverComponents: false,
|
||||
bundleLayer: WEBPACK_LAYERS.api,
|
||||
})
|
||||
: defaultLoaders.babel
|
||||
|
||||
const pageExtensions = config.pageExtensions
|
||||
|
||||
|
@ -1304,7 +1306,7 @@ export default async function getBaseWebpackConfig(
|
|||
test: /next[\\/]dist[\\/](esm[\\/])?server[\\/]route-modules[\\/]app-page[\\/]module/,
|
||||
},
|
||||
{
|
||||
issuerLayer: isWebpackAppLayer,
|
||||
issuerLayer: isWebpackBundledLayer,
|
||||
resolve: {
|
||||
alias: createNextApiEsmAliases(),
|
||||
},
|
||||
|
@ -1460,8 +1462,11 @@ export default async function getBaseWebpackConfig(
|
|||
...codeCondition,
|
||||
issuerLayer: WEBPACK_LAYERS.api,
|
||||
parser: {
|
||||
// Switch back to normal URL handling
|
||||
url: true,
|
||||
// In Node.js, switch back to normal URL handling.
|
||||
// In Edge runtime, we should disable parser.url handling in webpack so URLDependency is not added.
|
||||
// Then there's browser code won't be injected into the edge runtime chunk.
|
||||
// x-ref: https://github.com/webpack/webpack/blob/d9ce3b1f87e63c809d8a19bbd92257d65922e81f/lib/web/JsonpChunkLoadingRuntimeModule.js#L69
|
||||
url: !isEdgeServer,
|
||||
},
|
||||
use: apiRoutesLayerLoaders,
|
||||
},
|
||||
|
@ -1469,11 +1474,23 @@ export default async function getBaseWebpackConfig(
|
|||
test: codeCondition.test,
|
||||
issuerLayer: WEBPACK_LAYERS.middleware,
|
||||
use: middlewareLayerLoaders,
|
||||
resolve: {
|
||||
mainFields: getMainField(compilerType, true),
|
||||
conditionNames: reactServerCondition,
|
||||
// Always use default channels when use installed react
|
||||
alias: createRSCRendererAliases(''),
|
||||
},
|
||||
},
|
||||
{
|
||||
test: codeCondition.test,
|
||||
issuerLayer: WEBPACK_LAYERS.instrument,
|
||||
use: instrumentLayerLoaders,
|
||||
resolve: {
|
||||
mainFields: getMainField(compilerType, true),
|
||||
conditionNames: reactServerCondition,
|
||||
// Always use default channels when use installed react
|
||||
alias: createRSCRendererAliases(''),
|
||||
},
|
||||
},
|
||||
...(hasAppDir
|
||||
? [
|
||||
|
|
|
@ -155,6 +155,10 @@ export function getDefineEnv({
|
|||
* the runtime they are running with, if it's not using `edge-runtime`
|
||||
*/
|
||||
process.env.NEXT_EDGE_RUNTIME_PROVIDER ?? 'edge-runtime',
|
||||
|
||||
// process should be only { env: {...} } for edge runtime.
|
||||
// For ignore avoid warn on `process.emit` usage but directly omit it.
|
||||
'process.emit': false,
|
||||
}),
|
||||
'process.turbopack': isTurbopack,
|
||||
'process.env.TURBOPACK': isTurbopack,
|
||||
|
|
|
@ -28,7 +28,10 @@ import type { Telemetry } from '../../../telemetry/storage'
|
|||
import { traceGlobals } from '../../../trace/shared'
|
||||
import { EVENT_BUILD_FEATURE_USAGE } from '../../../telemetry/events'
|
||||
import { normalizeAppPath } from '../../../shared/lib/router/utils/app-paths'
|
||||
import { INSTRUMENTATION_HOOK_FILENAME } from '../../../lib/constants'
|
||||
import {
|
||||
INSTRUMENTATION_HOOK_FILENAME,
|
||||
WEBPACK_LAYERS,
|
||||
} from '../../../lib/constants'
|
||||
import type { CustomRoutes } from '../../../lib/load-custom-routes'
|
||||
import { isInterceptionRouteRewrite } from '../../../lib/generate-interception-routes-rewrites'
|
||||
import { getDynamicCodeEvaluationError } from './wellknown-errors-plugin/parse-dynamic-code-evaluation-error'
|
||||
|
@ -272,7 +275,8 @@ function buildWebpackError({
|
|||
}
|
||||
|
||||
function isInMiddlewareLayer(parser: webpack.javascript.JavascriptParser) {
|
||||
return parser.state.module?.layer === 'middleware'
|
||||
const layer = parser.state.module?.layer
|
||||
return layer === WEBPACK_LAYERS.middleware || layer === WEBPACK_LAYERS.api
|
||||
}
|
||||
|
||||
function isNodeJsModule(moduleName: string) {
|
||||
|
@ -849,7 +853,8 @@ export async function handleWebpackExternalForEdgeRuntime({
|
|||
getResolve: () => any
|
||||
}) {
|
||||
if (
|
||||
contextInfo.issuerLayer === 'middleware' &&
|
||||
(contextInfo.issuerLayer === WEBPACK_LAYERS.middleware ||
|
||||
contextInfo.issuerLayer === WEBPACK_LAYERS.api) &&
|
||||
isNodeJsModule(request) &&
|
||||
!supportedEdgePolyfills.has(request)
|
||||
) {
|
||||
|
|
19
packages/next/src/client/use-client-disallowed.ts
Normal file
19
packages/next/src/client/use-client-disallowed.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
const error = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target) {
|
||||
throw new Error(
|
||||
'Using client components is not allowed in this environment.'
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
export default new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_target, p) => {
|
||||
if (p === '__esModule') return true
|
||||
return error
|
||||
},
|
||||
}
|
||||
)
|
|
@ -159,6 +159,11 @@ export type WebpackLayerName =
|
|||
const WEBPACK_LAYERS = {
|
||||
...WEBPACK_LAYERS_NAMES,
|
||||
GROUP: {
|
||||
builtinReact: [
|
||||
WEBPACK_LAYERS_NAMES.reactServerComponents,
|
||||
WEBPACK_LAYERS_NAMES.actionBrowser,
|
||||
WEBPACK_LAYERS_NAMES.appMetadataRoute,
|
||||
],
|
||||
serverOnly: [
|
||||
WEBPACK_LAYERS_NAMES.reactServerComponents,
|
||||
WEBPACK_LAYERS_NAMES.actionBrowser,
|
||||
|
@ -174,7 +179,7 @@ const WEBPACK_LAYERS = {
|
|||
WEBPACK_LAYERS_NAMES.serverSideRendering,
|
||||
WEBPACK_LAYERS_NAMES.appPagesBrowser,
|
||||
],
|
||||
app: [
|
||||
bundled: [
|
||||
WEBPACK_LAYERS_NAMES.reactServerComponents,
|
||||
WEBPACK_LAYERS_NAMES.actionBrowser,
|
||||
WEBPACK_LAYERS_NAMES.appMetadataRoute,
|
||||
|
|
4
test/e2e/module-layer/lib/mixed-lib/shared-module.js
Normal file
4
test/e2e/module-layer/lib/mixed-lib/shared-module.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export const textValue = 'text-value'
|
||||
export const TestLink = Link
|
|
@ -1,11 +1,20 @@
|
|||
import 'server-only'
|
||||
import React from 'react'
|
||||
import * as React from 'react'
|
||||
import { NextResponse } from 'next/server'
|
||||
// import './lib/mixed-lib'
|
||||
|
||||
export function middleware(request) {
|
||||
if (React.useState) {
|
||||
// To avoid webpack ESM exports checking warning
|
||||
const ReactObject = Object(React)
|
||||
if (ReactObject.useState) {
|
||||
throw new Error('React.useState should not be defined in server layer')
|
||||
}
|
||||
|
||||
if (request.nextUrl.pathname === '/middleware') {
|
||||
return Response.json({
|
||||
React: Object.keys(ReactObject),
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { nextTestSetup } from 'e2e-utils'
|
|||
import { getRedboxSource, hasRedbox, retry } from 'next-test-utils'
|
||||
|
||||
describe('module layer', () => {
|
||||
const { next, isNextStart, isNextDev, isTurbopack } = nextTestSetup({
|
||||
const { next, isNextStart, isNextDev } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
|
||||
|
@ -18,8 +18,10 @@ describe('module layer', () => {
|
|||
'/app/route',
|
||||
'/app/route-edge',
|
||||
// pages/api
|
||||
'/api/hello',
|
||||
'/api/hello-edge',
|
||||
'/api/default',
|
||||
'/api/default-edge',
|
||||
'/api/server-only',
|
||||
'/api/server-only-edge',
|
||||
'/api/mixed',
|
||||
]
|
||||
|
||||
|
@ -30,6 +32,35 @@ describe('module layer', () => {
|
|||
})
|
||||
}
|
||||
|
||||
it('should render installed react-server condition for middleware', async () => {
|
||||
const json = await next.fetch('/middleware').then((res) => res.json())
|
||||
expect(json.React).toContain('version') // basic react-server export
|
||||
expect(json.React).not.toContain('useEffect') // no client api export
|
||||
})
|
||||
|
||||
// This is for backward compatibility, don't change react usage in existing pages/api
|
||||
it('should contain client react exports for pages api', async () => {
|
||||
async function verifyReactExports(route, isEdge) {
|
||||
const json = await next.fetch(route).then((res) => res.json())
|
||||
// contain all react-server and default condition exports
|
||||
expect(json.React).toContain('version')
|
||||
expect(json.React).toContain('useEffect')
|
||||
|
||||
// contain react-dom-server default condition exports
|
||||
expect(json.ReactDomServer).toContain('version')
|
||||
expect(json.ReactDomServer).toContain('renderToString')
|
||||
expect(json.ReactDomServer).toContain('renderToStaticMarkup')
|
||||
expect(json.ReactDomServer).toContain(
|
||||
isEdge ? 'renderToReadableStream' : 'renderToPipeableStream'
|
||||
)
|
||||
}
|
||||
|
||||
await verifyReactExports('/api/default', false)
|
||||
await verifyReactExports('/api/default-edge', true)
|
||||
await verifyReactExports('/api/server-only', false)
|
||||
await verifyReactExports('/api/server-only-edge', true)
|
||||
})
|
||||
|
||||
if (isNextStart) {
|
||||
it('should log the build info properly', async () => {
|
||||
const cliOutput = next.cliOutput
|
||||
|
@ -40,7 +71,8 @@ describe('module layer', () => {
|
|||
)
|
||||
expect(functionsManifest.functions).toContainKeys([
|
||||
'/app/route-edge',
|
||||
'/api/hello-edge',
|
||||
'/api/default-edge',
|
||||
'/api/server-only-edge',
|
||||
'/app/client-edge',
|
||||
'/app/server-edge',
|
||||
])
|
||||
|
@ -52,9 +84,10 @@ describe('module layer', () => {
|
|||
)
|
||||
expect(middlewareManifest.middleware).toBeTruthy()
|
||||
expect(pagesManifest).toContainKeys([
|
||||
'/api/hello-edge',
|
||||
'/api/default-edge',
|
||||
'/pages-ssr',
|
||||
'/api/hello',
|
||||
'/api/default',
|
||||
'/api/server-only',
|
||||
])
|
||||
})
|
||||
}
|
||||
|
@ -81,22 +114,13 @@ describe('module layer', () => {
|
|||
.replace("// import './lib/mixed-lib'", "import './lib/mixed-lib'")
|
||||
)
|
||||
|
||||
const existingCliOutputLength = next.cliOutput.length
|
||||
await retry(async () => {
|
||||
expect(await hasRedbox(browser)).toBe(true)
|
||||
const source = await getRedboxSource(browser)
|
||||
expect(source).toContain(
|
||||
`'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.`
|
||||
`You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client"`
|
||||
)
|
||||
})
|
||||
|
||||
if (!isTurbopack) {
|
||||
const newCliOutput = next.cliOutput.slice(existingCliOutputLength)
|
||||
expect(newCliOutput).toContain('./middleware.js')
|
||||
expect(newCliOutput).toContain(
|
||||
`'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
11
test/e2e/module-layer/pages/api/default-edge.js
Normal file
11
test/e2e/module-layer/pages/api/default-edge.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as ReactDomServer from 'react-dom/server'
|
||||
import * as React from 'react'
|
||||
|
||||
export default async (_req) => {
|
||||
return Response.json({
|
||||
React: Object.keys(Object(React)),
|
||||
ReactDomServer: Object.keys(Object(ReactDomServer)),
|
||||
})
|
||||
}
|
||||
|
||||
export const runtime = 'edge'
|
9
test/e2e/module-layer/pages/api/default.js
Normal file
9
test/e2e/module-layer/pages/api/default.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import * as ReactDomServer from 'react-dom/server'
|
||||
import * as React from 'react'
|
||||
|
||||
export default async (_req, res) => {
|
||||
return res.json({
|
||||
React: Object.keys(Object(React)),
|
||||
ReactDomServer: Object.keys(Object(ReactDomServer)),
|
||||
})
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import 'server-only'
|
||||
|
||||
export default function handler() {
|
||||
return new Response('pages/api/hello-edge.js:')
|
||||
}
|
||||
|
||||
export const runtime = 'edge'
|
|
@ -1,5 +0,0 @@
|
|||
import 'server-only'
|
||||
|
||||
export default function handler(req, res) {
|
||||
return res.send('pages/api/hello.js')
|
||||
}
|
12
test/e2e/module-layer/pages/api/server-only-edge.js
Normal file
12
test/e2e/module-layer/pages/api/server-only-edge.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'server-only'
|
||||
import * as ReactDomServer from 'react-dom/server'
|
||||
import * as React from 'react'
|
||||
|
||||
export default async (_req) => {
|
||||
return Response.json({
|
||||
React: Object.keys(Object(React)),
|
||||
ReactDomServer: Object.keys(Object(ReactDomServer)),
|
||||
})
|
||||
}
|
||||
|
||||
export const runtime = 'edge'
|
10
test/e2e/module-layer/pages/api/server-only.js
Normal file
10
test/e2e/module-layer/pages/api/server-only.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'server-only'
|
||||
import * as ReactDomServer from 'react-dom/server'
|
||||
import * as React from 'react'
|
||||
|
||||
export default async (_req, res) => {
|
||||
return res.json({
|
||||
React: Object.keys(Object(React)),
|
||||
ReactDomServer: Object.keys(Object(ReactDomServer)),
|
||||
})
|
||||
}
|
5
test/e2e/react-version/app/app/client-edge/page.js
vendored
Normal file
5
test/e2e/react-version/app/app/client-edge/page.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
'use client'
|
||||
|
||||
export { default } from '../client/page'
|
||||
|
||||
export const runtime = 'edge'
|
7
test/e2e/react-version/app/app/client/page.js
vendored
Normal file
7
test/e2e/react-version/app/app/client/page.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { ReactConditionUI } from '../../../lib/react-version'
|
||||
|
||||
export default function Page() {
|
||||
return <ReactConditionUI />
|
||||
}
|
3
test/e2e/react-version/app/app/route-edge/route.js
vendored
Normal file
3
test/e2e/react-version/app/app/route-edge/route.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { GET } from '../route/route'
|
||||
|
||||
export const runtime = 'edge'
|
5
test/e2e/react-version/app/app/route/route.js
vendored
Normal file
5
test/e2e/react-version/app/app/route/route.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { getReactConditionJson } from '../../../lib/react-version'
|
||||
|
||||
export function GET() {
|
||||
return Response.json(getReactConditionJson())
|
||||
}
|
3
test/e2e/react-version/app/app/server-edge/page.js
vendored
Normal file
3
test/e2e/react-version/app/app/server-edge/page.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
export { default } from '../server/page'
|
||||
|
||||
export const runtime = 'edge'
|
5
test/e2e/react-version/app/app/server/page.js
vendored
Normal file
5
test/e2e/react-version/app/app/server/page.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { ReactConditionUI } from '../../../lib/react-version'
|
||||
|
||||
export default function Page() {
|
||||
return <ReactConditionUI />
|
||||
}
|
7
test/e2e/react-version/app/layout.js
vendored
Normal file
7
test/e2e/react-version/app/layout.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
27
test/e2e/react-version/lib/react-validate.js
vendored
Normal file
27
test/e2e/react-version/lib/react-validate.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
function getReactConditionByModule(React_) {
|
||||
const React = Object(React_)
|
||||
const isReactServer =
|
||||
React.useState === undefined &&
|
||||
React.useEffect === undefined &&
|
||||
React.version !== undefined &&
|
||||
React.useId !== undefined
|
||||
return isReactServer ? 'react-server' : 'default'
|
||||
}
|
||||
|
||||
function getReactDomConditionByModule(ReactDOM_) {
|
||||
const ReactDOM = Object(ReactDOM_)
|
||||
const isReactServer =
|
||||
ReactDOM.useFormState === undefined && ReactDOM.preload !== undefined
|
||||
return isReactServer ? 'react-server' : 'default'
|
||||
}
|
||||
|
||||
export function getReactCondition() {
|
||||
return getReactConditionByModule(React)
|
||||
}
|
||||
|
||||
export function getReactDomCondition() {
|
||||
return getReactDomConditionByModule(ReactDOM)
|
||||
}
|
17
test/e2e/react-version/lib/react-version.js
vendored
Normal file
17
test/e2e/react-version/lib/react-version.js
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { getReactCondition, getReactDomCondition } from './react-validate'
|
||||
|
||||
export function ReactConditionUI() {
|
||||
return (
|
||||
<div>
|
||||
<p id="react-export-condition">{getReactCondition()}</p>
|
||||
<p id="react-dom-export-condition">{getReactDomCondition()}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function getReactConditionJson() {
|
||||
return {
|
||||
react: getReactCondition(),
|
||||
reactDom: getReactDomCondition(),
|
||||
}
|
||||
}
|
10
test/e2e/react-version/middleware.js
vendored
Normal file
10
test/e2e/react-version/middleware.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getReactConditionJson } from './lib/react-version'
|
||||
|
||||
export function middleware(request) {
|
||||
if (request.nextUrl.pathname === '/middleware') {
|
||||
return Response.json(getReactConditionJson())
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
13
test/e2e/react-version/pages/api/pages-api-edge-url-dep.js
vendored
Normal file
13
test/e2e/react-version/pages/api/pages-api-edge-url-dep.js
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { getReactConditionJson } from '../../lib/react-version'
|
||||
|
||||
// Adding URL dependency to edge api, it shouldn't break the build
|
||||
console.log(
|
||||
'TEST_URL_DEPENDENCY',
|
||||
import(new URL('./style.css', import.meta.url).href)
|
||||
)
|
||||
|
||||
export default async (_req) => {
|
||||
return Response.json(getReactConditionJson())
|
||||
}
|
||||
|
||||
export const runtime = 'experimental-edge'
|
7
test/e2e/react-version/pages/api/pages-api-edge.js
vendored
Normal file
7
test/e2e/react-version/pages/api/pages-api-edge.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { getReactConditionJson } from '../../lib/react-version'
|
||||
|
||||
export default async (_req) => {
|
||||
return Response.json(getReactConditionJson())
|
||||
}
|
||||
|
||||
export const runtime = 'experimental-edge'
|
5
test/e2e/react-version/pages/api/pages-api.js
vendored
Normal file
5
test/e2e/react-version/pages/api/pages-api.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { getReactConditionJson } from '../../lib/react-version'
|
||||
|
||||
export default async (_req, res) => {
|
||||
return res.json(getReactConditionJson())
|
||||
}
|
3
test/e2e/react-version/pages/api/style.css
Normal file
3
test/e2e/react-version/pages/api/style.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.foo {
|
||||
color: red;
|
||||
}
|
9
test/e2e/react-version/pages/pages-ssr-edge.js
vendored
Normal file
9
test/e2e/react-version/pages/pages-ssr-edge.js
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ReactConditionUI } from '../lib/react-version'
|
||||
|
||||
export default function Page() {
|
||||
return <ReactConditionUI />
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
}
|
5
test/e2e/react-version/pages/pages-ssr.js
vendored
Normal file
5
test/e2e/react-version/pages/pages-ssr.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { ReactConditionUI } from '../lib/react-version'
|
||||
|
||||
export default function Page() {
|
||||
return <ReactConditionUI />
|
||||
}
|
63
test/e2e/react-version/react-version.test.ts
Normal file
63
test/e2e/react-version/react-version.test.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { nextTestSetup } from 'e2e-utils'
|
||||
|
||||
describe('react version', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
|
||||
it('should use react-server condition for app router server components pages', async () => {
|
||||
const rscPagesRoutes = ['/app/server', '/app/server-edge']
|
||||
|
||||
for (const route of rscPagesRoutes) {
|
||||
const $ = await next.render$(route)
|
||||
expect($('#react-export-condition').text()).toBe('react-server')
|
||||
expect($('#react-dom-export-condition').text()).toBe('react-server')
|
||||
}
|
||||
})
|
||||
|
||||
it('should use react-server condition for app router client components pages', async () => {
|
||||
const rscPagesRoutes = ['/app/client', '/app/client-edge']
|
||||
|
||||
for (const route of rscPagesRoutes) {
|
||||
const $ = await next.render$(route)
|
||||
expect($('#react-export-condition').text()).toBe('default')
|
||||
expect($('#react-dom-export-condition').text()).toBe('default')
|
||||
}
|
||||
})
|
||||
|
||||
it('should use react-server condition for app router custom routes', async () => {
|
||||
const customRoutes = ['/app/route', '/app/route-edge']
|
||||
|
||||
for (const route of customRoutes) {
|
||||
const res = await next.fetch(route)
|
||||
const json = await res.json()
|
||||
expect(json.react).toBe('react-server')
|
||||
expect(json.reactDom).toBe('react-server')
|
||||
}
|
||||
})
|
||||
|
||||
it('should use default react condition for pages router pages', async () => {
|
||||
const pagesRoutes = ['/pages-ssr', '/pages-ssr-edge']
|
||||
|
||||
for (const route of pagesRoutes) {
|
||||
const $ = await next.render$(route)
|
||||
expect($('#react-export-condition').text()).toBe('default')
|
||||
expect($('#react-dom-export-condition').text()).toBe('default')
|
||||
}
|
||||
})
|
||||
|
||||
it('should use default react condition for pages router apis', async () => {
|
||||
const pagesRoutes = [
|
||||
'/api/pages-api',
|
||||
'/api/pages-api-edge',
|
||||
'/api/pages-api-edge-url-dep',
|
||||
]
|
||||
|
||||
for (const route of pagesRoutes) {
|
||||
const res = await next.fetch(route)
|
||||
const json = await res.json()
|
||||
expect(json.react).toBe('default')
|
||||
expect(json.reactDom).toBe('default')
|
||||
}
|
||||
})
|
||||
})
|
7
test/e2e/rsc-layers-transform/app/layout.js
Normal file
7
test/e2e/rsc-layers-transform/app/layout.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default function Layout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
3
test/e2e/rsc-layers-transform/app/page.js
Normal file
3
test/e2e/rsc-layers-transform/app/page.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Page() {
|
||||
return 'page'
|
||||
}
|
10
test/e2e/rsc-layers-transform/instrumentation.js
Normal file
10
test/e2e/rsc-layers-transform/instrumentation.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import { textValue } from './lib/shared-module'
|
||||
|
||||
export async function register() {
|
||||
if (Object(React).useState) {
|
||||
throw new Error('instrumentation is not working correctly in server layer')
|
||||
}
|
||||
console.log('instrumentation:register')
|
||||
console.log('instrumentation:text:' + textValue)
|
||||
}
|
4
test/e2e/rsc-layers-transform/lib/shared-module.js
Normal file
4
test/e2e/rsc-layers-transform/lib/shared-module.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export const textValue = 'text-value'
|
||||
export const TestLink = Link
|
19
test/e2e/rsc-layers-transform/middleware.js
Normal file
19
test/e2e/rsc-layers-transform/middleware.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { textValue, TestLink } from './lib/shared-module'
|
||||
|
||||
export function middleware(request) {
|
||||
if (request.nextUrl.pathname === '/middleware') {
|
||||
let testLink
|
||||
try {
|
||||
testLink = TestLink.$$typeof.toString()
|
||||
} catch (e) {
|
||||
testLink = e.message
|
||||
}
|
||||
return Response.json({
|
||||
clientReference: testLink,
|
||||
textValue,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
5
test/e2e/rsc-layers-transform/next.config.js
Normal file
5
test/e2e/rsc-layers-transform/next.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
},
|
||||
}
|
22
test/e2e/rsc-layers-transform/rsc-layers-transform.test.ts
Normal file
22
test/e2e/rsc-layers-transform/rsc-layers-transform.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { nextTestSetup } from 'e2e-utils'
|
||||
|
||||
describe('rsc layers transform', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
})
|
||||
|
||||
it('should render installed react-server condition for middleware', async () => {
|
||||
const json = await next.fetch('/middleware').then((res) => res.json())
|
||||
|
||||
expect(json).toEqual({
|
||||
textValue: 'text-value',
|
||||
clientReference: 'Symbol(react.client.reference)',
|
||||
})
|
||||
})
|
||||
|
||||
it('should call instrumentation hook without errors', async () => {
|
||||
const output = next.cliOutput
|
||||
expect(output).toContain('instrumentation:register')
|
||||
expect(output).toContain('instrumentation:text:text-value')
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue