docs: huge migration to runtime asset cache
This commit is contained in:
85
packages/docs/src/asset-builder/asset-builder.ts
Normal file
85
packages/docs/src/asset-builder/asset-builder.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { RequestHandler } from "express"
|
||||
import { createHash } from "node:crypto"
|
||||
import { readFileSync } from "node:fs"
|
||||
import { mkdir, stat, writeFile } from "node:fs/promises"
|
||||
import { dirname, extname, join, parse } from "node:path"
|
||||
|
||||
export type Asset = {
|
||||
file: string
|
||||
url: string
|
||||
content: Buffer
|
||||
}
|
||||
|
||||
export type AssetTransformer = {
|
||||
transform: (asset: Asset) => Promise<AssetTransformResult | undefined>
|
||||
}
|
||||
|
||||
export type AssetTransformResult = {
|
||||
content: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export class AssetBuilder {
|
||||
// map of asset urls to asset objects
|
||||
private library = new Map<string, Asset>()
|
||||
|
||||
constructor(
|
||||
private cacheFolder: string,
|
||||
private transformers: AssetTransformer[],
|
||||
) {}
|
||||
|
||||
// accepts a path to a file, then returns a url to where the built file will be served
|
||||
// the url will include a hash of the file contents
|
||||
file(file: string | URL): string {
|
||||
if (file instanceof URL) {
|
||||
file = file.pathname
|
||||
}
|
||||
|
||||
const existing = this.library.get(file)
|
||||
if (existing) {
|
||||
return existing.url
|
||||
}
|
||||
|
||||
const content = readFileSync(file)
|
||||
const hash = createHash("sha256").update(content).digest("hex").slice(0, 8)
|
||||
|
||||
const { name, ext } = parse(file)
|
||||
const url = `/${name}.${hash}${ext}`
|
||||
this.library.set(url, { file, url, content })
|
||||
return url
|
||||
}
|
||||
|
||||
middleware(): RequestHandler {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const asset = this.library.get(req.path)
|
||||
if (!asset) return next()
|
||||
|
||||
const file = join(this.cacheFolder, asset.url)
|
||||
const extension = extname(file)
|
||||
|
||||
const stats = await stat(file).catch(() => undefined)
|
||||
if (stats?.isFile()) {
|
||||
res
|
||||
.status(200)
|
||||
.type(extension.endsWith("tsx") ? "text/javascript" : extension)
|
||||
.sendFile(file)
|
||||
return
|
||||
}
|
||||
|
||||
for (const transformer of this.transformers) {
|
||||
const result = await transformer.transform(asset)
|
||||
if (result) {
|
||||
await mkdir(dirname(file), { recursive: true })
|
||||
await writeFile(file, result.content)
|
||||
return res.type(extension).send(result.content)
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user