feat(theme): add @vuepress/plugin-cache
This commit is contained in:
parent
688c96452e
commit
62ac0b3371
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@ -5,13 +5,19 @@
|
||||
"name": "dev",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"command": "pnpm run dev"
|
||||
"command": "pnpm dev"
|
||||
},
|
||||
{
|
||||
"name": "build",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"command": "pnpm build"
|
||||
},
|
||||
{
|
||||
"name": "docs:dev",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "pnpm run docs:dev"
|
||||
"command": "pnpm docs:dev"
|
||||
},
|
||||
{
|
||||
"name": "docs:build",
|
||||
|
||||
@ -161,6 +161,26 @@ interface BlogOptions {
|
||||
}
|
||||
```
|
||||
|
||||
### cache
|
||||
|
||||
- 类型: `false | 'memory' | 'filesystem'`
|
||||
- 默认值: `filesystem`
|
||||
- 详情:
|
||||
|
||||
是否启用 编译缓存,或配置缓存方式
|
||||
|
||||
此配置项用于解决 VuePress 启动速度慢的问题,在首次启动服务时,对编译结果进行缓存,二次启动时
|
||||
直接读取缓存,跳过编译,从而加快启动速度。
|
||||
|
||||
- `false`:禁用 缓存
|
||||
- `'memory'`:使用内存缓存,此方式可获得更快的启动速度,但随着项目文件数量增加,内存占用会增加,
|
||||
适合文章数量较少的项目使用
|
||||
- `'filesystem'`:使用文件系统缓存,此方式可获得相对快且稳定的启动速度,更适合内容多的项目使用
|
||||
|
||||
::: warning
|
||||
该字段不支持在 [主题配置文件 `plume.config.js`](./配置说明.md#主题配置文件) 中进行配置。
|
||||
:::
|
||||
|
||||
### locales
|
||||
|
||||
- 类型: `Record<string, PlumeThemeLocaleConfig>`
|
||||
|
||||
@ -19,11 +19,10 @@ import {
|
||||
transformerRenderWhitespace,
|
||||
} from '@shikijs/transformers'
|
||||
import type { HighlighterOptions, ThemeOptions } from './types.js'
|
||||
import { LRUCache, attrsToLines, resolveLanguage } from './utils/index.js'
|
||||
import { attrsToLines, resolveLanguage } from './utils/index.js'
|
||||
import { defaultHoverInfoProcessor, transformerTwoslash } from './twoslash/rendererTransformer.js'
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
|
||||
const cache = new LRUCache<string, string>(64)
|
||||
|
||||
const vueRE = /-vue$/
|
||||
const mustacheRE = /\{\{.*?\}\}/g
|
||||
@ -32,7 +31,6 @@ const decorationsRE = /^\/\/ @decorations:(.*)\n/
|
||||
export async function highlight(
|
||||
theme: ThemeOptions,
|
||||
options: HighlighterOptions,
|
||||
isDev: boolean,
|
||||
): Promise<(str: string, lang: string, attrs: string) => string> {
|
||||
const {
|
||||
defaultHighlightLang: defaultLang = '',
|
||||
@ -95,14 +93,6 @@ export async function highlight(
|
||||
let lang = resolveLanguage(language) || defaultLang
|
||||
const vPre = vueRE.test(lang) ? '' : 'v-pre'
|
||||
|
||||
const key = str + language + attrs
|
||||
|
||||
if (isDev) {
|
||||
const rendered = cache.get(key)
|
||||
if (rendered)
|
||||
return rendered
|
||||
}
|
||||
|
||||
if (lang) {
|
||||
const langLoaded = loadedLanguages.includes(lang as any)
|
||||
if (!langLoaded && !isPlainLang(lang) && !isSpecialLang(lang)) {
|
||||
@ -183,9 +173,6 @@ export async function highlight(
|
||||
|
||||
const rendered = restoreMustache(highlighted)
|
||||
|
||||
if (isDev)
|
||||
cache.set(key, rendered)
|
||||
|
||||
return rendered
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@ -51,7 +51,7 @@ export function shikiPlugin({
|
||||
extendsMarkdown: async (md, app) => {
|
||||
const theme = options.theme ?? { light: 'github-light', dark: 'github-dark' }
|
||||
|
||||
md.options.highlight = await highlight(theme, options, app.env.isDev)
|
||||
md.options.highlight = await highlight(theme, options)
|
||||
|
||||
md.use(highlightLinesPlugin)
|
||||
md.use<PreWrapperOptions>(preWrapperPlugin, {
|
||||
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@ -259,6 +259,9 @@ importers:
|
||||
|
||||
theme:
|
||||
dependencies:
|
||||
'@iconify/vue':
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2(vue@3.4.33(typescript@5.5.3))
|
||||
'@pengzhanbo/utils':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
@ -283,6 +286,9 @@ importers:
|
||||
'@vuepress/plugin-active-header-links':
|
||||
specifier: 2.0.0-rc.39
|
||||
version: 2.0.0-rc.39(typescript@5.5.3)(vuepress@2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3)))
|
||||
'@vuepress/plugin-cache':
|
||||
specifier: 2.0.0-rc.39
|
||||
version: 2.0.0-rc.39(vuepress@2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3)))
|
||||
'@vuepress/plugin-comment':
|
||||
specifier: 2.0.0-rc.39
|
||||
version: 2.0.0-rc.39(typescript@5.5.3)(vuepress@2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3)))
|
||||
@ -361,6 +367,10 @@ importers:
|
||||
vuepress-plugin-md-power:
|
||||
specifier: workspace:*
|
||||
version: link:../plugins/plugin-md-power
|
||||
devDependencies:
|
||||
'@iconify/json':
|
||||
specifier: ^2.2.229
|
||||
version: 2.2.229
|
||||
|
||||
packages:
|
||||
|
||||
@ -1934,6 +1944,11 @@ packages:
|
||||
peerDependencies:
|
||||
vuepress: 2.0.0-rc.14
|
||||
|
||||
'@vuepress/plugin-cache@2.0.0-rc.39':
|
||||
resolution: {integrity: sha512-PVsC797lGMuu8L7jtW9vv2hYM+d5qq5fbWwBJuSyRXEdpcwryhAjGWnz9F19dYe5KWLYG6EbCoANTQObmiyBag==}
|
||||
peerDependencies:
|
||||
vuepress: 2.0.0-rc.14
|
||||
|
||||
'@vuepress/plugin-comment@2.0.0-rc.39':
|
||||
resolution: {integrity: sha512-/oCS+0wH/MtE4c1HUKlqH/tj70oXSz/tfR1hsHj8F8wiZ+IVJxexvtzMKk0vdRmYnH4nqeZh6dg5ggSJjrLEZQ==}
|
||||
peerDependencies:
|
||||
@ -3981,14 +3996,13 @@ packages:
|
||||
resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
lru-cache@10.0.1:
|
||||
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
|
||||
lru-cache@10.0.2:
|
||||
resolution: {integrity: sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==}
|
||||
engines: {node: 14 || >=16.14}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru-cache@11.0.0:
|
||||
resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==}
|
||||
engines: {node: 20 || >=22}
|
||||
@ -7269,6 +7283,11 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- typescript
|
||||
|
||||
'@vuepress/plugin-cache@2.0.0-rc.39(vuepress@2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3)))':
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
vuepress: 2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3))
|
||||
|
||||
'@vuepress/plugin-comment@2.0.0-rc.39(typescript@5.5.3)(vuepress@2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3)))':
|
||||
dependencies:
|
||||
'@vuepress/helper': 2.0.0-rc.39(typescript@5.5.3)(vuepress@2.0.0-rc.14(@vuepress/bundler-vite@2.0.0-rc.14(@types/node@20.12.10)(jiti@1.21.6)(tsx@4.16.0)(typescript@5.5.3)(yaml@2.4.2))(typescript@5.5.3)(vue@3.4.33(typescript@5.5.3)))
|
||||
@ -9534,11 +9553,11 @@ snapshots:
|
||||
|
||||
longest@2.0.1: {}
|
||||
|
||||
lru-cache@10.0.1: {}
|
||||
|
||||
lru-cache@10.0.2:
|
||||
dependencies:
|
||||
semver: 7.6.0
|
||||
semver: 7.6.3
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@11.0.0: {}
|
||||
|
||||
@ -10331,7 +10350,7 @@ snapshots:
|
||||
|
||||
path-scurry@1.10.1:
|
||||
dependencies:
|
||||
lru-cache: 10.0.1
|
||||
lru-cache: 10.0.2
|
||||
minipass: 5.0.0
|
||||
|
||||
path-scurry@2.0.0:
|
||||
|
||||
@ -59,9 +59,16 @@
|
||||
"tsup:watch": "tsup --config tsup.config.ts --watch"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@iconify/json": "^2",
|
||||
"vuepress": "2.0.0-rc.14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@iconify/json": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@pengzhanbo/utils": "^1.1.2",
|
||||
"@vuepress-plume/plugin-content-update": "workspace:*",
|
||||
"@vuepress-plume/plugin-fonts": "workspace:*",
|
||||
@ -70,6 +77,7 @@
|
||||
"@vuepress-plume/plugin-shikiji": "workspace:*",
|
||||
"@vuepress/helper": "2.0.0-rc.39",
|
||||
"@vuepress/plugin-active-header-links": "2.0.0-rc.39",
|
||||
"@vuepress/plugin-cache": "2.0.0-rc.39",
|
||||
"@vuepress/plugin-comment": "2.0.0-rc.39",
|
||||
"@vuepress/plugin-docsearch": "2.0.0-rc.39",
|
||||
"@vuepress/plugin-git": "2.0.0-rc.38",
|
||||
@ -95,5 +103,8 @@
|
||||
"vue-router": "^4.4.0",
|
||||
"vuepress-plugin-md-enhance": "2.0.0-rc.52",
|
||||
"vuepress-plugin-md-power": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.229"
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ export function resolveThemeOptions({
|
||||
plugins,
|
||||
hostname,
|
||||
configFile,
|
||||
cache,
|
||||
...localeOptions
|
||||
}: PlumeThemeOptions) {
|
||||
const pluginOptions = plugins ?? themePlugins ?? {}
|
||||
@ -17,6 +18,7 @@ export function resolveThemeOptions({
|
||||
}
|
||||
|
||||
return {
|
||||
cache,
|
||||
configFile,
|
||||
pluginOptions,
|
||||
hostname,
|
||||
|
||||
@ -1,134 +0,0 @@
|
||||
/**
|
||||
* 针对主题使用了 shiki + twoslash, 以及各种各样的对 markdown 的扩展,
|
||||
* 导致了 markdown render 的速度变得越来越慢,如果每次启动都全量编译,那么时间开销会非常夸张。
|
||||
* 因此,对 markdown render 包装一层 缓存,通过 content hash 对比内容是否有更新,
|
||||
* 没有更新的直接应用缓存从而跳过编译过程,加快启动速度。
|
||||
*
|
||||
* 此功能计划做成独立的插件,但还不确定是放在 vuepress/ecosystem 还是在 主题插件内,
|
||||
* 也有可能到 vuepress/core 仓库中进行更深度的优化。
|
||||
* 因此,先在本主题中进行 实验性验证。
|
||||
*
|
||||
* 使用此功能后,本主题原本的启动耗时,由每次 13s 左右 优化到 二次启动时 1.2s 左右。
|
||||
* 基本只剩下 vuepress 本身的开销和 加载 shiki 所有语言带来 0.5s 左右的开销。
|
||||
*/
|
||||
import process from 'node:process'
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import type { App } from 'vuepress'
|
||||
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
|
||||
import { hash } from './utils/index.js'
|
||||
|
||||
export interface CacheData {
|
||||
content: string
|
||||
env: MarkdownEnv
|
||||
}
|
||||
|
||||
// { [filepath]: CacheDta }
|
||||
export type Cache = Record<string, CacheData>
|
||||
|
||||
// { [filepath]: hash }
|
||||
export type Metadata = Record<string, string>
|
||||
|
||||
const CACHE_DIR = 'markdown/rendered'
|
||||
const META_FILE = '_metadata.json'
|
||||
|
||||
export async function extendsMarkdown(md: Markdown, app: App): Promise<void> {
|
||||
if (app.env.isBuild && !fs.existsSync(app.dir.cache(CACHE_DIR))) {
|
||||
return
|
||||
}
|
||||
const basename = app.dir.cache(CACHE_DIR)
|
||||
|
||||
await fs.ensureDir(basename)
|
||||
|
||||
const speed = checkIOSpeed(basename)
|
||||
|
||||
const metaFilepath = `${basename}/${META_FILE}`
|
||||
|
||||
const metadata = (await readFile<Metadata>(metaFilepath)) || {}
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
const update = (filepath: string, data: CacheData): void => {
|
||||
writeFile(`${basename}/${filepath}`, data)
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
timer = setTimeout(async () => writeFile(metaFilepath, metadata), 200)
|
||||
}
|
||||
const rawRender = md.render
|
||||
md.render = (input, env: MarkdownEnv) => {
|
||||
const filepath = env.filePathRelative
|
||||
|
||||
if (!filepath) {
|
||||
return rawRender(input, env)
|
||||
}
|
||||
|
||||
const key = hash(input)
|
||||
const filename = normalizeFilename(filepath)
|
||||
|
||||
if (metadata[filepath] === key) {
|
||||
const cached = readFileSync<CacheData>(`${basename}/${filename}`)
|
||||
if (cached) {
|
||||
Object.assign(env, cached.env)
|
||||
return cached.content
|
||||
}
|
||||
else {
|
||||
metadata[filepath] = ''
|
||||
}
|
||||
}
|
||||
const start = performance.now()
|
||||
const content = rawRender(input, env)
|
||||
|
||||
/**
|
||||
* High-frequency I/O is also a time-consuming operation,
|
||||
* therefore, for render operations with low overhead, caching is not performed.
|
||||
*/
|
||||
if (performance.now() - start > speed) {
|
||||
metadata[filepath] = key
|
||||
update(filename, { content, env })
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeFilename(filename: string): string {
|
||||
return hash(filename).slice(0, 10)
|
||||
}
|
||||
|
||||
async function readFile<T = any>(filepath: string): Promise<T | null> {
|
||||
try {
|
||||
const content = await fs.readFile(filepath, 'utf-8')
|
||||
return JSON.parse(content) as T
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function readFileSync<T = any>(filepath: string): T | null {
|
||||
try {
|
||||
const content = fs.readFileSync(filepath, 'utf-8')
|
||||
return JSON.parse(content) as T
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function writeFile<T = any>(filepath: string, data: T): Promise<void> {
|
||||
return await fs.writeFile(filepath, JSON.stringify(data), 'utf-8')
|
||||
}
|
||||
|
||||
export function checkIOSpeed(cwd = process.cwd()): number {
|
||||
try {
|
||||
const tmp = path.join(cwd, 'tmp')
|
||||
fs.writeFileSync(tmp, '{}', 'utf-8')
|
||||
const start = performance.now()
|
||||
readFileSync(tmp)
|
||||
const end = performance.now()
|
||||
fs.unlinkSync(tmp)
|
||||
return end - start
|
||||
}
|
||||
catch {
|
||||
return 0.15
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import type { App, PluginConfig } from 'vuepress/core'
|
||||
import { activeHeaderLinksPlugin } from '@vuepress/plugin-active-header-links'
|
||||
import { cachePlugin } from '@vuepress/plugin-cache'
|
||||
import { docsearchPlugin } from '@vuepress/plugin-docsearch'
|
||||
import { gitPlugin } from '@vuepress/plugin-git'
|
||||
import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
|
||||
@ -27,12 +28,14 @@ export interface SetupPluginOptions {
|
||||
app: App
|
||||
pluginOptions: PlumeThemePluginOptions
|
||||
hostname?: string
|
||||
cache?: false | 'memory' | 'filesystem'
|
||||
}
|
||||
|
||||
export function getPlugins({
|
||||
app,
|
||||
pluginOptions,
|
||||
hostname,
|
||||
cache,
|
||||
}: SetupPluginOptions): PluginConfig {
|
||||
const isProd = !app.env.isDev
|
||||
|
||||
@ -156,5 +159,9 @@ export function getPlugins({
|
||||
plugins.push(seoPlugin({ hostname }))
|
||||
}
|
||||
|
||||
if (cache !== false) {
|
||||
plugins.push(cachePlugin({ type: cache || 'filesystem' }))
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
|
||||
@ -26,7 +26,6 @@ import {
|
||||
} from './autoFrontmatter/index.js'
|
||||
import { prepareData, watchPrepare } from './prepare/index.js'
|
||||
import { prepareThemeData } from './prepare/prepareThemeData.js'
|
||||
import { extendsMarkdown } from './extendsMarkdown.js'
|
||||
|
||||
export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
|
||||
const {
|
||||
@ -34,6 +33,7 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
|
||||
pluginOptions,
|
||||
hostname,
|
||||
configFile,
|
||||
cache,
|
||||
} = resolveThemeOptions(options)
|
||||
|
||||
return (app) => {
|
||||
@ -65,7 +65,7 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
|
||||
|
||||
alias: resolveAlias(),
|
||||
|
||||
plugins: getPlugins({ app, pluginOptions, hostname }),
|
||||
plugins: getPlugins({ app, pluginOptions, hostname, cache }),
|
||||
|
||||
onInitialized: async (app) => {
|
||||
const { localeOptions } = await waitForConfigLoaded()
|
||||
@ -90,14 +90,14 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
|
||||
},
|
||||
|
||||
extendsPage: async (page) => {
|
||||
const { localeOptions } = await waitForConfigLoaded()
|
||||
await waitForAutoFrontmatter()
|
||||
const { localeOptions, autoFrontmatter } = await waitForConfigLoaded()
|
||||
if ((autoFrontmatter ?? pluginOptions.frontmatter) !== false) {
|
||||
await waitForAutoFrontmatter()
|
||||
}
|
||||
extendsPageData(page as Page<PlumeThemePageData>, localeOptions)
|
||||
resolvePageHead(page, localeOptions)
|
||||
},
|
||||
|
||||
extendsMarkdown,
|
||||
|
||||
extendsBundlerOptions,
|
||||
|
||||
templateBuildRenderer,
|
||||
|
||||
@ -23,6 +23,13 @@ export interface PlumeThemeOptions extends PlumeThemeLocaleOptions {
|
||||
*/
|
||||
hostname?: string
|
||||
|
||||
/**
|
||||
* 是否启用编译缓存
|
||||
*
|
||||
* @default 'filesystem'
|
||||
*/
|
||||
cache?: false | 'memory' | 'filesystem'
|
||||
|
||||
/**
|
||||
* 加密配置
|
||||
*/
|
||||
@ -33,6 +40,9 @@ export interface PlumeThemeOptions extends PlumeThemeLocaleOptions {
|
||||
*/
|
||||
configFile?: string
|
||||
|
||||
/**
|
||||
* 自动插入 frontmatter
|
||||
*/
|
||||
autoFrontmatter?: false | Omit<AutoFrontmatter, 'frontmatter'>
|
||||
|
||||
}
|
||||
|
||||
@ -60,13 +60,16 @@ export interface PlumeThemePluginOptions {
|
||||
* @deprecated
|
||||
* 请使用 [@vuepress/plugin-baidu-analytics](https://ecosystem.vuejs.press/zh/plugins/analytics/baidu-analytics.html) 代替
|
||||
*/
|
||||
baiduTongji?: never
|
||||
baiduTongji?: false | { key: string }
|
||||
|
||||
/**
|
||||
* @deprecated 使用 `autoFrontmatter` 代替
|
||||
*/
|
||||
frontmatter?: Omit<AutoFrontmatter, 'frontmatter'>
|
||||
|
||||
/**
|
||||
* 阅读时间、字数统计
|
||||
*/
|
||||
readingTime?: false | ReadingTimePluginOptions
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user