From 8c3e5f0f067180d97719120b6705d3ca4f11e7e3 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Mon, 24 Jun 2024 00:10:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=E6=96=B0=E5=A2=9E=20markdown=20?= =?UTF-8?q?render=20=E7=BC=93=E5=AD=98=EF=BC=8C=E4=BC=98=E5=8C=96=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=9C=8D=E5=8A=A1=E5=90=AF=E5=8A=A8=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.js | 2 +- theme/src/node/extendsMarkdown.ts | 111 ++++++++++++++++++++++++++++++ theme/src/node/theme.ts | 3 + 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 theme/src/node/extendsMarkdown.ts diff --git a/eslint.config.js b/eslint.config.js index a9aade18..79b2984a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,7 +2,7 @@ import config from '@pengzhanbo/eslint-config-vue' export default config({ // todo: 正则校验 - // 当前项目中的 正则 海冰不能完全通过 规则,存在 53 个问题 + // 当前项目中的 正则 还并不能完全通过 规则,存在 53 个问题 // 但处理起来比较麻烦,因此将会作为一项比较长期的工作来完成。 regexp: false, ignores: [ diff --git a/theme/src/node/extendsMarkdown.ts b/theme/src/node/extendsMarkdown.ts new file mode 100644 index 00000000..8260dcd2 --- /dev/null +++ b/theme/src/node/extendsMarkdown.ts @@ -0,0 +1,111 @@ +/** + * 针对主题使用了 shiki + twoslash, 以及各种各样的对 markdown 的扩展, + * 导致了 markdown render 的速度变得越来越慢,如果每次启动都全量编译,那么时间开销会非常夸张。 + * 因此,对 markdown render 包装一层 缓存,通过 content hash 对比内容是否有更新, + * 没有更新的直接应用缓存从而跳过编译过程,加快启动速度。 + * + * 此功能计划做成独立的插件,但还不确定是放在 vuepress/ecosystem 还是在 主题插件内, + * 也有可能到 vuepress/core 仓库中进行更深度的优化。 + * 因此,先在本主题中进行 实验性验证。 + * + * 使用此功能后,本主题原本的启动耗时,由每次 13s 左右 优化到 二次启动时 1.2s 左右。 + * 基本只剩下 vuepress 本身的开销和 加载 shiki 所有语言带来 0.5s 左右的开销。 + */ +import { createHash } from 'node:crypto' +import type { App } from 'vuepress' +import type { Markdown, MarkdownEnv } from 'vuepress/markdown' +import { fs } from 'vuepress/utils' + +interface CacheContent { + content: string + env: MarkdownEnv +} + +const cacheDir = 'markdown/render' +const metaFile = '_metadata.json' + +export async function extendsMarkdown(md: Markdown, app: App): Promise { + // 如果是在 构建阶段,且缓存文件夹不存在,则不进行缓存 + // 因为构建阶段仅一次性产物,生成缓存资源反而会带来额外的开销 + if (app.env.isBuild && !fs.existsSync(app.dir.cache())) { + return + } + + await fs.ensureDir(app.dir.cache(cacheDir)) + const metadata = await readMetadata(app) + + const writeCache = (filepath: string, cache: CacheContent) => { + const cachePath = app.dir.cache(cacheDir, filepath) + const content = JSON.stringify(cache) + fs.writeFileSync(cachePath, content, 'utf-8') + } + + const readCache = (filepath: string): CacheContent | null => { + const cachePath = app.dir.cache(cacheDir, filepath) + try { + const content = fs.readFileSync(cachePath, 'utf-8') + return JSON.parse(content) as CacheContent + } + catch {} + + return null + } + + const rawRender = md.render + md.render = (input, env: MarkdownEnv) => { + const filepath = env.filePathRelative + + if (!filepath) { + return rawRender(input, env) + } + + const hash = getContentHash(input) + const cachePath = normalizePath(filepath) + + if (metadata[filepath] === hash) { + const cache = readCache(cachePath) + if (cache) { + Object.assign(env, cache.env) + return cache.content + } + } + + metadata[filepath] = hash + + const renderedContent = rawRender(input, env) + + writeCache(cachePath, { content: renderedContent, env }) + updateMetadata(app, metadata) + return renderedContent + } +} + +async function readMetadata(app: App): Promise> { + const filepath = app.dir.cache(cacheDir, metaFile) + try { + const content = await fs.readFile(filepath, 'utf-8') + return JSON.parse(content) + } + catch {} + return {} +} + +let timer: NodeJS.Timeout | null = null +function updateMetadata(app: App, metadata: Record) { + const filepath = app.dir.cache(cacheDir, metaFile) + timer && clearTimeout(timer) + timer = setTimeout( + async () => await fs.writeFile(filepath, JSON.stringify(metadata), 'utf-8'), + 200, + ) +} + +function normalizePath(filepath: string) { + return getContentHash(filepath) +} + +function getContentHash(content: string): string { + const hash = createHash('md5') + hash.update(content) + return hash.digest('hex') +} diff --git a/theme/src/node/theme.ts b/theme/src/node/theme.ts index 251cdffa..c47bafe6 100644 --- a/theme/src/node/theme.ts +++ b/theme/src/node/theme.ts @@ -13,6 +13,7 @@ import { templateBuildRenderer, } from './config/index.js' import { setupPrepare, watchPrepare } from './prepare/index.js' +import { extendsMarkdown } from './extendsMarkdown.js' export function plumeTheme(options: PlumeThemeOptions = {}): Theme { const { @@ -55,6 +56,8 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme { resolvePageHead(page, localeOptions) }, + extendsMarkdown, + extendsBundlerOptions, templateBuildRenderer,