Skip to content

Commit 10b4ef9

Browse files
SukkaWhuozhi
authored andcommitted
refactor: rewrite config schema in zod (#56383)
The PR supersedes the #53150, which is way too outdated, has way too many conflicts, and also heavily relies on GitHub Copilot (which makes the progress slow and tedious). The PR uses [`json-schema-to-zod`](https://github.com/StefanTerdell/json-schema-to-zod) (instead of the GitHub Copilot) to generate the zod schema, and manually replaces all generated `z.customRefine` with my hand-written zod schema. TODO: - [x] Convert schema - [x] Reduce `z.any()` usage - [x] Create human-readable errors from the `ZodError` - [x] Update test cases to reflect the latest error message ----- The benefit of using zod over ajv: - Easier maintenance: zod schema is straightforward to compose. - Better typescript support: config schema now strictly reflects the `NextConfig` type. - Smaller installation size: by replacing `ajv` and `@segment/ajv-human-errors` w/ `zod`, I am able to reduce installation size by 114 KiB. - Better Extension: the zod error message is easy to customize. ----- In the previous PR #56083, @feedthejim replaces `zod` w/ `superstruct`. `superstruct` is lightweight and fast, which makes it perfect for creating simple schemas for RSC payload. But, this also means `superstruct` has its limitations compared to `zod`: - `superstruct`'s syntax is different, and some utilities's usage is counter-intuitive: - `z.array(z.string()).gt(1)` vs `s.size(s.array(s.string()), 1)` - `z.numer().gt(1)` vs `s.size(s.number(), 1)`, `s.min(s.number(), 1)` - `z.boolean().optional().nullable()` vs `s.nullable(s.optional(z.boolean()))` - `superstruct` has weaker TypeScript support and worse DX compared to `zod` when composing huge schema: - `zod.ZodType + z.object()` can provide a more detailed type mismatch message on which specific property is the culprit, while `Describe + s.object()` provides almost no information at all. - `zod`'s schema is more powerful - `z.function()` supports `z.args()` and `z.returns()`, while `superstruct` only has `s.func()` - zod also has Promise type `z.promise()` and intersection type `z.and()` - `superstruct`'s error is harder to parse compared to `zod`'s `ZodError` So in the PR, I re-introduced `zod` for `next.config.js` validation.
1 parent 7e1f311 commit 10b4ef9

File tree

25 files changed

+836
-1034
lines changed

25 files changed

+836
-1034
lines changed

packages/next-swc/crates/core/src/react_server_components.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ impl<C: Comments> ReactServerComponents<C> {
344344
}
345345

346346
fn assert_server_graph(&self, imports: &[ModuleImports], module: &Module) {
347+
// If the
348+
if self.is_from_node_modules(&self.filepath) {
349+
return;
350+
}
347351
for import in imports {
348352
let source = import.source.0.clone();
349353
if self.invalid_server_imports.contains(&source) {
@@ -391,6 +395,9 @@ impl<C: Comments> ReactServerComponents<C> {
391395
}
392396

393397
fn assert_server_filename(&self, module: &Module) {
398+
if self.is_from_node_modules(&self.filepath) {
399+
return;
400+
}
394401
let is_error_file = Regex::new(r"[\\/]error\.(ts|js)x?$")
395402
.unwrap()
396403
.is_match(&self.filepath);
@@ -416,6 +423,9 @@ impl<C: Comments> ReactServerComponents<C> {
416423
}
417424

418425
fn assert_client_graph(&self, imports: &[ModuleImports]) {
426+
if self.is_from_node_modules(&self.filepath) {
427+
return;
428+
}
419429
for import in imports {
420430
let source = import.source.0.clone();
421431
if self.invalid_client_imports.contains(&source) {
@@ -432,6 +442,9 @@ impl<C: Comments> ReactServerComponents<C> {
432442
}
433443

434444
fn assert_invalid_api(&self, module: &Module, is_client_entry: bool) {
445+
if self.is_from_node_modules(&self.filepath) {
446+
return;
447+
}
435448
let is_layout_or_page = Regex::new(r"[\\/](page|layout)\.(ts|js)x?$")
436449
.unwrap()
437450
.is_match(&self.filepath);
@@ -562,6 +575,12 @@ impl<C: Comments> ReactServerComponents<C> {
562575
},
563576
);
564577
}
578+
579+
fn is_from_node_modules(&self, filepath: &str) -> bool {
580+
Regex::new(r"[\\/]node_modules[\\/]")
581+
.unwrap()
582+
.is_match(filepath)
583+
}
565584
}
566585

567586
pub fn server_components<C: Comments>(

packages/next/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@
151151
"@next/swc": "13.5.5-canary.2",
152152
"@opentelemetry/api": "1.4.1",
153153
"@playwright/test": "^1.35.1",
154-
"@segment/ajv-human-errors": "2.1.2",
155154
"@taskr/clear": "1.1.0",
156155
"@taskr/esnext": "1.1.0",
157156
"@types/amphtml-validator": "1.0.0",
@@ -194,7 +193,6 @@
194193
"@vercel/nft": "0.22.6",
195194
"@vercel/turbopack-ecmascript-runtime": "https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-231002.1",
196195
"acorn": "8.5.0",
197-
"ajv": "8.11.0",
198196
"amphtml-validator": "1.0.35",
199197
"anser": "1.4.9",
200198
"arg": "4.1.0",
@@ -314,7 +312,8 @@
314312
"webpack": "5.86.0",
315313
"webpack-sources1": "npm:[email protected]",
316314
"webpack-sources3": "npm:[email protected]",
317-
"ws": "8.2.3"
315+
"ws": "8.2.3",
316+
"zod": "3.22.3"
318317
},
319318
"engines": {
320319
"node": ">=16.14.0"

packages/next/src/compiled/sass-loader/cjs.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Colin McDonnell
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/next/src/compiled/zod/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"zod","main":"index.js","author":"Colin McDonnell <[email protected]>","license":"MIT"}

0 commit comments

Comments
 (0)