Skip to content

Commit

Permalink
Emit build error when "use cache" is used without dynamicIO enabl…
Browse files Browse the repository at this point in the history
…ed (#72781)

Using a build error instead of a runtime error allows us to fail early,
and show a proper error source in the terminal and in the dev overlay.

<img width="777" alt="Screenshot 2024-11-13 at 22 19 45"
src="https://github.com/user-attachments/assets/d0ee3c69-71f5-4aa6-8c0a-879217f66930">
  • Loading branch information
unstubbable authored Nov 13, 2024
1 parent 5c45d58 commit 2c6b09d
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 9 deletions.
7 changes: 7 additions & 0 deletions crates/next-core/src/next_client/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ pub async fn get_next_client_transforms_rules(
rules.push(get_debug_fn_name_rule(enable_mdx_rs));
}

let dynamic_io_enabled = next_config
.experimental()
.await?
.dynamic_io
.unwrap_or(false);

let mut is_app_dir = false;

match context_ty {
Expand All @@ -72,6 +78,7 @@ pub async fn get_next_client_transforms_rules(
rules.push(get_server_actions_transform_rule(
ActionsTransform::Client,
enable_mdx_rs,
dynamic_io_enabled,
));
}
ClientContextType::Fallback | ClientContextType::Other => {}
Expand Down
9 changes: 9 additions & 0 deletions crates/next-core/src/next_server/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ pub async fn get_next_server_transforms_rules(
));
}

let dynamic_io_enabled = next_config
.experimental()
.await?
.dynamic_io
.unwrap_or(false);

let mut is_app_dir = false;

