Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2155,7 +2155,6 @@ export default async function getBaseWebpackConfig(
dev,
isEdgeServer,
pageExtensions: config.pageExtensions,
cacheLifeConfig: config.cacheLife,
originalRewrites,
originalRedirects,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ describe('next-types-plugin', () => {
dev: false,
isEdgeServer: false,
pageExtensions: ['tsx', 'ts', 'jsx', 'js'],
cacheLifeConfig: undefined,
originalRewrites: undefined,
originalRedirects: undefined,
})
Expand Down Expand Up @@ -39,7 +38,6 @@ describe('next-types-plugin', () => {
dev: false,
isEdgeServer: false,
pageExtensions: ['tsx', 'ts', 'jsx', 'js'],
cacheLifeConfig: undefined,
originalRewrites: undefined,
originalRedirects: undefined,
})
Expand All @@ -59,7 +57,7 @@ describe('next-types-plugin', () => {
dev: false,
isEdgeServer: false,
pageExtensions: ['tsx', 'ts', 'jsx', 'js'],
cacheLifeConfig: undefined,

originalRewrites: undefined,
originalRedirects: undefined,
})
Expand Down
200 changes: 4 additions & 196 deletions packages/next/src/build/webpack/plugins/next-types-plugin/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// DO NOT ADD NEW FEATURES TO THIS PLUGIN
// DOING SO PREVENTS THEM FROM WORKING FOR TURBOPACK USERS.
// FOLLOW THE PATTERN OF TYPED-ROUTES AND CACHE-LIFE GENERATION

import type { Rewrite, Redirect } from '../../../../lib/load-custom-routes'

import fs from 'fs/promises'
Expand All @@ -14,7 +18,6 @@ import { normalizeAppPath } from '../../../../shared/lib/router/utils/app-paths'
import { getPageFromPath } from '../../../entries'
import type { PageExtensions } from '../../../page-extensions-type'
import { getProxiedPluginState } from '../../../build-context'
import type { CacheLife } from '../../../../server/use-cache/cache-life'

const PLUGIN_NAME = 'NextTypesPlugin'

Expand All @@ -31,7 +34,6 @@ interface Options {
dev: boolean
isEdgeServer: boolean
pageExtensions: PageExtensions
cacheLifeConfig: undefined | { [profile: string]: CacheLife }
originalRewrites: Rewrites | undefined
originalRedirects: Redirect[] | undefined
}
Expand Down Expand Up @@ -283,63 +285,6 @@ function formatRouteToRouteType(route: string) {
}
}

function formatTimespan(seconds: number): string {
if (seconds > 0) {
if (seconds === 18748800) {
return '1 month'
}
if (seconds === 18144000) {
return '1 month'
}
if (seconds === 604800) {
return '1 week'
}
if (seconds === 86400) {
return '1 day'
}
if (seconds === 3600) {
return '1 hour'
}
if (seconds === 60) {
return '1 minute'
}
if (seconds % 18748800 === 0) {
return seconds / 18748800 + ' months'
}
if (seconds % 18144000 === 0) {
return seconds / 18144000 + ' months'
}
if (seconds % 604800 === 0) {
return seconds / 604800 + ' weeks'
}
if (seconds % 86400 === 0) {
return seconds / 86400 + ' days'
}
if (seconds % 3600 === 0) {
return seconds / 3600 + ' hours'
}
if (seconds % 60 === 0) {
return seconds / 60 + ' minutes'
}
}
return seconds + ' seconds'
}

function formatTimespanWithSeconds(seconds: undefined | number): string {
if (seconds === undefined) {
return 'default'
}
if (seconds >= 0xfffffffe) {
return 'never'
}
const text = seconds + ' seconds'
const descriptive = formatTimespan(seconds)
if (descriptive === text) {
return text
}
return text + ' (' + descriptive + ')'
}

