-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
11 changed files
with
138 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 0 additions & 1 deletion
1
test/development/acceptance-app/fixtures/rsc-build-errors/next.config.js
This file was deleted.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
test/development/acceptance-app/fixtures/rsc-runtime-errors/app/client/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
'use client' | ||
export default function page() { | ||
return 'page' | ||
} |
12 changes: 12 additions & 0 deletions
12
test/development/acceptance-app/fixtures/rsc-runtime-errors/app/layout.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export const metadata = { | ||
title: 'Next.js', | ||
description: 'Generated by Next.js', | ||
} | ||
|
||
export default function RootLayout({ children }) { | ||
return ( | ||
<html lang="en"> | ||
<body>{children}</body> | ||
</html> | ||
) | ||
} |
3 changes: 3 additions & 0 deletions
3
test/development/acceptance-app/fixtures/rsc-runtime-errors/app/server/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function page() { | ||
return 'page' | ||
} |
5 changes: 5 additions & 0 deletions
5
...pment/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { useState } from 'react' | ||
|
||
export function callClientApi() { | ||
return useState(0) | ||
} |
4 changes: 4 additions & 0 deletions
4
...t/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "client-package", | ||
"exports": "./index.js" | ||
} |
7 changes: 7 additions & 0 deletions
7
...pment/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use client' | ||
|
||
import { cookies } from 'next/headers' | ||
|
||
export function callServerApi() { | ||
return cookies() | ||
} |
4 changes: 4 additions & 0 deletions
4
...t/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"name": "server-package", | ||
"exports": "./index.js" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
test/development/acceptance-app/rsc-runtime-errors.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import path from 'path' | ||
import { outdent } from 'outdent' | ||
import { FileRef, createNextDescribe } from 'e2e-utils' | ||
import { | ||
check, | ||
getRedboxDescription, | ||
hasRedbox, | ||
shouldRunTurboDevTest, | ||
} from 'next-test-utils' | ||
|
||
createNextDescribe( | ||
'Error overlay - RSC runtime errors', | ||
{ | ||
files: new FileRef(path.join(__dirname, 'fixtures', 'rsc-runtime-errors')), | ||
packageJson: { | ||
scripts: { | ||
setup: 'cp -r ./node_modules_bak/* ./node_modules', | ||
build: 'yarn setup && next build', | ||
dev: `yarn setup && next ${ | ||
shouldRunTurboDevTest() ? 'dev --turbo' : 'dev' | ||
}`, | ||
start: 'next start', | ||
}, | ||
}, | ||
installCommand: 'yarn', | ||
startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start', | ||
}, | ||
({ next }) => { | ||
it('should show runtime errors if invalid client API from node_modules is executed', async () => { | ||
await next.patchFile( | ||
'app/server/page.js', | ||
outdent` | ||
import { callClientApi } from 'client-package' | ||
export default function Page() { | ||
callClientApi() | ||
return 'page' | ||
} | ||
` | ||
) | ||
|
||
const browser = await next.browser('/server') | ||
|
||
await check( | ||
async () => ((await hasRedbox(browser, true)) ? 'success' : 'fail'), | ||
/success/ | ||
) | ||
const errorDescription = await getRedboxDescription(browser) | ||
|
||
expect(errorDescription).toContain( | ||
`Error: useState only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component` | ||
) | ||
}) | ||
|
||
it('should show runtime errors if invalid server API from node_modules is executed', async () => { | ||
await next.patchFile( | ||
'app/client/page.js', | ||
outdent` | ||
'use client' | ||
import { callServerApi } from 'server-package' | ||
export default function Page() { | ||
callServerApi() | ||
return 'page' | ||
} | ||
` | ||
) | ||
|
||
const browser = await next.browser('/client') | ||
|
||
await check( | ||
async () => ((await hasRedbox(browser, true)) ? 'success' : 'fail'), | ||
/success/ | ||
) | ||
const errorDescription = await getRedboxDescription(browser) | ||
|
||
expect(errorDescription).toContain( | ||
`Error: Invariant: cookies() expects to have requestAsyncStorage, none available.` | ||
) | ||
}) | ||
} | ||
) |