Skip to content

Commit

Permalink
Update page config APIs (#41580)
Browse files Browse the repository at this point in the history
This PR updates app configurations from `export const config = { ... }`
to be directly exported.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have a helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)

Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
shuding and ijjk committed Oct 20, 2022
1 parent d261e7c commit bad909e
Show file tree
Hide file tree
Showing 37 changed files with 112 additions and 131 deletions.
71 changes: 45 additions & 26 deletions packages/next/build/analysis/get-page-static-info.ts
Expand Up @@ -48,22 +48,39 @@ export function getRSCModuleType(source: string): RSCModuleType {
* requires a runtime to be specified. Those are:
* - Modules with `export function getStaticProps | getServerSideProps`
* - Modules with `export { getStaticProps | getServerSideProps } <from ...>`
* - Modules with `export const runtime = ...`
*/
export function checkExports(swcAST: any): { ssr: boolean; ssg: boolean } {
function checkExports(swcAST: any): {
ssr: boolean
ssg: boolean
runtime?: string
} {
if (Array.isArray(swcAST?.body)) {
try {
let runtime: string | undefined
let ssr: boolean = false
let ssg: boolean = false

for (const node of swcAST.body) {
if (
node.type === 'ExportDeclaration' &&
node.declaration?.type === 'VariableDeclaration'
) {
const id = node.declaration?.declarations[0]?.id.value
if (id === 'runtime') {
runtime = node.declaration?.declarations[0]?.init.value
}
}

if (
node.type === 'ExportDeclaration' &&
node.declaration?.type === 'FunctionDeclaration' &&
['getStaticProps', 'getServerSideProps'].includes(
node.declaration.identifier?.value
)
) {
return {
ssg: node.declaration.identifier.value === 'getStaticProps',
ssr: node.declaration.identifier.value === 'getServerSideProps',
}
ssg = node.declaration.identifier.value === 'getStaticProps'
ssr = node.declaration.identifier.value === 'getServerSideProps'
}

if (
Expand All @@ -72,10 +89,8 @@ export function checkExports(swcAST: any): { ssr: boolean; ssg: boolean } {
) {
const id = node.declaration?.declarations[0]?.id.value
if (['getStaticProps', 'getServerSideProps'].includes(id)) {
return {
ssg: id === 'getStaticProps',
ssr: id === 'getServerSideProps',
}
ssg = id === 'getStaticProps'
ssr = id === 'getServerSideProps'
}
}

Expand All @@ -87,16 +102,14 @@ export function checkExports(swcAST: any): { ssr: boolean; ssg: boolean } {
specifier.orig?.value
)

return {
ssg: values.some((value: any) =>
['getStaticProps'].includes(value)
),
ssr: values.some((value: any) =>
['getServerSideProps'].includes(value)
),
}
ssg = values.some((value: any) => ['getStaticProps'].includes(value))
ssr = values.some((value: any) =>
['getServerSideProps'].includes(value)
)
}
}

return { ssr, ssg, runtime }
} catch (err) {}
}

Expand Down Expand Up @@ -270,7 +283,7 @@ export async function getPageStaticInfo(params: {
)
) {
const swcAST = await parseModule(pageFilePath, fileContent)
const { ssg, ssr } = checkExports(swcAST)
const { ssg, ssr, runtime } = checkExports(swcAST)
const rsc = getRSCModuleType(fileContent)

// default / failsafe value for config
Expand All @@ -284,12 +297,18 @@ export async function getPageStaticInfo(params: {
// `export config` doesn't exist, or other unknown error throw by swc, silence them
}

// Currently, we use `export const config = { runtime: '...' }` to specify the page runtime.
// But in the new app directory, we prefer to use `export const runtime = '...'`
// and deprecate the old way. To prevent breaking changes for `pages`, we use the exported config
// as the fallback value.
let resolvedRuntime = runtime || config.runtime

if (
typeof config.runtime !== 'undefined' &&
!isServerRuntime(config.runtime)
typeof resolvedRuntime !== 'undefined' &&
!isServerRuntime(resolvedRuntime)
) {
const options = Object.values(SERVER_RUNTIME).join(', ')
if (typeof config.runtime !== 'string') {
if (typeof resolvedRuntime !== 'string') {
Log.error(
`The \`runtime\` config must be a string. Please leave it empty or choose one of: ${options}`
)
Expand All @@ -303,14 +322,14 @@ export async function getPageStaticInfo(params: {
}
}

let runtime =
SERVER_RUNTIME.edge === config?.runtime
resolvedRuntime =
SERVER_RUNTIME.edge === resolvedRuntime
? SERVER_RUNTIME.edge
: ssr || ssg
? config?.runtime || nextConfig.experimental?.runtime
? resolvedRuntime || nextConfig.experimental?.runtime
: undefined

if (runtime === SERVER_RUNTIME.edge) {
if (resolvedRuntime === SERVER_RUNTIME.edge) {
warnAboutExperimentalEdgeApiFunctions()
}

Expand All @@ -325,7 +344,7 @@ export async function getPageStaticInfo(params: {
ssg,
rsc,
...(middlewareConfig && { middleware: middlewareConfig }),
...(runtime && { runtime }),
...(resolvedRuntime && { runtime: resolvedRuntime }),
}
}

Expand Down
33 changes: 31 additions & 2 deletions packages/next/build/utils.ts
Expand Up @@ -1044,13 +1044,41 @@ export type AppConfig = {
preferredRegion?: string
}
type GenerateParams = Array<{
config: AppConfig
config?: AppConfig
segmentPath: string
getStaticPaths?: GetStaticPaths
generateStaticParams?: any
isLayout?: boolean
}>

export const collectAppConfig = (mod: any): AppConfig | undefined => {
let hasConfig = false

const config: AppConfig = {}
if (typeof mod?.revalidate !== 'undefined') {
config.revalidate = mod.revalidate
hasConfig = true
}
if (typeof mod?.dynamicParams !== 'undefined') {
config.dynamicParams = mod.dynamicParams
hasConfig = true
}
if (typeof mod?.dynamic !== 'undefined') {
config.dynamic = mod.dynamic
hasConfig = true
}
if (typeof mod?.fetchCache !== 'undefined') {
config.fetchCache = mod.fetchCache
hasConfig = true
}
if (typeof mod?.preferredRegion !== 'undefined') {
config.preferredRegion = mod.preferredRegion
hasConfig = true
}

return hasConfig ? config : undefined
}

export const collectGenerateParams = async (
segment: any,
parentSegments: string[] = [],
Expand All @@ -1059,13 +1087,14 @@ export const collectGenerateParams = async (
if (!Array.isArray(segment)) return generateParams
const isLayout = !!segment[2]?.layout
const mod = await (isLayout ? segment[2]?.layout?.() : segment[2]?.page?.())
const config = collectAppConfig(mod)

const result = {
isLayout,
segmentPath: `/${parentSegments.join('/')}${
segment[0] && parentSegments.length > 0 ? '/' : ''
}${segment[0]}`,
config: mod?.config,
config,
getStaticPaths: mod?.getStaticPaths,
generateStaticParams: mod?.generateStaticParams,
}
Expand Down
7 changes: 2 additions & 5 deletions packages/next/build/webpack/plugins/flight-types-plugin.ts
Expand Up @@ -31,17 +31,14 @@ interface IEntry {
? `default: (props: { children: React.ReactNode; params?: any }) => React.ReactNode | null`
: `default: (props: { params?: any }) => React.ReactNode | null`
}
config?: {}
generateStaticParams?: (params?:any) => Promise<any[]>
config?: {
// TODO: remove revalidate here
revalidate?: number | boolean
${options.type === 'page' ? 'runtime?: string' : ''}
}
revalidate?: RevalidateRange<TEntry> | false
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
dynamicParams?: boolean
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
preferredRegion?: 'auto' | 'home' | 'edge'
${options.type === 'page' ? "runtime?: 'nodejs' | 'experimental-edge'" : ''}
}
// =============
Expand Down
4 changes: 2 additions & 2 deletions packages/next/server/app-render.tsx
Expand Up @@ -947,8 +947,8 @@ export async function renderToHTMLOrFlight(
? await page()
: undefined

if (layoutOrPageMod?.config) {
defaultRevalidate = layoutOrPageMod.config.revalidate
if (typeof layoutOrPageMod?.revalidate !== 'undefined') {
defaultRevalidate = layoutOrPageMod.revalidate

if (isStaticGeneration && defaultRevalidate === 0) {
const { DynamicServerError } =
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/app-edge/app/app-edge/page.tsx
@@ -1,4 +1,4 @@
export default function Page() {
return <p>app-edge-ssr</p>
}
export const config = { runtime: 'experimental-edge' }
export const runtime = 'experimental-edge'
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-prefetch/app/dashboard/page.js
@@ -1,8 +1,6 @@
import { experimental_use as use } from 'react'

export const config = {
revalidate: 0,
}
export const revalidate = 0

async function getData() {
await new Promise((resolve) => setTimeout(resolve, 3000))
Expand Down
@@ -1,8 +1,6 @@
import { experimental_use as use } from 'react'

export const config = {
revalidate: 1,
}
export const revalidate = 1

async function getData() {
return {
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-rendering/app/ssr-only/layout.js
@@ -1,8 +1,6 @@
import { experimental_use as use } from 'react'

export const config = {
revalidate: 0,
}
export const revalidate = 0

async function getData() {
return {
Expand Down
@@ -1,8 +1,6 @@
import { experimental_use as use } from 'react'

export const config = {
revalidate: false,
}
export const revalidate = false

async function getData() {
return {
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-rendering/app/static-only/slow/page.js
@@ -1,8 +1,6 @@
import { experimental_use as use } from 'react'

export const config = {
revalidate: false,
}
export const revalidate = false

async function getData() {
await new Promise((resolve) => setTimeout(resolve, 5000))
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-static/app/(new)/custom/page.js
@@ -1,6 +1,4 @@
export const config = {
revalidate: 0,
}
export const revalidate = 0

export default function Page() {
return <p>new root ssr</p>
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-static/app/blog/[author]/[slug]/page.js
@@ -1,6 +1,4 @@
export const config = {
dynamicParams: true,
}
export const dynamicParams = true

export default function Page({ params }) {
return (
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-static/app/blog/[author]/page.js
@@ -1,8 +1,6 @@
import Link from 'next/link'

export const config = {
dynamicParams: false,
}
export const dynamicParams = false

export default function Page({ params }) {
return (
Expand Down
@@ -1,6 +1,4 @@
export const config = {
revalidate: 0,
}
export const revalidate = 0

export default function Page({ params }) {
return (
Expand Down
Expand Up @@ -20,6 +20,4 @@ export default function Page() {
}

// TODO-APP: remove revalidate config once next.revalidate is supported
export const config = {
revalidate: 0,
}
export const revalidate = 0
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app-static/app/ssr-forced/page.js
@@ -1,6 +1,4 @@
export const config = {
revalidate: 0,
}
export const revalidate = 0

export default function Page() {
return (
Expand Down
4 changes: 0 additions & 4 deletions test/e2e/app-dir/app-typescript/app/layout.tsx
@@ -1,9 +1,5 @@
/* eslint-disable */

export const config = {
revalidate: 0,
}

export const revalidate = -1

export default function Root({ children }) {
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app/app/(rootonly)/dashboard/hello/page.js
Expand Up @@ -6,6 +6,4 @@ export default function HelloPage(props) {
)
}

export const config = {
runtime: 'experimental-edge',
}
export const runtime = 'experimental-edge'
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app/app/dashboard/page.js
Expand Up @@ -13,6 +13,4 @@ export default function DashboardPage(props) {
)
}

export const config = {
runtime: 'experimental-edge',
}
export const runtime = 'experimental-edge'
@@ -1,6 +1,4 @@
export const config = {
revalidate: 0,
}
export const revalidate = 0

export default function Page() {
const err = new Error('this is a test')
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app/app/error/server-component/page.js
@@ -1,6 +1,4 @@
export const config = {
revalidate: 0,
}
export const revalidate = 0

export default function Page() {
throw new Error('this is a test')
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app/app/layout.js
Expand Up @@ -3,9 +3,7 @@ import { experimental_use as use } from 'react'
import '../styles/global.css'
import './style.css'

export const config = {
revalidate: 0,
}
export const revalidate = 0

async function getData() {
return {
Expand Down
4 changes: 1 addition & 3 deletions test/e2e/app-dir/app/app/slow-page-no-loading/page.js
Expand Up @@ -13,6 +13,4 @@ export default function SlowPage(props) {
return <h1 id="slow-page-message">{data.message}</h1>
}

export const config = {
runtime: 'experimental-edge',
}
export const runtime = 'experimental-edge'

0 comments on commit bad909e

Please sign in to comment.