function getRootParamsFromLayouts(layouts: Record<string, string[]>) {
// Sort layouts by depth (descending)
const sortedLayouts = Object.entries(layouts).sort(
Expand Down Expand Up @@ -426,127 +371,6 @@ function createServerDefinitions() {
`
}

function createCustomCacheLifeDefinitions(cacheLife: {
[profile: string]: CacheLife
}) {
let overloads = ''

const profileNames = Object.keys(cacheLife)
for (let i = 0; i < profileNames.length; i++) {
const profileName = profileNames[i]
const profile = cacheLife[profileName]
if (typeof profile !== 'object' || profile === null) {
continue
}

let description = ''

if (profile.stale === undefined) {
description += `
* This cache may be stale on clients for the default stale time of the scope before checking with the server.`
} else if (profile.stale >= 0xfffffffe) {
description += `
* This cache may be stale on clients indefinitely before checking with the server.`
} else {
description += `
* This cache may be stale on clients for ${formatTimespan(profile.stale)} before checking with the server.`
}
if (
profile.revalidate !== undefined &&
profile.expire !== undefined &&
profile.revalidate >= profile.expire
) {
description += `
* This cache will expire after ${formatTimespan(profile.expire)}. The next request will recompute it.`
} else {
if (profile.revalidate === undefined) {
description += `
* It will inherit the default revalidate time of its scope since it does not define its own.`
} else if (profile.revalidate >= 0xfffffffe) {
// Nothing to mention.
} else {
description += `
* If the server receives a new request after ${formatTimespan(profile.revalidate)}, start revalidating new values in the background.`
}
if (profile.expire === undefined) {
description += `
* It will inherit the default expiration time of its scope since it does not define its own.`
} else if (profile.expire >= 0xfffffffe) {
description += `
* It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.`
} else {
description += `
* If this entry has no traffic for ${formatTimespan(profile.expire)} it will expire. The next request will recompute it.`
}
}

overloads += `
/**
* Cache this \`"use cache"\` for a timespan defined by the \`${JSON.stringify(profileName)}\` profile.
* \`\`\`
* stale: ${formatTimespanWithSeconds(profile.stale)}
* revalidate: ${formatTimespanWithSeconds(profile.revalidate)}
* expire: ${formatTimespanWithSeconds(profile.expire)}
* \`\`\`
* ${description}
*/
export function cacheLife(profile: ${JSON.stringify(profileName)}): void
`
}

overloads += `
/**
* Cache this \`"use cache"\` using a custom timespan.
* \`\`\`
* stale: ... // seconds
* revalidate: ... // seconds
* expire: ... // seconds
* \`\`\`
*
* This is similar to Cache-Control: max-age=\`stale\`,s-max-age=\`revalidate\`,stale-while-revalidate=\`expire-revalidate\`
*
* If a value is left out, the lowest of other cacheLife() calls or the default, is used instead.
*/
export function cacheLife(profile: {
/**
* This cache may be stale on clients for ... seconds before checking with the server.
*/
stale?: number,
/**
* If the server receives a new request after ... seconds, start revalidating new values in the background.
*/
revalidate?: number,
/**
* If this entry has no traffic for ... seconds it will expire. The next request will recompute it.
*/
expire?: number
}): void
`

// Redefine the cacheLife() accepted arguments.
return `// Type definitions for Next.js cacheLife configs

declare module 'next/cache' {
export { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache'
export {
updateTag,
revalidateTag,
revalidatePath,
refresh,
} from 'next/dist/server/web/spec-extension/revalidate'
export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'

${overloads}

import { cacheTag } from 'next/dist/server/use-cache/cache-tag'
export { cacheTag }

export const unstable_cacheTag: typeof cacheTag
export const unstable_cacheLife: typeof cacheLife
}
`
}

const appTypesBasePath = path.join('types', 'app')

export class NextTypesPlugin {
Expand All @@ -557,7 +381,6 @@ export class NextTypesPlugin {
isEdgeServer: boolean
pageExtensions: string[]
pagesDir: string
cacheLifeConfig: undefined | { [profile: string]: CacheLife }
distDirAbsolutePath: string

constructor(options: Options) {
Expand All @@ -568,7 +391,6 @@ export class NextTypesPlugin {
this.isEdgeServer = options.isEdgeServer
this.pageExtensions = options.pageExtensions
this.pagesDir = path.join(this.appDir, '..', 'pages')
this.cacheLifeConfig = options.cacheLifeConfig
this.distDirAbsolutePath = path.join(this.dir, this.distDir)
}

Expand Down Expand Up @@ -829,20 +651,6 @@ export class NextTypesPlugin {
) as unknown as webpack.sources.RawSource
)

if (this.cacheLifeConfig) {
const cacheLifeAssetPath = path.join(
assetDirRelative,
'types/cache-life.d.ts'
)

compilation.emitAsset(
cacheLifeAssetPath,
new sources.RawSource(
createCustomCacheLifeDefinitions(this.cacheLifeConfig)
) as unknown as webpack.sources.RawSource
)
}

callback()
}
)
Expand Down
7 changes: 6 additions & 1 deletion packages/next/src/cli/next-typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
writeRouteTypesManifest,
writeValidatorFile,
} from '../server/lib/router-utils/route-types-utils'
import { writeCacheLifeTypes } from '../server/lib/router-utils/cache-life-type-utils'
import { createValidFileMatcher } from '../server/lib/find-page-file'

export type NextTypegenOptions = {
Expand Down Expand Up @@ -167,7 +168,11 @@ const nextTypegen = async (

await writeValidatorFile(routeTypesManifest, validatorFilePath)

console.log('✓ Route types generated successfully')
// Generate cache-life types if cacheLife config exists
const cacheLifeFilePath = join(distDir, 'types', 'cache-life.d.ts')
writeCacheLifeTypes(nextConfig.cacheLife, cacheLifeFilePath)

console.log('✓ Types generated successfully')
}

export { nextTypegen }
Loading
Loading