Skip to content

Commit 86f28a3

Browse files
committed
[turbopack] fully prevent usage of next/root-params outside of server components
1 parent eb345e4 commit 86f28a3

File tree

4 files changed

+146
-24
lines changed

4 files changed

+146
-24
lines changed

crates/next-core/src/next_client/context.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::iter::once;
22

33
use anyhow::Result;
4+
use either::Either;
45
use turbo_rcstr::{RcStr, rcstr};
56
use turbo_tasks::{FxIndexMap, OptionVcExt, ResolvedVc, TaskInput, Vc};
67
use turbo_tasks_env::EnvMap;
@@ -47,6 +48,7 @@ use crate::{
4748
get_next_client_fallback_import_map, get_next_client_import_map,
4849
get_next_client_resolved_map,
4950
},
51+
next_root_params::get_invalid_next_root_params_resolve_plugin,
5052
next_shared::{
5153
resolve::{
5254
ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin,
@@ -172,7 +174,7 @@ pub async fn get_client_resolve_options_context(
172174
.to_resolved()
173175
.await?;
174176
let custom_conditions = vec![mode.await?.condition().into()];
175-
let resolve_options_context = ResolveOptionsContext {
177+
let mut resolve_options_context = ResolveOptionsContext {
176178
enable_node_modules: Some(project_path.root().to_resolved().await?),
177179
custom_conditions,
178180
import_map: Some(next_client_import_map),
@@ -205,6 +207,22 @@ pub async fn get_client_resolve_options_context(
205207
..Default::default()
206208
};
207209

210+
if let Some(invalid_next_root_params_resolve_plugin) =
211+
get_invalid_next_root_params_resolve_plugin(
212+
*next_config.enable_root_params().await?,
213+
Either::Right(ty),
214+
project_path,
215+
)
216+
{
217+
resolve_options_context
218+
.before_resolve_plugins
219+
.push(ResolvedVc::upcast(
220+
invalid_next_root_params_resolve_plugin
221+
.to_resolved()
222+
.await?,
223+
))
224+
}
225+
208226
Ok(ResolveOptionsContext {
209227
enable_typescript: true,
210228
enable_react: true,

crates/next-core/src/next_import_map.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use crate::{
3030
GOOGLE_FONTS_INTERNAL_PREFIX, NextFontGoogleCssModuleReplacer,
3131
NextFontGoogleFontFileReplacer, NextFontGoogleReplacer,
3232
},
33-
next_root_params::get_next_root_params_mapping,
33+
next_root_params::insert_next_root_params_mapping,
3434
next_server::context::ServerContextType,
3535
util::NextRuntime,
3636
};
@@ -682,19 +682,7 @@ async fn insert_next_server_special_aliases(
682682
}
683683
}
684684

685-
if *next_config.enable_root_params().await? {
686-
match ty {
687-
ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => {
688-
import_map.insert_exact_alias(
689-
"next/root-params",
690-
get_next_root_params_mapping(collected_root_params)
691-
.to_resolved()
692-
.await?,
693-
);
694-
}
695-
_ => {}
696-
}
697-
}
685+
insert_next_root_params_mapping(import_map, ty, collected_root_params).await?;
698686

699687
import_map.insert_exact_alias(
700688
"@vercel/og",
Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,120 @@
11
use std::iter;
22

33
use anyhow::Result;
4+
use either::Either;
45
use indoc::formatdoc;
56
use itertools::Itertools;
67
use turbo_tasks::{ResolvedVc, Vc};
7-
use turbo_tasks_fs::FileContent;
8+
use turbo_tasks_fs::{FileContent, FileSystemPath};
89
use turbopack_core::{
910
asset::AssetContent,
10-
resolve::{ResolveResult, options::ImportMapping},
11+
resolve::{
12+
ResolveResult,
13+
options::{ImportMap, ImportMapping},
14+
},
1115
virtual_source::VirtualSource,
1216
};
1317

14-
use crate::{app_structure::CollectedRootParams, embed_js::next_js_file_path};
18+
use crate::{
19+
app_structure::CollectedRootParams,
20+
embed_js::next_js_file_path,
21+
next_client::ClientContextType,
22+
next_server::ServerContextType,
23+
next_shared::resolve::{InvalidImportPattern, InvalidImportResolvePlugin},
24+
};
25+
26+
pub fn get_invalid_next_root_params_resolve_plugin(
27+
is_root_params_enabled: bool,
28+
ty: Either<ServerContextType, ClientContextType>,
29+
root: ResolvedVc<FileSystemPath>,
30+
) -> Option<Vc<InvalidImportResolvePlugin>> {
31+
// Hard-error if the flag is not enabled, regardless of if we're on the server or on the client.
32+
if !is_root_params_enabled {
33+
return Some(InvalidImportResolvePlugin::new(
34+
*root,
35+
InvalidImportPattern::Glob("next/root-params".into()),
36+
vec![
37+
"'next/root-params' can only be imported when `experimental.rootParams` is \
38+
enabled."
39+
.into(),
40+
],
41+
));
42+
}
43+
match ty {
44+
Either::Left(server_ty) => match server_ty {
45+
ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => {
46+
// Valid usage. We'll map this request to generated code in
47+
// `insert_next_root_params_mapping`.
48+
None
49+
}
50+
ServerContextType::PagesData { .. }
51+
| ServerContextType::PagesApi { .. }
52+
| ServerContextType::Instrumentation { .. }
53+
| ServerContextType::Middleware { .. } => {
54+
// There's no sensible way to use root params outside of the app directory.
55+
// TODO: make sure this error is consistent with webpack
56+
Some(InvalidImportResolvePlugin::new(
57+
*root,
58+
InvalidImportPattern::Glob("next/root-params".into()),
59+
vec!["'next/root-params' can only be used inside the App Directory.".into()],
60+
))
61+
}
62+
_ => {
63+
// In general, the compiler should prevent importing 'next/root-params' from client
64+
// modules, but it doesn't catch everything. If an import slips
65+
// through our validation, make it error.
66+
Some(InvalidImportResolvePlugin::new(
67+
*root,
68+
InvalidImportPattern::Glob("next/root-params".into()),
69+
vec![
70+
"'next/root-params' cannot be imported from a Client Component module. It \
71+
should only be used from a Server Component."
72+
.into(),
73+
],
74+
))
75+
}
76+
},
77+
Either::Right(_) => {
78+
// In general, the compiler should prevent importing 'next/root-params' from client
79+
// modules, but it doesn't catch everything. If an import slips
80+
// through our validation, make it error.
81+
Some(InvalidImportResolvePlugin::new(
82+
*root,
83+
InvalidImportPattern::Glob("next/root-params".into()),
84+
vec![
85+
"'next/root-params' cannot be imported from a Client Component module. It \
86+
should only be used from a Server Component."
87+
.into(),
88+
],
89+
))
90+
}
91+
}
92+
}
93+
94+
pub async fn insert_next_root_params_mapping(
95+
import_map: &mut ImportMap,
96+
ty: ServerContextType,
97+
collected_root_params: Option<Vc<CollectedRootParams>>,
98+
) -> Result<()> {
99+
match ty {
100+
ServerContextType::AppRSC { .. } | ServerContextType::AppRoute { .. } => {
101+
import_map.insert_exact_alias(
102+
"next/root-params",
103+
get_next_root_params_mapping(collected_root_params)
104+
.to_resolved()
105+
.await?,
106+
);
107+
}
108+
_ => {
109+
// `get_invalid_next_root_params_resolve_plugin` already triggered an error for other
110+
// contexts, so we can ignore them here.
111+
}
112+
};
113+
Ok(())
114+
}
15115

16116
#[turbo_tasks::function]
17-
pub async fn get_next_root_params_mapping(
117+
async fn get_next_root_params_mapping(
18118
collected_root_params: Option<Vc<CollectedRootParams>>,
19119
) -> Result<Vc<ImportMapping>> {
20120
let module_content = match collected_root_params {
@@ -30,24 +130,24 @@ pub async fn get_next_root_params_mapping(
30130
.chain(collected_root_params.iter().map(|param_name| {
31131
formatdoc!(
32132
r#"
33-
export function {param_name}() {{
34-
return getRootParam('{param_name}');
133+
export function {PARAM_NAME}() {{
134+
return getRootParam('{PARAM_NAME}');
35135
}}
36136
"#,
37-
param_name = param_name,
137+
PARAM_NAME = param_name,
38138
)
39139
}))
40140
.join("\n")
41141
}
42142
};
43143

44-
let js_asset = VirtualSource::new(
144+
let source = VirtualSource::new(
45145
next_js_file_path("root-params.js".into()),
46146
AssetContent::file(FileContent::Content(module_content.into()).cell()),
47147
)
48148
.to_resolved()
49149
.await?;
50150

51-
let mapping = ImportMapping::Direct(ResolveResult::source(ResolvedVc::upcast(js_asset)));
151+
let mapping = ImportMapping::Direct(ResolveResult::source(ResolvedVc::upcast(source)));
52152
Ok(mapping.cell())
53153
}

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::iter::once;
22

33
use anyhow::{Result, bail};
4+
use either::Either;
45
use turbo_rcstr::{RcStr, rcstr};
56
use turbo_tasks::{FxIndexMap, OptionVcExt, ResolvedVc, TaskInput, Vc};
67
use turbo_tasks_env::{EnvMap, ProcessEnv};
@@ -51,6 +52,7 @@ use crate::{
5152
next_config::NextConfig,
5253
next_font::local::NextFontLocalResolvePlugin,
5354
next_import_map::get_next_server_import_map,
55+
next_root_params::get_invalid_next_root_params_resolve_plugin,
5456
next_server::resolve::ExternalPredicate,
5557
next_shared::{
5658
resolve::{
@@ -316,6 +318,20 @@ pub async fn get_server_resolve_options_context(
316318
}
317319
}
318320

321+
if let Some(invalid_next_root_params_resolve_plugin) =
322+
get_invalid_next_root_params_resolve_plugin(
323+
*next_config.enable_root_params().await?,
324+
Either::Left(ty),
325+
project_path,
326+
)
327+
{
328+
before_resolve_plugins.push(ResolvedVc::upcast(
329+
invalid_next_root_params_resolve_plugin
330+
.to_resolved()
331+
.await?,
332+
))
333+
}
334+
319335
let resolve_options_context = ResolveOptionsContext {
320336
enable_node_modules: Some(root_dir),
321337
enable_node_externals: true,

0 commit comments

Comments
 (0)