let is_server_components = match context_ty {
Expand Down Expand Up @@ -89,6 +95,7 @@ pub async fn get_next_server_transforms_rules(
rules.push(get_server_actions_transform_rule(
ActionsTransform::Client,
mdx_rs,
dynamic_io_enabled,
));

is_app_dir = true;
Expand All @@ -99,6 +106,7 @@ pub async fn get_next_server_transforms_rules(
rules.push(get_server_actions_transform_rule(
ActionsTransform::Server,
mdx_rs,
dynamic_io_enabled,
));

is_app_dir = true;
Expand All @@ -109,6 +117,7 @@ pub async fn get_next_server_transforms_rules(
rules.push(get_server_actions_transform_rule(
ActionsTransform::Server,
mdx_rs,
dynamic_io_enabled,
));

is_app_dir = true;
Expand Down
9 changes: 7 additions & 2 deletions crates/next-core/src/next_shared/transforms/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ pub enum ActionsTransform {
pub fn get_server_actions_transform_rule(
transform: ActionsTransform,
enable_mdx_rs: bool,
dynamic_io_enabled: bool,
) -> ModuleRule {
let transformer =
EcmascriptInputTransform::Plugin(Vc::cell(Box::new(NextServerActions { transform }) as _));
let transformer = EcmascriptInputTransform::Plugin(Vc::cell(Box::new(NextServerActions {
transform,
dynamic_io_enabled,
}) as _));
ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
vec![ModuleRuleEffect::ExtendEcmascriptTransforms {
Expand All @@ -33,6 +36,7 @@ pub fn get_server_actions_transform_rule(
#[derive(Debug)]
struct NextServerActions {
transform: ActionsTransform,
dynamic_io_enabled: bool,
}

#[async_trait]
Expand All @@ -43,6 +47,7 @@ impl CustomTransformer for NextServerActions {
&FileName::Real(ctx.file_path_str.into()),
Config {
is_react_server_layer: matches!(self.transform, ActionsTransform::Server),
dynamic_io_enabled: self.dynamic_io_enabled,
hash_salt: "".into(),
},
ctx.comments.clone(),
Expand Down
35 changes: 35 additions & 0 deletions crates/next-custom-transforms/src/transforms/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use swc_core::{
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Config {
pub is_react_server_layer: bool,
pub dynamic_io_enabled: bool,
pub hash_salt: String,
}

Expand Down Expand Up @@ -265,6 +266,7 @@ impl<C: Comments> ServerActions<C> {
&mut is_action_fn,
&mut cache_type,
&mut span,
self.config.dynamic_io_enabled,
);

if !self.config.is_react_server_layer {
Expand Down Expand Up @@ -1301,6 +1303,7 @@ impl<C: Comments> VisitMut for ServerActions<C> {
&mut self.in_cache_file,
&mut self.has_action,
&mut self.has_cache,
self.config.dynamic_io_enabled,
);

// If we're in a "use cache" file, collect all original IDs from export
Expand Down Expand Up @@ -2355,6 +2358,7 @@ fn remove_server_directive_index_in_module(
in_cache_file: &mut Option<String>,
has_action: &mut bool,
has_cache: &mut bool,
dynamic_io_enabled: bool,
) {
let mut is_directive = true;

Expand Down Expand Up @@ -2383,6 +2387,21 @@ fn remove_server_directive_index_in_module(
// `use cache` or `use cache: foo`
if value == "use cache" || value.starts_with("use cache: ") {
if is_directive {
if !dynamic_io_enabled {
HANDLER.with(|handler| {
handler
.struct_span_err(
*span,
format!(
"To use \"{value}\", please enable the experimental feature flag \"dynamicIO\" in your Next.js config.\n\n\
Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage\n"
)
.as_str(),
)
.emit();
})
}

*in_cache_file = Some(if value == "use cache" {
"default".into()
} else {
Expand Down Expand Up @@ -2518,6 +2537,7 @@ fn remove_server_directive_index_in_fn(
is_action_fn: &mut bool,
cache_type: &mut Option<String>,
action_span: &mut Option<Span>,
dynamic_io_enabled: bool,
) {
let mut is_directive = true;

Expand Down Expand Up @@ -2560,6 +2580,21 @@ fn remove_server_directive_index_in_fn(
});
} else if value == "use cache" || value.starts_with("use cache: ") {
if is_directive {
if !dynamic_io_enabled {
HANDLER.with(|handler| {
handler
.struct_span_err(
*span,
format!(
"To use \"{value}\", please enable the experimental feature flag \"dynamicIO\" in your Next.js config.\n\n\
Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage\n"
)
.as_str(),
)
.emit();
})
}

*cache_type = Some(if value == "use cache" {
"default".into()
} else {
Expand Down
45 changes: 43 additions & 2 deletions crates/next-custom-transforms/tests/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ fn react_server_actions_server_errors(input: PathBuf) {
FileName::Real(PathBuf::from("/app/item.js")).into(),
Config::WithOptions(Options {
is_react_server_layer: true,
dynamic_io_enabled: false,
dynamic_io_enabled: true,
}),
tr.comments.as_ref().clone(),
None,
Expand All @@ -185,6 +185,7 @@ fn react_server_actions_server_errors(input: PathBuf) {
&FileName::Real("/app/item.js".into()),
server_actions::Config {
is_react_server_layer: true,
dynamic_io_enabled: true,
hash_salt: "".into(),
},
tr.comments.as_ref().clone(),
Expand Down Expand Up @@ -214,7 +215,7 @@ fn react_server_actions_client_errors(input: PathBuf) {
FileName::Real(PathBuf::from("/app/item.js")).into(),
Config::WithOptions(Options {
is_react_server_layer: false,
dynamic_io_enabled: false,
dynamic_io_enabled: true,
}),
tr.comments.as_ref().clone(),
None,
Expand All @@ -223,6 +224,7 @@ fn react_server_actions_client_errors(input: PathBuf) {
&FileName::Real("/app/item.js".into()),
server_actions::Config {
is_react_server_layer: false,
dynamic_io_enabled: true,
hash_salt: "".into(),
},
tr.comments.as_ref().clone(),
Expand Down Expand Up @@ -256,3 +258,42 @@ fn next_transform_strip_page_exports_errors(input: PathBuf) {
},
);
}

#[fixture("tests/errors/use-cache-not-allowed/**/input.js")]
fn use_cache_not_allowed(input: PathBuf) {
use next_custom_transforms::transforms::react_server_components::{Config, Options};
let output = input.parent().unwrap().join("output.js");
test_fixture(
syntax(),
&|tr| {
(
resolver(Mark::new(), Mark::new(), false),
server_components(
FileName::Real(PathBuf::from("/app/item.js")).into(),
Config::WithOptions(Options {
is_react_server_layer: true,
dynamic_io_enabled: false,
}),
tr.comments.as_ref().clone(),
None,
),
server_actions(
&FileName::Real("/app/item.js".into()),
server_actions::Config {
is_react_server_layer: true,
dynamic_io_enabled: false,
hash_salt: "".into(),
},
tr.comments.as_ref().clone(),
),
)
},
&input,
&output,
FixtureTestConfig {
allow_error: true,
module: Some(true),
..Default::default()
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use cache'

export default async function Page() {
return <p>hello world</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference";
import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption";
import { cache as $$cache__ } from "private-next-rsc-cache-wrapper";
export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function Page() {
return <p>hello world</p>;
});
Object.defineProperty($$RSC_SERVER_CACHE_0, "name", {
"value": "Page",
"writable": false
});
export default registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
x To use "use cache", please enable the experimental feature flag "dynamicIO" in your Next.js config.
|
| Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
|
,-[input.js:1:1]
1 | 'use cache'
: ^^^^^^^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default async function Page() {
'use cache: x'

return <p>hello world</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* __next_internal_action_entry_do_not_use__ {"803128060c414d59f8552e4788b846c0d2b7f74743":"$$RSC_SERVER_CACHE_0"} */ import { registerServerReference } from "private-next-rsc-server-reference";
import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption";
import { cache as $$cache__ } from "private-next-rsc-cache-wrapper";
export var $$RSC_SERVER_CACHE_0 = $$cache__("x", "803128060c414d59f8552e4788b846c0d2b7f74743", 0, async function Page() {
return <p>hello world</p>;
});
Object.defineProperty($$RSC_SERVER_CACHE_0, "name", {
"value": "Page",
"writable": false
});
export default registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
x To use "use cache: x", please enable the experimental feature flag "dynamicIO" in your Next.js config.
|
| Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
|
,-[input.js:2:1]
1 | export default async function Page() {
2 | 'use cache: x'
: ^^^^^^^^^^^^^^
`----
3 changes: 3 additions & 0 deletions crates/next-custom-transforms/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ fn server_actions_server_fixture(input: PathBuf) {
&FileName::Real("/app/item.js".into()),
server_actions::Config {
is_react_server_layer: true,
dynamic_io_enabled: true,
hash_salt: "".into(),
},
_tr.comments.as_ref().clone(),
Expand Down Expand Up @@ -445,6 +446,7 @@ fn next_font_with_directive_fixture(input: PathBuf) {
&FileName::Real("/app/test.tsx".into()),
server_actions::Config {
is_react_server_layer: true,
dynamic_io_enabled: true,
hash_salt: "".into(),
},
_tr.comments.as_ref().clone(),
Expand All @@ -469,6 +471,7 @@ fn server_actions_client_fixture(input: PathBuf) {
&FileName::Real("/app/item.js".into()),
server_actions::Config {
is_react_server_layer: false,
dynamic_io_enabled: true,
hash_salt: "".into(),
},
_tr.comments.as_ref().clone(),
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/build/swc/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ function getBaseSWCOptions({
isAppRouterPagesLayer && !jest
? {
isReactServerLayer,
dynamicIoEnabled: isDynamicIo,
hashSalt: serverReferenceHashSalt,
}
: undefined,
Expand Down
5 changes: 0 additions & 5 deletions packages/next/src/server/use-cache/use-cache-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,6 @@ export function cache(
boundArgsLength: number,
fn: any
) {
if (!process.env.__NEXT_DYNAMIC_IO) {
throw new Error(
'"use cache" is only available with the experimental.dynamicIO config.'
)
}
for (const [key, value] of Object.entries(
_globalThis.__nextCacheHandlers || {}
)) {
Expand Down
8 changes: 8 additions & 0 deletions test/e2e/app-dir/use-cache-without-dynamic-io/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react'
export default function Root({ children }: { children: ReactNode }) {
return (
<html>
<body>{children}</body>
</html>
)
}
5 changes: 5 additions & 0 deletions test/e2e/app-dir/use-cache-without-dynamic-io/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use cache'

export default async function Page() {
return <p>hello world</p>
}
6 changes: 6 additions & 0 deletions test/e2e/app-dir/use-cache-without-dynamic-io/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {}

module.exports = nextConfig
Loading

0 comments on commit 2c6b09d

Please sign in to comment.