rsnext/packages/next/server/lib/incremental-cache/file-system-cache.ts

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,
}
}
}
}