From d41873beb3fe295022e023481f34d3b725d27cd2 Mon Sep 17 00:00:00 2001 From: pieh Date: Thu, 22 Aug 2024 11:56:48 +0200 Subject: [PATCH 01/15] update react version needed by next@canary --- tests/utils/next-version-helpers.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/next-version-helpers.mjs b/tests/utils/next-version-helpers.mjs index abf4053471..9cddd8f19a 100644 --- a/tests/utils/next-version-helpers.mjs +++ b/tests/utils/next-version-helpers.mjs @@ -9,7 +9,7 @@ const FUTURE_NEXT_PATCH_VERSION = '14.999.0' const NEXT_VERSION_REQUIRES_REACT_19 = '14.3.0-canary.45' // TODO: Update this when React 19 is released -const REACT_19_VERSION = '19.0.0-rc.0' +const REACT_19_VERSION = '19.0.0-rc-1eaccd82-20240816' const REACT_18_VERSION = '18.2.0' /** From 89ff2285a0f184884d7fddb3a5bdf4ba790682c9 Mon Sep 17 00:00:00 2001 From: pieh Date: Fri, 23 Aug 2024 18:33:45 +0200 Subject: [PATCH 02/15] tmp: just checking if canary tests will be happier --- src/build/content/prerendered.ts | 6 +++--- src/run/handlers/cache.cts | 26 ++++++++++++++++++-------- src/shared/cache-types.cts | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/build/content/prerendered.ts b/src/build/content/prerendered.ts index 9f1ae2fe75..b93e5e11fa 100644 --- a/src/build/content/prerendered.ts +++ b/src/build/content/prerendered.ts @@ -46,7 +46,7 @@ const writeCacheEntry = async ( const routeToFilePath = (path: string) => (path === '/' ? '/index' : path) const buildPagesCacheValue = async (path: string): Promise => ({ - kind: 'PAGE', + kind: 'PAGES', html: await readFile(`${path}.html`, 'utf-8'), pageData: JSON.parse(await readFile(`${path}.json`, 'utf-8')), headers: undefined, @@ -97,7 +97,7 @@ const buildRouteCacheValue = async ( path: string, initialRevalidateSeconds: number | false, ): Promise => ({ - kind: 'ROUTE', + kind: 'APP_ROUTE', body: await readFile(`${path}.body`, 'base64'), ...JSON.parse(await readFile(`${path}.meta`, 'utf-8')), revalidate: initialRevalidateSeconds, @@ -171,7 +171,7 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise } // Netlify Forms are not support and require a workaround - if (value.kind === 'PAGE' || value.kind === 'APP_PAGE') { + if (value.kind === 'PAGE' || value.kind === 'PAGES' || value.kind === 'APP_PAGE') { verifyNetlifyForms(ctx, value.html) } diff --git a/src/run/handlers/cache.cts b/src/run/handlers/cache.cts index 790c89004e..e8c94c6758 100644 --- a/src/run/handlers/cache.cts +++ b/src/run/handlers/cache.cts @@ -132,13 +132,18 @@ export class NetlifyCacheHandler implements CacheHandler { if ( cacheValue.kind === 'PAGE' || + cacheValue.kind === 'PAGES' || cacheValue.kind === 'APP_PAGE' || - cacheValue.kind === 'ROUTE' + cacheValue.kind === 'ROUTE' || + cacheValue.kind === 'APP_ROUTE' ) { if (cacheValue.headers?.[NEXT_CACHE_TAGS_HEADER]) { const cacheTags = (cacheValue.headers[NEXT_CACHE_TAGS_HEADER] as string).split(',') requestContext.responseCacheTags = cacheTags - } else if (cacheValue.kind === 'PAGE' && typeof cacheValue.pageData === 'object') { + } else if ( + (cacheValue.kind === 'PAGE' || cacheValue.kind === 'PAGES') && + typeof cacheValue.pageData === 'object' + ) { // pages router doesn't have cache tags headers in PAGE cache value // so we need to generate appropriate cache tags for it const cacheTags = [`_N_T_${key === '/index' ? '/' : key}`] @@ -186,6 +191,7 @@ export class NetlifyCacheHandler implements CacheHandler { } async get(...args: Parameters): ReturnType { + debugger return this.tracer.withActiveSpan('get cache key', async (span) => { const [key, ctx = {}] = args getLogger().debug(`[NetlifyCacheHandler.get]: ${key}`) @@ -224,8 +230,9 @@ export class NetlifyCacheHandler implements CacheHandler { value: blob.value, } - case 'ROUTE': { - span.addEvent('ROUTE', { lastModified: blob.lastModified, status: blob.value.status }) + case 'ROUTE': + case 'APP_ROUTE': { + span.addEvent('APP_ROUTE', { lastModified: blob.lastModified, status: blob.value.status }) const valueWithoutRevalidate = this.captureRouteRevalidateAndRemoveFromObject(blob.value) @@ -237,7 +244,8 @@ export class NetlifyCacheHandler implements CacheHandler { }, } } - case 'PAGE': { + case 'PAGE': + case 'PAGES': { span.addEvent('PAGE', { lastModified: blob.lastModified }) const { revalidate, ...restOfPageValue } = blob.value @@ -275,7 +283,7 @@ export class NetlifyCacheHandler implements CacheHandler { data: Parameters[1], context: Parameters[2], ): NetlifyIncrementalCacheValue | null { - if (data?.kind === 'ROUTE') { + if (data?.kind === 'ROUTE' || data?.kind === 'APP_ROUTE') { return { ...data, revalidate: context.revalidate, @@ -283,7 +291,7 @@ export class NetlifyCacheHandler implements CacheHandler { } } - if (data?.kind === 'PAGE') { + if (data?.kind === 'PAGE' || data?.kind === 'PAGES') { return { ...data, revalidate: context.revalidate, @@ -397,8 +405,10 @@ export class NetlifyCacheHandler implements CacheHandler { cacheTags = [...tags, ...softTags] } else if ( cacheEntry.value?.kind === 'PAGE' || + cacheEntry.value?.kind === 'PAGES' || cacheEntry.value?.kind === 'APP_PAGE' || - cacheEntry.value?.kind === 'ROUTE' + cacheEntry.value?.kind === 'ROUTE' || + cacheEntry.value?.kind === 'APP_ROUTE' ) { cacheTags = (cacheEntry.value.headers?.[NEXT_CACHE_TAGS_HEADER] as string)?.split(',') || [] } else { diff --git a/src/shared/cache-types.cts b/src/shared/cache-types.cts index 06cee07516..f638a14aa1 100644 --- a/src/shared/cache-types.cts +++ b/src/shared/cache-types.cts @@ -28,7 +28,7 @@ export type NetlifyCachedAppPageValue = Omit[2]['revalidate'] } -type CachedPageValue = Extract +type CachedPageValue = Extract export type NetlifyCachedPageValue = CachedPageValue & { revalidate?: Parameters[2]['revalidate'] From 3a42a1cac407ad9872dd58f7ecffaf719603a026 Mon Sep 17 00:00:00 2001 From: pieh Date: Fri, 23 Aug 2024 21:55:15 +0200 Subject: [PATCH 03/15] what's up with NODE_ENV? --- src/run/handlers/request-context.cts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index c54427f74a..ead5719cac 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -4,6 +4,9 @@ import { LogLevel, systemLogger } from '@netlify/functions/internal' import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs' +// hacks? +process.env.NODE_ENV = 'production' + type SystemLogger = typeof systemLogger export type RequestContext = { From 407759c172194b697d14558b925879a3036f0fc8 Mon Sep 17 00:00:00 2001 From: pieh Date: Mon, 26 Aug 2024 21:25:51 +0200 Subject: [PATCH 04/15] missing PAGES --- src/run/handlers/cache.cts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/handlers/cache.cts b/src/run/handlers/cache.cts index e8c94c6758..1f0affb7e1 100644 --- a/src/run/handlers/cache.cts +++ b/src/run/handlers/cache.cts @@ -329,7 +329,7 @@ export class NetlifyCacheHandler implements CacheHandler { value, }) - if (data?.kind === 'PAGE') { + if (data?.kind === 'PAGE' || data?.kind === 'PAGES') { const requestContext = getRequestContext() if (requestContext?.didPagesRouterOnDemandRevalidate) { const tag = `_N_T_${key === '/index' ? '/' : key}` From 86eeae5bd5edd4e79b2a3db2716a4050fd70bf75 Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 27 Aug 2024 08:51:06 +0200 Subject: [PATCH 05/15] chore: use npm info to figure out react version needed for canary instead of hardcoding it --- tests/utils/next-version-helpers.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/utils/next-version-helpers.mjs b/tests/utils/next-version-helpers.mjs index 9cddd8f19a..f9b158e85a 100644 --- a/tests/utils/next-version-helpers.mjs +++ b/tests/utils/next-version-helpers.mjs @@ -4,12 +4,11 @@ import { readFile, writeFile } from 'node:fs/promises' import fg from 'fast-glob' import { gte, satisfies, valid } from 'semver' +import { execaCommand } from 'execa' const FUTURE_NEXT_PATCH_VERSION = '14.999.0' const NEXT_VERSION_REQUIRES_REACT_19 = '14.3.0-canary.45' -// TODO: Update this when React 19 is released -const REACT_19_VERSION = '19.0.0-rc-1eaccd82-20240816' const REACT_18_VERSION = '18.2.0' /** @@ -97,8 +96,17 @@ export async function setNextVersionInFixture( return } packageJson.dependencies.next = version + + const { stdout } = await execaCommand( + `npm info next@${resolvedVersion} peerDependencies --json`, + { cwd }, + ) + + const nextPeerDependencies = JSON.parse(stdout) + if (updateReact && nextVersionRequiresReact19(checkVersion)) { - const reactVersion = operation === 'update' ? REACT_19_VERSION : REACT_18_VERSION + const reactVersion = + operation === 'update' ? nextPeerDependencies['react'] : REACT_18_VERSION packageJson.dependencies.react = reactVersion packageJson.dependencies['react-dom'] = reactVersion } From 7da023fc4399203258298f3eecce387752de3c60 Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 27 Aug 2024 09:30:54 +0200 Subject: [PATCH 06/15] chore: drop package lock file when preparing fixtures --- tests/prepare.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/prepare.mjs b/tests/prepare.mjs index 13beeaca2a..5bc35c6f1f 100644 --- a/tests/prepare.mjs +++ b/tests/prepare.mjs @@ -44,14 +44,17 @@ const promises = fixtures.map((fixture) => }) } - // npm is the default - let cmd = `npm install --no-audit --progress=false --prefer-offline --legacy-peer-deps` + let cmd = `` const { packageManager } = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8')) if (packageManager?.startsWith('pnpm')) { // We disable frozen-lockfile because we may have changed the version of Next.js cmd = `pnpm install --frozen-lockfile=false ${ process.env.DEBUG || NEXT_VERSION !== 'latest' ? '' : '--reporter silent' }` + } else { + // npm is the default + cmd = `npm install --no-audit --progress=false --prefer-offline --legacy-peer-deps` + await rm(join(cwd, 'package-lock.json'), { force: true }) } const addPrefix = new Transform({ From 7b91eb91ac2a714e5be9bb3a12ff56c5a0acfbba Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 27 Aug 2024 12:26:08 +0200 Subject: [PATCH 07/15] test: unset more things related to next's fetch patching --- tests/utils/fixture.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index 18ebb328fd..ddcff4479f 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -89,6 +89,7 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => ) { // @ts-ignore fetch doesn't have _nextOriginalFetch property in types globalThis.fetch = globalThis._nextOriginalFetch || globalThis.fetch._nextOriginalFetch + delete globalThis[Symbol.for('next-patch')] } ctx.cwd = await mkdtemp(join(tmpdir(), 'netlify-next-runtime-')) From 8c2c04077e919c1107683c2a4882ec0598eea590 Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 27 Aug 2024 12:57:13 +0200 Subject: [PATCH 08/15] use correct cache kind for initial cache seeding depending on next version --- src/build/content/prerendered.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/build/content/prerendered.ts b/src/build/content/prerendered.ts index b93e5e11fa..cdcf50cb5c 100644 --- a/src/build/content/prerendered.ts +++ b/src/build/content/prerendered.ts @@ -45,8 +45,11 @@ const writeCacheEntry = async ( */ const routeToFilePath = (path: string) => (path === '/' ? '/index' : path) -const buildPagesCacheValue = async (path: string): Promise => ({ - kind: 'PAGES', +const buildPagesCacheValue = async ( + path: string, + shouldUseEnumKind: boolean, +): Promise => ({ + kind: shouldUseEnumKind ? 'PAGES' : 'PAGE', html: await readFile(`${path}.html`, 'utf-8'), pageData: JSON.parse(await readFile(`${path}.json`, 'utf-8')), headers: undefined, @@ -96,8 +99,9 @@ const buildAppCacheValue = async ( const buildRouteCacheValue = async ( path: string, initialRevalidateSeconds: number | false, + shouldUseEnumKind: boolean, ): Promise => ({ - kind: 'APP_ROUTE', + kind: shouldUseEnumKind ? 'APP_ROUTE' : 'ROUTE', body: await readFile(`${path}.body`, 'base64'), ...JSON.parse(await readFile(`${path}.meta`, 'utf-8')), revalidate: initialRevalidateSeconds, @@ -133,6 +137,12 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise }) : false + const shouldUseEnumKind = ctx.nextVersion + ? satisfies(ctx.nextVersion, '>=15.0.0-canary.114 <15.0.0-d || >15.0.0-rc.0', { + includePrerelease: true, + }) + : false + await Promise.all( Object.entries(manifest.routes).map( ([route, meta]): Promise => @@ -152,7 +162,10 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise // if pages router returns 'notFound: true', build won't produce html and json files return } - value = await buildPagesCacheValue(join(ctx.publishDir, 'server/pages', key)) + value = await buildPagesCacheValue( + join(ctx.publishDir, 'server/pages', key), + shouldUseEnumKind, + ) break case meta.dataRoute?.endsWith('.rsc'): value = await buildAppCacheValue( @@ -164,6 +177,7 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise value = await buildRouteCacheValue( join(ctx.publishDir, 'server/app', key), meta.initialRevalidateSeconds, + shouldUseEnumKind, ) break default: From 868d8d8eb306efda65475f32d5c567ed77927be1 Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 27 Aug 2024 13:22:01 +0200 Subject: [PATCH 09/15] small cleanup --- src/build/content/prerendered.ts | 1 + src/run/handlers/cache.cts | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/build/content/prerendered.ts b/src/build/content/prerendered.ts index cdcf50cb5c..de14d850e1 100644 --- a/src/build/content/prerendered.ts +++ b/src/build/content/prerendered.ts @@ -137,6 +137,7 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise }) : false + // https://github.com/vercel/next.js/pull/68602 changed the cache kind for Pages router pages from `PAGE` to `PAGES` and from `ROUTE` to `APP_ROUTE`. const shouldUseEnumKind = ctx.nextVersion ? satisfies(ctx.nextVersion, '>=15.0.0-canary.114 <15.0.0-d || >15.0.0-rc.0', { includePrerelease: true, diff --git a/src/run/handlers/cache.cts b/src/run/handlers/cache.cts index 1f0affb7e1..7fa6fefe06 100644 --- a/src/run/handlers/cache.cts +++ b/src/run/handlers/cache.cts @@ -191,7 +191,6 @@ export class NetlifyCacheHandler implements CacheHandler { } async get(...args: Parameters): ReturnType { - debugger return this.tracer.withActiveSpan('get cache key', async (span) => { const [key, ctx = {}] = args getLogger().debug(`[NetlifyCacheHandler.get]: ${key}`) @@ -232,7 +231,10 @@ export class NetlifyCacheHandler implements CacheHandler { case 'ROUTE': case 'APP_ROUTE': { - span.addEvent('APP_ROUTE', { lastModified: blob.lastModified, status: blob.value.status }) + span.addEvent(blob.value?.kind, { + lastModified: blob.lastModified, + status: blob.value.status, + }) const valueWithoutRevalidate = this.captureRouteRevalidateAndRemoveFromObject(blob.value) @@ -246,7 +248,7 @@ export class NetlifyCacheHandler implements CacheHandler { } case 'PAGE': case 'PAGES': { - span.addEvent('PAGE', { lastModified: blob.lastModified }) + span.addEvent(blob.value?.kind, { lastModified: blob.lastModified }) const { revalidate, ...restOfPageValue } = blob.value @@ -258,7 +260,7 @@ export class NetlifyCacheHandler implements CacheHandler { } } case 'APP_PAGE': { - span.addEvent('APP_PAGE', { lastModified: blob.lastModified }) + span.addEvent(blob.value?.kind, { lastModified: blob.lastModified }) const { revalidate, rscData, ...restOfPageValue } = blob.value From 312cd28c07d8092343aac5ca62ea8a261f964a81 Mon Sep 17 00:00:00 2001 From: pieh Date: Tue, 27 Aug 2024 21:02:45 +0200 Subject: [PATCH 10/15] typehell --- src/build/content/prerendered.ts | 6 +- src/run/handlers/cache.cts | 37 ++++---- src/run/handlers/request-context.cts | 3 +- src/shared/cache-types.cts | 123 +++++++++++++++++++++------ 4 files changed, 127 insertions(+), 42 deletions(-) diff --git a/src/build/content/prerendered.ts b/src/build/content/prerendered.ts index de14d850e1..6c45a74472 100644 --- a/src/build/content/prerendered.ts +++ b/src/build/content/prerendered.ts @@ -10,7 +10,7 @@ import { satisfies } from 'semver' import { encodeBlobKey } from '../../shared/blobkey.js' import type { - CachedFetchValue, + CachedFetchValueForMultipleVersions, NetlifyCachedAppPageValue, NetlifyCachedPageValue, NetlifyCachedRouteValue, @@ -107,7 +107,9 @@ const buildRouteCacheValue = async ( revalidate: initialRevalidateSeconds, }) -const buildFetchCacheValue = async (path: string): Promise => ({ +const buildFetchCacheValue = async ( + path: string, +): Promise => ({ kind: 'FETCH', ...JSON.parse(await readFile(path, 'utf-8')), }) diff --git a/src/run/handlers/cache.cts b/src/run/handlers/cache.cts index 7fa6fefe06..32e569c065 100644 --- a/src/run/handlers/cache.cts +++ b/src/run/handlers/cache.cts @@ -11,14 +11,15 @@ import { type Span } from '@opentelemetry/api' import type { PrerenderManifest } from 'next/dist/build/index.js' import { NEXT_CACHE_TAGS_HEADER } from 'next/dist/lib/constants.js' -import type { - CacheHandler, - CacheHandlerContext, - IncrementalCache, - NetlifyCachedPageValue, - NetlifyCachedRouteValue, - NetlifyCacheHandlerValue, - NetlifyIncrementalCacheValue, +import { + type CacheHandlerContext, + type CacheHandlerForMultipleVersions, + isCachedPageValue, + isCachedRouteValue, + type NetlifyCachedPageValue, + type NetlifyCachedRouteValue, + type NetlifyCacheHandlerValue, + type NetlifyIncrementalCacheValue, } from '../../shared/cache-types.cjs' import { getRegionalBlobStore } from '../regional-blob-store.cjs' @@ -29,7 +30,7 @@ type TagManifest = { revalidatedAt: number } type TagManifestBlobCache = Record> -export class NetlifyCacheHandler implements CacheHandler { +export class NetlifyCacheHandler implements CacheHandlerForMultipleVersions { options: CacheHandlerContext revalidatedTags: string[] blobStore: Store @@ -190,7 +191,9 @@ export class NetlifyCacheHandler implements CacheHandler { } } - async get(...args: Parameters): ReturnType { + async get( + ...args: Parameters + ): ReturnType { return this.tracer.withActiveSpan('get cache key', async (span) => { const [key, ctx = {}] = args getLogger().debug(`[NetlifyCacheHandler.get]: ${key}`) @@ -282,10 +285,14 @@ export class NetlifyCacheHandler implements CacheHandler { } private transformToStorableObject( - data: Parameters[1], - context: Parameters[2], + data: Parameters[1], + context: Parameters[2], ): NetlifyIncrementalCacheValue | null { - if (data?.kind === 'ROUTE' || data?.kind === 'APP_ROUTE') { + if (!data) { + return null + } + + if (isCachedRouteValue(data)) { return { ...data, revalidate: context.revalidate, @@ -293,7 +300,7 @@ export class NetlifyCacheHandler implements CacheHandler { } } - if (data?.kind === 'PAGE' || data?.kind === 'PAGES') { + if (isCachedPageValue(data)) { return { ...data, revalidate: context.revalidate, @@ -311,7 +318,7 @@ export class NetlifyCacheHandler implements CacheHandler { return data } - async set(...args: Parameters) { + async set(...args: Parameters) { return this.tracer.withActiveSpan('set cache key', async (span) => { const [key, data, context] = args const blobKey = await this.encodeBlobKey(key) diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index ead5719cac..707e55ed06 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -4,7 +4,8 @@ import { LogLevel, systemLogger } from '@netlify/functions/internal' import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs' -// hacks? +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore - last remaining bit to fix process.env.NODE_ENV = 'production' type SystemLogger = typeof systemLogger diff --git a/src/shared/cache-types.cts b/src/shared/cache-types.cts index f638a14aa1..a8ad9f2ba0 100644 --- a/src/shared/cache-types.cts +++ b/src/shared/cache-types.cts @@ -1,53 +1,78 @@ import type { + CacheHandler, CacheHandlerValue, - IncrementalCache, } from 'next/dist/server/lib/incremental-cache/index.js' import type { + CachedFetchValue, CachedRouteValue, IncrementalCachedAppPageValue, + IncrementalCachedPageValue, IncrementalCacheValue, } from 'next/dist/server/response-cache/types.js' -export type { - CacheHandler, - CacheHandlerContext, - IncrementalCache, -} from 'next/dist/server/lib/incremental-cache/index.js' +export type { CacheHandlerContext } from 'next/dist/server/lib/incremental-cache/index.js' -export type NetlifyCachedRouteValue = Omit & { +/** + * Shape of the cache value that is returned from CacheHandler.get or passed to CacheHandler.set + */ +type CachedRouteValueForMultipleVersions = Omit & { + kind: 'ROUTE' | 'APP_ROUTE' +} + +/** + * Used for storing in blobs and reading from blobs + */ +export type NetlifyCachedRouteValue = Omit & { // Next.js stores body as buffer, while we store it as base64 encoded string body: string // Next.js doesn't produce cache-control tag we use to generate cdn cache control // so store needed values as part of cached response data - revalidate: Parameters[2]['revalidate'] + revalidate?: Parameters[2]['revalidate'] } -export type NetlifyCachedAppPageValue = Omit & { +/** + * Shape of the cache value that is returned from CacheHandler.get or passed to CacheHandler.set + */ +type IncrementalCachedAppPageValueForMultipleVersions = Omit< + IncrementalCachedAppPageValue, + 'kind' +> & { + kind: 'APP_PAGE' +} + +/** + * Used for storing in blobs and reading from blobs + */ +export type NetlifyCachedAppPageValue = Omit< + IncrementalCachedAppPageValueForMultipleVersions, + 'rscData' +> & { // Next.js stores rscData as buffer, while we store it as base64 encoded string rscData: string | undefined - revalidate?: Parameters[2]['revalidate'] + revalidate?: Parameters[2]['revalidate'] } -type CachedPageValue = Extract - -export type NetlifyCachedPageValue = CachedPageValue & { - revalidate?: Parameters[2]['revalidate'] +/** + * Shape of the cache value that is returned from CacheHandler.get or passed to CacheHandler.set + */ +type IncrementalCachedPageValueForMultipleVersions = Omit & { + kind: 'PAGE' | 'PAGES' } -export type CachedFetchValue = Extract +/** + * Used for storing in blobs and reading from blobs + */ +export type NetlifyCachedPageValue = IncrementalCachedPageValueForMultipleVersions & { + revalidate?: Parameters[2]['revalidate'] +} -export type NetlifyIncrementalCacheValue = - | Exclude< - IncrementalCacheValue, - CachedRouteValue | CachedPageValue | IncrementalCachedAppPageValue - > - | NetlifyCachedRouteValue - | NetlifyCachedPageValue - | NetlifyCachedAppPageValue +export type CachedFetchValueForMultipleVersions = Omit & { + kind: 'FETCH' +} type CachedRouteValueToNetlify = T extends CachedRouteValue ? NetlifyCachedRouteValue - : T extends CachedPageValue + : T extends IncrementalCachedPageValue ? NetlifyCachedPageValue : T extends IncrementalCachedAppPageValue ? NetlifyCachedAppPageValue @@ -55,4 +80,54 @@ type CachedRouteValueToNetlify = T extends CachedRouteValue type MapCachedRouteValueToNetlify = { [K in keyof T]: CachedRouteValueToNetlify } +/** + * Used for storing in blobs and reading from blobs + */ export type NetlifyCacheHandlerValue = MapCachedRouteValueToNetlify + +/** + * Used for storing in blobs and reading from blobs + */ +export type NetlifyIncrementalCacheValue = NetlifyCacheHandlerValue['value'] + +// type IncrementalCacheValueToMultipleVersions = T extends CachedRouteValue ? +type IncrementalCacheValueToMultipleVersions = T extends CachedRouteValue + ? CachedRouteValueForMultipleVersions + : T extends IncrementalCachedPageValue + ? IncrementalCachedPageValueForMultipleVersions + : T extends IncrementalCachedAppPageValue + ? IncrementalCachedAppPageValueForMultipleVersions + : T extends CachedFetchValue + ? CachedFetchValueForMultipleVersions + : T extends CacheHandlerValue + ? { + [K in keyof T]: IncrementalCacheValueToMultipleVersions + } + : T + +type IncrementalCacheValueForMultipleVersions = + IncrementalCacheValueToMultipleVersions + +export const isCachedPageValue = ( + value: IncrementalCacheValueForMultipleVersions, +): value is IncrementalCachedPageValueForMultipleVersions => + value.kind === 'PAGE' || value.kind === 'PAGES' + +export const isCachedRouteValue = ( + value: IncrementalCacheValueForMultipleVersions, +): value is CachedRouteValueForMultipleVersions => + value.kind === 'ROUTE' || value.kind === 'APP_ROUTE' + +type MapArgsOrReturn = T extends readonly any[] + ? { [K in keyof T]: MapArgsOrReturn } + : T extends Promise + ? Promise> + : IncrementalCacheValueToMultipleVersions + +type MapCacheHandlerClassMethod = T extends (...args: infer Args) => infer Ret + ? (...args: MapArgsOrReturn) => MapArgsOrReturn + : T + +type MapCacheHandlerClass = { [K in keyof T]: MapCacheHandlerClassMethod } + +export type CacheHandlerForMultipleVersions = MapCacheHandlerClass From da3702bd30b6c63e0e1d6c55d7f1be6b3413e168 Mon Sep 17 00:00:00 2001 From: pieh Date: Wed, 28 Aug 2024 08:21:13 +0200 Subject: [PATCH 11/15] just checking --- src/run/handlers/request-context.cts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index 707e55ed06..cb47c8473d 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -6,7 +6,7 @@ import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs' // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - last remaining bit to fix -process.env.NODE_ENV = 'production' +// process.env.NODE_ENV = 'production' type SystemLogger = typeof systemLogger From 678a182715a1ca964140bcc5143d57d65ec3a066 Mon Sep 17 00:00:00 2001 From: pieh Date: Wed, 28 Aug 2024 12:27:32 +0200 Subject: [PATCH 12/15] proper NODE_ENV setting with explanation --- src/run/handlers/request-context.cts | 4 ---- src/run/next.cts | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index cb47c8473d..c54427f74a 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -4,10 +4,6 @@ import { LogLevel, systemLogger } from '@netlify/functions/internal' import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore - last remaining bit to fix -// process.env.NODE_ENV = 'production' - type SystemLogger = typeof systemLogger export type RequestContext = { diff --git a/src/run/next.cts b/src/run/next.cts index edb6fd2a44..b99515e393 100644 --- a/src/run/next.cts +++ b/src/run/next.cts @@ -8,6 +8,15 @@ import { getRequestContext } from './handlers/request-context.cjs' import { getTracer } from './handlers/tracer.cjs' import { getRegionalBlobStore } from './regional-blob-store.cjs' +// https://github.com/vercel/next.js/pull/68193/files#diff-37243d614f1f5d3f7ea50bbf2af263f6b1a9a4f70e84427977781e07b02f57f1R49 +// This import resulted in importing unbundled React which depending if NODE_ENV is `production` or not would use +// either development or production version of React. When not set to `production` it would use development version +// which later cause mismatching problems when both development and production versions of React were loaded causing +// react errors. +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore ignoring readonly NODE_ENV +process.env.NODE_ENV = 'production' + console.time('import next server') // eslint-disable-next-line @typescript-eslint/no-var-requires From 9f9b9d2a88ffdc25fbd944d1277b2549b9a875a5 Mon Sep 17 00:00:00 2001 From: pieh Date: Wed, 28 Aug 2024 13:57:34 +0200 Subject: [PATCH 13/15] any is bad --- src/shared/cache-types.cts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/cache-types.cts b/src/shared/cache-types.cts index a8ad9f2ba0..f0e9478494 100644 --- a/src/shared/cache-types.cts +++ b/src/shared/cache-types.cts @@ -118,7 +118,7 @@ export const isCachedRouteValue = ( ): value is CachedRouteValueForMultipleVersions => value.kind === 'ROUTE' || value.kind === 'APP_ROUTE' -type MapArgsOrReturn = T extends readonly any[] +type MapArgsOrReturn = T extends readonly unknown[] ? { [K in keyof T]: MapArgsOrReturn } : T extends Promise ? Promise> From 7e64957681849abc92fc38c718ad753264f722e3 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 28 Aug 2024 21:50:47 +0200 Subject: [PATCH 14/15] Update src/shared/cache-types.cts Co-authored-by: Philippe Serhal --- src/shared/cache-types.cts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared/cache-types.cts b/src/shared/cache-types.cts index f0e9478494..bdc48bb973 100644 --- a/src/shared/cache-types.cts +++ b/src/shared/cache-types.cts @@ -90,7 +90,6 @@ export type NetlifyCacheHandlerValue = MapCachedRouteValueToNetlify = T extends CachedRouteValue ? type IncrementalCacheValueToMultipleVersions = T extends CachedRouteValue ? CachedRouteValueForMultipleVersions : T extends IncrementalCachedPageValue From d7b3492c5c5f3bd3851ea9a7053e32ccf00badaa Mon Sep 17 00:00:00 2001 From: pieh Date: Wed, 28 Aug 2024 21:59:57 +0200 Subject: [PATCH 15/15] add note about deleting next-patch symbol --- tests/utils/fixture.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index ddcff4479f..db86b9ea32 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -89,6 +89,9 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => ) { // @ts-ignore fetch doesn't have _nextOriginalFetch property in types globalThis.fetch = globalThis._nextOriginalFetch || globalThis.fetch._nextOriginalFetch + // https://github.com/vercel/next.js/pull/68193/files#diff-4c54e369ddb9a2db1eed95fe1d678f94c8e82c540204475d42c78e49bf4f223aR37-R40 + // above changed the way Next.js checks wether fetch was already patched. It still sets `__nextPatched` and `_nextOriginalFetch` + // properties we check above and use to get original fetch back delete globalThis[Symbol.for('next-patch')] }