From 2a355705ff206a8a12d9fbaf93d3509d976c8dd9 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Thu, 3 Oct 2024 09:26:28 +0800 Subject: [PATCH] feat(theme): add fs cache for encrypt and icons (#248) --- theme/src/node/prepare/prepareEncrypt.ts | 25 ++++++--- theme/src/node/prepare/prepareIcons.ts | 14 ++++- theme/src/node/utils/createFsCache.ts | 65 ++++++++++++++++++++++++ theme/src/node/utils/index.ts | 1 + 4 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 theme/src/node/utils/createFsCache.ts diff --git a/theme/src/node/prepare/prepareEncrypt.ts b/theme/src/node/prepare/prepareEncrypt.ts index 57b7e4f2..5721385d 100644 --- a/theme/src/node/prepare/prepareEncrypt.ts +++ b/theme/src/node/prepare/prepareEncrypt.ts @@ -3,7 +3,7 @@ import type { Page } from 'vuepress/core' import type { PlumeThemeEncrypt, PlumeThemePageData } from '../../shared/index.js' import { isNumber, isString, random, toArray } from '@pengzhanbo/utils' import { genSaltSync, hashSync } from 'bcrypt-ts' -import { hash, logger, resolveContent, writeTemp } from '../utils/index.js' +import { createFsCache, type FsCache, hash, logger, resolveContent, writeTemp } from '../utils/index.js' export type EncryptConfig = readonly [ boolean, // global @@ -16,19 +16,30 @@ export type EncryptConfig = readonly [ const isStringLike = (value: unknown): boolean => isString(value) || isNumber(value) const separator = ':' let contentHash = '' +let fsCache: FsCache<[string, EncryptConfig]> | null = null export async function prepareEncrypt(app: App, encrypt?: PlumeThemeEncrypt) { const start = performance.now() + + if (!fsCache && app.env.isDev) { + fsCache = createFsCache(app, 'encrypt') + await fsCache.read() + } + contentHash = fsCache?.data?.[0] ?? '' + let resolvedEncrypt = fsCache?.data?.[1] const currentHash = encrypt ? hash(JSON.stringify(encrypt)) : '' - if (!contentHash || contentHash !== currentHash) { + if (!contentHash || contentHash !== currentHash || !resolvedEncrypt) { contentHash = currentHash - const content = resolveContent(app, { - name: 'encrypt', - content: resolveEncrypt(encrypt), - }) - await writeTemp(app, 'internal/encrypt.js', content) + resolvedEncrypt = resolveEncrypt(encrypt) } + await writeTemp(app, 'internal/encrypt.js', resolveContent(app, { + name: 'encrypt', + content: resolvedEncrypt, + })) + + fsCache?.write([currentHash, resolvedEncrypt]) + if (app.env.isDebug) { logger.info(`Generate encrypt: ${(performance.now() - start).toFixed(2)}ms`) } diff --git a/theme/src/node/prepare/prepareIcons.ts b/theme/src/node/prepare/prepareIcons.ts index ebb4e3e7..63f48242 100644 --- a/theme/src/node/prepare/prepareIcons.ts +++ b/theme/src/node/prepare/prepareIcons.ts @@ -5,7 +5,7 @@ import { isArray, uniq } from '@pengzhanbo/utils' import { entries, isLinkAbsolute, isLinkHttp, isPlainObject } from '@vuepress/helper' import { isPackageExists } from 'local-pkg' import { fs } from 'vuepress/utils' -import { interopDefault, logger, nanoid, resolveContent, writeTemp } from '../utils/index.js' +import { createFsCache, type FsCache, interopDefault, logger, nanoid, resolveContent, writeTemp } from '../utils/index.js' interface IconData { className: string @@ -26,6 +26,7 @@ const CSS_FILENAME = 'internal/iconify.css' const isInstalled = isPackageExists('@iconify/json') let locate!: ((name: string) => any) +let fsCache: FsCache | null = null // { iconName: { className, content } } const cache: IconDataMap = {} @@ -41,13 +42,21 @@ export async function prepareIcons(app: App, localeOptions: PlumeThemeLocaleOpti await writeTemp(app, JS_FILENAME, resolveContent(app, { name: 'icons', content: '{}' })) return } + if (!fsCache && app.env.isDev) { + fsCache = createFsCache(app, 'iconify') + await fsCache.read() + } const iconList: string[] = [] app.pages.forEach(page => iconList.push(...getIconsWithPage(page))) iconList.push(...getIconWithThemeConfig(localeOptions)) const collectMap: CollectMap = {} - uniq(iconList).filter(icon => !cache[icon]).forEach((iconName) => { + uniq(iconList).filter((icon) => { + if (fsCache?.data?.[icon] && !cache[icon]) + cache[icon] = fsCache.data[icon] + return !cache[icon] + }).forEach((iconName) => { const [collect, name] = iconName.split(':') if (!collectMap[collect]) collectMap[collect] = [] @@ -94,6 +103,7 @@ export async function prepareIcons(app: App, localeOptions: PlumeThemeLocaleOpti })), ]) + fsCache?.write(cache) if (app.env.isDebug) { logger.info(`Generate icons total time: ${(performance.now() - start).toFixed(2)}ms`) } diff --git a/theme/src/node/utils/createFsCache.ts b/theme/src/node/utils/createFsCache.ts new file mode 100644 index 00000000..971a994e --- /dev/null +++ b/theme/src/node/utils/createFsCache.ts @@ -0,0 +1,65 @@ +import type { App } from 'vuepress' +import fs from 'node:fs/promises' +import path from 'node:path' +import { hash } from 'vuepress/utils' + +interface CacheData { + hash: string + data: T | null +} + +export interface FsCache { + hash: string + data: T | null + read: () => Promise + write: (data: T) => Promise +} + +const CACHE_BASE = 'markdown' + +export function createFsCache(app: App, name: string): FsCache { + const filepath = app.dir.cache(`${CACHE_BASE}/${name}.json`) + const cache: CacheData = { hash: '', data: null } + + const read = async (): Promise => { + if (!cache.data) { + try { + const content = await fs.readFile(filepath, 'utf-8') + if (content) { + const res = JSON.parse(content) as CacheData + cache.data = res.data ?? null + cache.hash = hash(res.hash || '') + } + } + catch {} + } + return cache.data + } + + let timer: NodeJS.Timeout | null = null + const write = async (data: T) => { + const currentHash = hash(data) + if (cache.hash && currentHash === cache.hash) + return + + cache.data = data + cache.hash = currentHash + + timer && clearTimeout(timer) + timer = setTimeout(async () => { + await fs.mkdir(path.dirname(filepath), { recursive: true }) + await fs.writeFile(filepath, JSON.stringify(cache), 'utf-8') + }, 300) + } + + return { + get hash() { + return cache.hash + }, + get data() { + return cache.data + }, + read, + write, + } +} diff --git a/theme/src/node/utils/index.ts b/theme/src/node/utils/index.ts index 94695bd9..a017b027 100644 --- a/theme/src/node/utils/index.ts +++ b/theme/src/node/utils/index.ts @@ -4,6 +4,7 @@ export const THEME_NAME = 'vuepress-theme-plume' export const logger = new Logger(THEME_NAME) +export * from './createFsCache.js' export * from './deleteAttrs.js' export * from './hash.js' export * from './interopDefault.js'