Skip to content

Commit

Permalink
Emit build error for unknown cache kinds
Browse files Browse the repository at this point in the history
When a `"use cache"` directive with a custom cache kind is used, e.g.
`"use cache: custom"`, a cache handler with the same name must be
specified in the Next.js config:

```js
/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  experimental: {
    dynamicIO: true,
    cacheHandlers: {
      custom: require.resolve('path/to/custom/cache/handler'),
    },
  },
}

module.exports = nextConfig
```

If this is not the case, we emit a build error with an error message
that explains this requirement.

When we'll get a docs page for this experimental config, we will add
the usual "Read more: ..." hint as well.
  • Loading branch information
unstubbable committed Nov 14, 2024
1 parent 5cbefc3 commit 301baaf
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 15 deletions.
2 changes: 2 additions & 0 deletions crates/next-core/src/next_client/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub async fn get_next_client_transforms_rules(
}

let dynamic_io_enabled = *next_config.enable_dynamic_io().await?;
let cache_kinds = next_config.cache_kinds();
let mut is_app_dir = false;

match context_ty {
Expand All @@ -74,6 +75,7 @@ pub async fn get_next_client_transforms_rules(
ActionsTransform::Client,
enable_mdx_rs,
dynamic_io_enabled,
cache_kinds,
));
}
ClientContextType::Fallback | ClientContextType::Other => {}
Expand Down
18 changes: 18 additions & 0 deletions crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;

