Skip to content

Commit

Permalink
fix: marked all manifests that are loaded as frozen
Browse files Browse the repository at this point in the history
  • Loading branch information
wyattjoh committed Apr 10, 2024
1 parent c976ca6 commit 52b0358
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 28 deletions.
26 changes: 26 additions & 0 deletions packages/next/src/server/lib/freeze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function freeze(obj: object): void {
// `null` is an object, if we get this, we should just return it.
if (obj === null) return

// An array is an object, but we also want to freeze each element in the array
// as well.
if (Array.isArray(obj)) {
for (const item of obj) {
if (!item || typeof item !== 'object') continue
freeze(item)
}

Object.freeze(obj)
return
}

for (const name of Object.keys(obj)) {
const value = obj[name as keyof typeof obj]

if (!value || typeof value !== 'object') continue
freeze(value)
}

Object.freeze(obj)
return
}
59 changes: 35 additions & 24 deletions packages/next/src/server/lib/incremental-cache/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { CacheFs } from '../../../shared/lib/utils'
import type { PrerenderManifest } from '../../../build'
import type { PrerenderManifest, SsgRoute } from '../../../build'
import type {
IncrementalCacheValue,
IncrementalCacheEntry,
Expand All @@ -9,7 +9,6 @@ import type {

import FetchCache from './fetch-cache'
import FileSystemCache from './file-system-cache'
import path from '../../../shared/lib/isomorphic/path'
import { normalizePagePath } from '../../../shared/lib/page-path/normalize-page-path'

import {
Expand Down Expand Up @@ -189,6 +188,31 @@ export class IncrementalCache implements IncrementalCacheType {
}
}

private readonly revalidateSeconds = new Map<string, Revalidate>()

private getRevalidateSeconds(route: string): Revalidate | undefined {
let revalidateSeconds = this.revalidateSeconds.get(route)

// If we already have an entry for this pathname, return it.
if (typeof revalidateSeconds === 'number') {
return revalidateSeconds
}

// Otherwise, source it from the prerender manifest.
const ssgRoute: SsgRoute | undefined = this.prerenderManifest.routes[route]

// If there's no entry, return undefined.
if (typeof ssgRoute === 'undefined') {
return undefined
}

// If there is an entry, store it in the cache and return it.
revalidateSeconds = ssgRoute.initialRevalidateSeconds
this.revalidateSeconds.set(route, revalidateSeconds)

return revalidateSeconds
}

private calculateRevalidate(
pathname: string,
fromTime: number,
Expand All @@ -199,12 +223,10 @@ export class IncrementalCache implements IncrementalCacheType {
if (dev) return new Date().getTime() - 1000

// if an entry isn't present in routes we fallback to a default
// of revalidating after 1 second
const { initialRevalidateSeconds } = this.prerenderManifest.routes[
toRoute(pathname)
] || {
initialRevalidateSeconds: 1,
}
// of revalidating after 1 second.
const initialRevalidateSeconds =
this.getRevalidateSeconds(toRoute(pathname)) ?? 1

const revalidateAfter =
typeof initialRevalidateSeconds === 'number'
? initialRevalidateSeconds * 1000 + fromTime
Expand Down Expand Up @@ -485,8 +507,7 @@ export class IncrementalCache implements IncrementalCacheType {
}
}

const curRevalidate =
this.prerenderManifest.routes[toRoute(cacheKey)]?.initialRevalidateSeconds
const curRevalidate = this.getRevalidateSeconds(toRoute(cacheKey))

let isStale: boolean | -1 | undefined
let revalidateAfter: false | number
Expand Down Expand Up @@ -584,22 +605,12 @@ export class IncrementalCache implements IncrementalCacheType {
pathname = this._getPathname(pathname, ctx.fetchCache)

try {
// we use the prerender manifest memory instance
// to store revalidate timings for calculating
// revalidateAfter values so we update this on set
// Set the value for the revalidate seconds so if it changes we can
// update the cache with the new value.
if (typeof ctx.revalidate !== 'undefined' && !ctx.fetchCache) {
this.prerenderManifest.routes[pathname] = {
experimentalPPR: undefined,
dataRoute: path.posix.join(
'/_next/data',
`${normalizePagePath(pathname)}.json`
),
srcRoute: null, // FIXME: provide actual source route, however, when dynamically appending it doesn't really matter
initialRevalidateSeconds: ctx.revalidate,
// Pages routes do not have a prefetch data route.
prefetchDataRoute: undefined,
}
this.revalidateSeconds.set(pathname, ctx.revalidate)
}

await this.cacheHandler?.set(pathname, data, ctx)
} catch (error) {
console.warn('Failed to update prerender cache for', pathname, error)
Expand Down
13 changes: 11 additions & 2 deletions packages/next/src/server/load-manifest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync } from 'fs'
import { runInNewContext } from 'vm'
import { freeze } from './lib/freeze'

const cache = new Map<string, unknown>()

Expand All @@ -8,13 +9,17 @@ export function loadManifest(
shouldCache: boolean = true
): unknown {
const cached = shouldCache && cache.get(path)

if (cached) {
return cached
}

const manifest = JSON.parse(readFileSync(path, 'utf8'))

// Freeze the manifest so it cannot be modified if we're caching it.
if (shouldCache) {
freeze(manifest)
}

if (shouldCache) {
cache.set(path, manifest)
}
Expand All @@ -27,7 +32,6 @@ export function evalManifest(
shouldCache: boolean = true
): unknown {
const cached = shouldCache && cache.get(path)

if (cached) {
return cached
}
Expand All @@ -40,6 +44,11 @@ export function evalManifest(
const contextObject = {}
runInNewContext(content, contextObject)

// Freeze the context object so it cannot be modified if we're caching it.
if (shouldCache) {
freeze(contextObject)
}

if (shouldCache) {
cache.set(path, contextObject)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1768,11 +1768,11 @@ export default class NextNodeServer extends BaseServer {
return this._cachedPreviewManifest
}

const manifest = loadManifest(
this._cachedPreviewManifest = loadManifest(
join(this.distDir, PRERENDER_MANIFEST)
) as PrerenderManifest

return (this._cachedPreviewManifest = manifest)
return this._cachedPreviewManifest
}

protected getRoutesManifest(): NormalizedRouteManifest | undefined {
Expand Down

0 comments on commit 52b0358

Please sign in to comment.