Skip to content

Commit

Permalink
feat(theme): 新增 markdown render 缓存,优化开发服务启动时间
Browse files Browse the repository at this point in the history
  • Loading branch information
pengzhanbo committed Jun 23, 2024
1 parent 4789f1e commit 8c3e5f0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 1 deletion.
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import config from '@pengzhanbo/eslint-config-vue'

export default config({
// todo: 正则校验
// 当前项目中的 正则 海冰不能完全通过 规则,存在 53 个问题
// 当前项目中的 正则 还并不能完全通过 规则,存在 53 个问题
// 但处理起来比较麻烦,因此将会作为一项比较长期的工作来完成。
regexp: false,
ignores: [
Expand Down
111 changes: 111 additions & 0 deletions theme/src/node/extendsMarkdown.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
// 如果是在 构建阶段,且缓存文件夹不存在,则不进行缓存
// 因为构建阶段仅一次性产物,生成缓存资源反而会带来额外的开销
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<Record<string, string>> {
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<string, string>) {
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')
}
3 changes: 3 additions & 0 deletions theme/src/node/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -55,6 +56,8 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
resolvePageHead(page, localeOptions)
},

extendsMarkdown,

extendsBundlerOptions,

templateBuildRenderer,
Expand Down

0 comments on commit 8c3e5f0

Please sign in to comment.