use anyhow::{bail, Context, Result};
use rustc_hash::FxHashSet;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value as JsonValue;
use turbo_tasks::{trace::TraceRawVcs, FxIndexMap, RcStr, ResolvedVc, TaskInput, Vc};
Expand Down Expand Up @@ -39,6 +40,9 @@ struct CustomRoutes {
#[turbo_tasks::value(transparent)]
pub struct ModularizeImports(FxIndexMap<String, ModularizeImportPackageConfig>);

#[turbo_tasks::value(transparent)]
pub struct CacheKinds(FxHashSet<String>);

#[turbo_tasks::value(serialization = "custom", eq = "manual")]
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -528,6 +532,7 @@ pub struct ExperimentalConfig {
after: Option<bool>,
amp: Option<serde_json::Value>,
app_document_preloading: Option<bool>,
cache_handlers: Option<FxIndexMap<String, String>>,
cache_life: Option<FxIndexMap<String, CacheLifeProfile>>,
case_sensitive_routes: Option<bool>,
cpus: Option<f64>,
Expand Down Expand Up @@ -1158,6 +1163,19 @@ impl NextConfig {
Vc::cell(self.experimental.dynamic_io.unwrap_or(false))
}

#[turbo_tasks::function]
pub fn cache_kinds(&self) -> Vc<CacheKinds> {
Vc::cell(
self.experimental
.cache_handlers
.clone()
.unwrap_or_default()
.keys()
.cloned()
.collect(),
)
}

#[turbo_tasks::function]
pub fn optimize_package_imports(&self) -> Vc<Vec<RcStr>> {
Vc::cell(
Expand Down
4 changes: 4 additions & 0 deletions crates/next-core/src/next_server/transforms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub async fn get_next_server_transforms_rules(
}

let dynamic_io_enabled = *next_config.enable_dynamic_io().await?;
let cache_kinds = next_config.cache_kinds();
let mut is_app_dir = false;

let is_server_components = match context_ty {
Expand Down Expand Up @@ -91,6 +92,7 @@ pub async fn get_next_server_transforms_rules(
ActionsTransform::Client,
mdx_rs,
dynamic_io_enabled,
cache_kinds,
));

is_app_dir = true;
Expand All @@ -102,6 +104,7 @@ pub async fn get_next_server_transforms_rules(
ActionsTransform::Server,
mdx_rs,
dynamic_io_enabled,
cache_kinds,
));

is_app_dir = true;
Expand All @@ -113,6 +116,7 @@ pub async fn get_next_server_transforms_rules(
ActionsTransform::Server,
mdx_rs,
dynamic_io_enabled,
cache_kinds,
));

is_app_dir = true;
Expand Down
5 changes: 5 additions & 0 deletions crates/next-core/src/next_shared/transforms/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use turbopack::module_options::{ModuleRule, ModuleRuleEffect};
use turbopack_ecmascript::{CustomTransformer, EcmascriptInputTransform, TransformContext};

use super::module_rule_match_js_no_url;
use crate::next_config::CacheKinds;

#[derive(Debug)]
pub enum ActionsTransform {
Expand All @@ -19,10 +20,12 @@ pub fn get_server_actions_transform_rule(
transform: ActionsTransform,
enable_mdx_rs: bool,
dynamic_io_enabled: bool,
cache_kinds: Vc<CacheKinds>,
) -> ModuleRule {
let transformer = EcmascriptInputTransform::Plugin(Vc::cell(Box::new(NextServerActions {
transform,
dynamic_io_enabled,
cache_kinds,
}) as _));
ModuleRule::new(
module_rule_match_js_no_url(enable_mdx_rs),
Expand All @@ -37,6 +40,7 @@ pub fn get_server_actions_transform_rule(
struct NextServerActions {
transform: ActionsTransform,
dynamic_io_enabled: bool,
cache_kinds: Vc<CacheKinds>,
}

#[async_trait]
Expand All @@ -49,6 +53,7 @@ impl CustomTransformer for NextServerActions {
is_react_server_layer: matches!(self.transform, ActionsTransform::Server),
dynamic_io_enabled: self.dynamic_io_enabled,
hash_salt: "".into(),
cache_kinds: self.cache_kinds.await?.clone_value(),
},
ctx.comments.clone(),
);
Expand Down
62 changes: 48 additions & 14 deletions crates/next-custom-transforms/src/transforms/server_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{

use hex::encode as hex_encode;
use indoc::formatdoc;
use rustc_hash::FxHashSet;
use serde::Deserialize;
use sha1::{Digest, Sha1};
use swc_core::{
Expand All @@ -29,6 +30,7 @@ pub struct Config {
pub is_react_server_layer: bool,
pub dynamic_io_enabled: bool,
pub hash_salt: String,
pub cache_kinds: FxHashSet<String>,
}

enum DirectiveLocation {
Expand Down Expand Up @@ -65,6 +67,10 @@ enum ServerActionsErrorKind {
directive: String,
expected_directive: String,
},
UnknownCacheKind {
span: Span,
cache_kind: String,
},
UseCacheWithoutDynamicIO {
span: Span,
directive: String,
Expand Down Expand Up @@ -311,7 +317,7 @@ impl<C: Comments> ServerActions<C> {
&mut is_action_fn,
&mut cache_kind,
&mut span,
self.config.dynamic_io_enabled,
&self.config,
);

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

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

Expand All @@ -2375,19 +2381,30 @@ 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 {
if !config.dynamic_io_enabled {
emit_error(ServerActionsErrorKind::UseCacheWithoutDynamicIO {
span: *span,
directive: value.to_string(),
});
}

*file_cache_kind = Some(if value == "use cache" {
"default".into()
if value == "use cache" {
*file_cache_kind = Some("default".into());
} else {
// Slice the value after "use cache: "
value.split_at("use cache: ".len()).1.into()
});
let cache_kind_str: String =
value.split_at("use cache: ".len()).1.into();

if !config.cache_kinds.contains(&cache_kind_str) {
emit_error(ServerActionsErrorKind::UnknownCacheKind {
span: *span,
cache_kind: cache_kind_str.clone(),
});
}

*file_cache_kind = Some(cache_kind_str)
}

*has_cache = true;
return false;
} else {
Expand Down Expand Up @@ -2488,7 +2505,7 @@ fn remove_server_directive_index_in_fn(
is_action_fn: &mut bool,
cache_kind: &mut Option<String>,
action_span: &mut Option<Span>,
dynamic_io_enabled: bool,
config: &Config,
) {
let mut is_directive = true;

Expand Down Expand Up @@ -2520,19 +2537,28 @@ fn remove_server_directive_index_in_fn(
});
} else if value == "use cache" || value.starts_with("use cache: ") {
if is_directive {
if !dynamic_io_enabled {
if !config.dynamic_io_enabled {
emit_error(ServerActionsErrorKind::UseCacheWithoutDynamicIO {
span: *span,
directive: value.to_string(),
});
}

*cache_kind = Some(if value == "use cache" {
"default".into()
if value == "use cache" {
*cache_kind = Some("default".into());
} else {
// Slice the value after "use cache: "
value.split_at("use cache: ".len()).1.into()
});
let cache_kind_str: String = value.split_at("use cache: ".len()).1.into();

if !config.cache_kinds.contains(&cache_kind_str) {
emit_error(ServerActionsErrorKind::UnknownCacheKind {
span: *span,
cache_kind: cache_kind_str.clone(),
});
}

*cache_kind = Some(cache_kind_str);
};
return false;
} else {
emit_error(ServerActionsErrorKind::MisplacedDirective {
Expand Down Expand Up @@ -2891,6 +2917,14 @@ fn emit_error(error_kind: ServerActionsErrorKind) {
"#
},
),
ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
span,
formatdoc! {
r#"
Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the experimental "cacheHandlers" object in your Next.js config.
"#
},
),
ServerActionsErrorKind::UseCacheWithoutDynamicIO { span, directive } => (
span,
formatdoc! {
Expand Down
6 changes: 5 additions & 1 deletion crates/next-custom-transforms/tests/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::{iter::FromIterator, path::PathBuf};

use next_custom_transforms::transforms::{
disallow_re_export_all_in_page::disallow_re_export_all_in_page,
Expand All @@ -11,6 +11,7 @@ use next_custom_transforms::transforms::{
},
strip_page_exports::{next_transform_strip_page_exports, ExportFilter},
};
use rustc_hash::FxHashSet;
use swc_core::{
common::{FileName, Mark},
ecma::{
Expand Down Expand Up @@ -187,6 +188,7 @@ fn react_server_actions_server_errors(input: PathBuf) {
is_react_server_layer: true,
dynamic_io_enabled: true,
hash_salt: "".into(),
cache_kinds: FxHashSet::default(),
},
tr.comments.as_ref().clone(),
),
Expand Down Expand Up @@ -226,6 +228,7 @@ fn react_server_actions_client_errors(input: PathBuf) {
is_react_server_layer: false,
dynamic_io_enabled: true,
hash_salt: "".into(),
cache_kinds: FxHashSet::default(),
},
tr.comments.as_ref().clone(),
),
Expand Down Expand Up @@ -283,6 +286,7 @@ fn use_cache_not_allowed(input: PathBuf) {
is_react_server_layer: true,
dynamic_io_enabled: false,
hash_salt: "".into(),
cache_kinds: FxHashSet::from_iter(["x".into()]),
},
tr.comments.as_ref().clone(),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use cache: x'

export async function foo() {
return 'data'
}
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 foo() {
return 'data';
});
Object.defineProperty($$RSC_SERVER_CACHE_0, "name", {
"value": "foo",
"writable": false
});
export var foo = registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
x Unknown cache kind "x". Please configure a cache handler for this kind in the experimental "cacheHandlers" object in your Next.js config.
|
,-[input.js:1:1]
1 | 'use cache: x'
: ^^^^^^^^^^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function foo() {
'use cache: x'

return 'data'
}
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 foo() {
return 'data';
});
Object.defineProperty($$RSC_SERVER_CACHE_0, "name", {
"value": "foo",
"writable": false
});
export var foo = registerServerReference($$RSC_SERVER_CACHE_0, "803128060c414d59f8552e4788b846c0d2b7f74743", null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
x Unknown cache kind "x". Please configure a cache handler for this kind in the experimental "cacheHandlers" object in your Next.js config.
|
,-[input.js:2:1]
1 | export async function foo() {
2 | 'use cache: x'
: ^^^^^^^^^^^^^^
`----
Loading

0 comments on commit 301baaf

Please sign in to comment.