aed2dc0c85
x-ref: [internal notes](https://www.notion.so/vercel/App-Static-Generation-dc5f1e0916684501b586e56a5b7b9483)
136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
import LRUCache from 'next/dist/compiled/lru-cache'
|
|
import path from '../../../shared/lib/isomorphic/path'
|
|
import type { CacheHandler, CacheHandlerContext, CacheHandlerValue } from './'
|
|
|
|
export default class FileSystemCache implements CacheHandler {
|
|
private fs: CacheHandlerContext['fs']
|
|
private flushToDisk?: CacheHandlerContext['flushToDisk']
|
|
private serverDistDir: CacheHandlerContext['serverDistDir']
|
|
private memoryCache?: LRUCache<string, CacheHandlerValue>
|
|
private appDir: boolean
|
|
|
|
constructor(ctx: CacheHandlerContext) {
|
|
this.fs = ctx.fs
|
|
this.flushToDisk = ctx.flushToDisk
|
|
this.serverDistDir = ctx.serverDistDir
|
|
this.appDir = !!ctx._appDir
|
|
|
|
if (ctx.maxMemoryCacheSize) {
|
|
this.memoryCache = new LRUCache({
|
|
max: ctx.maxMemoryCacheSize,
|
|
length({ value }) {
|
|
if (!value) {
|
|
return 25
|
|
} else if (value.kind === 'REDIRECT') {
|
|
return JSON.stringify(value.props).length
|
|
} else if (value.kind === 'IMAGE') {
|
|
throw new Error('invariant image should not be incremental-cache')
|
|
}
|
|
// rough estimate of size of cache value
|
|
return (
|
|
value.html.length + (JSON.stringify(value.pageData)?.length || 0)
|
|
)
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
public async get(key: string) {
|
|
let data = this.memoryCache?.get(key)
|
|
|
|
// let's check the disk for seed data
|
|
if (!data) {
|
|
try {
|
|
const { filePath: htmlPath, isAppPath } = await this.getFsPath(
|
|
`${key}.html`
|
|
)
|
|
const html = await this.fs.readFile(htmlPath)
|
|
const pageData = isAppPath
|
|
? await this.fs.readFile(
|
|
(
|
|
await this.getFsPath(`${key}.rsc`, true)
|
|
).filePath
|
|
)
|
|
: JSON.parse(
|
|
await this.fs.readFile(
|
|
await (
|
|
await this.getFsPath(`${key}.json`, false)
|
|
).filePath
|
|
)
|
|
)
|
|
const { mtime } = await this.fs.stat(htmlPath)
|
|
|
|
data = {
|
|
lastModified: mtime.getTime(),
|
|
value: {
|
|
kind: 'PAGE',
|
|
html,
|
|
pageData,
|
|
},
|
|
}
|
|
this.memoryCache?.set(key, data)
|
|
} catch (_) {
|
|
// unable to get data from disk
|
|
}
|
|
}
|
|
return data || null
|
|
}
|
|
|
|
public async set(key: string, data: CacheHandlerValue['value']) {
|
|
if (!this.flushToDisk) return
|
|
|
|
this.memoryCache?.set(key, {
|
|
value: data,
|
|
lastModified: Date.now(),
|
|
})
|
|
|
|
if (data?.kind === 'PAGE') {
|
|
const isAppPath = typeof data.pageData === 'string'
|
|
const { filePath: htmlPath } = await this.getFsPath(
|
|
`${key}.html`,
|
|
isAppPath
|
|
)
|
|
await this.fs.mkdir(path.dirname(htmlPath))
|
|
await this.fs.writeFile(htmlPath, data.html)
|
|
|
|
await this.fs.writeFile(
|
|
(
|
|
await this.getFsPath(
|
|
`${key}.${isAppPath ? 'rsc' : 'json'}`,
|
|
isAppPath
|
|
)
|
|
).filePath,
|
|
isAppPath ? data.pageData : JSON.stringify(data.pageData)
|
|
)
|
|
}
|
|
}
|
|
|
|
private async getFsPath(
|
|
pathname: string,
|
|
appDir?: boolean
|
|
): Promise<{
|
|
filePath: string
|
|
isAppPath: boolean
|
|
}> {
|
|
let isAppPath = false
|
|
let filePath = path.join(this.serverDistDir, 'pages', pathname)
|
|
|
|
if (!this.appDir || appDir === false)
|
|
return {
|
|
filePath,
|
|
isAppPath,
|
|
}
|
|
try {
|
|
await this.fs.readFile(filePath)
|
|
return {
|
|
filePath,
|
|
isAppPath,
|
|
}
|
|
} catch (err) {
|
|
return {
|
|
filePath: path.join(this.serverDistDir, 'app', pathname),
|
|
isAppPath: true,
|
|
}
|
|
}
|
|
}
|
|
}
|