perf: 优化 frontmatter 生成
This commit is contained in:
parent
5a600048d5
commit
025a6bf680
@ -2,6 +2,7 @@
|
||||
"name": "@vuepress-plume/plugin-auto-frontmatter",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.72",
|
||||
"private": true,
|
||||
"description": "The Plugin for VuePress 2 - auto frontmatter",
|
||||
"author": "pengzhanbo <volodymyr@foxmail.com>",
|
||||
"license": "MIT",
|
||||
|
||||
122
theme/src/node/autoFrontmatter/generator.ts
Normal file
122
theme/src/node/autoFrontmatter/generator.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { fs } from 'vuepress/utils'
|
||||
import chokidar from 'chokidar'
|
||||
import { createFilter } from 'create-filter'
|
||||
import grayMatter from 'gray-matter'
|
||||
import jsonToYaml from 'json2yaml'
|
||||
import { isArray, isEmptyObject, promiseParallel, toArray } from '@pengzhanbo/utils'
|
||||
import type { App } from 'vuepress'
|
||||
import type {
|
||||
AutoFrontmatter,
|
||||
AutoFrontmatterArray,
|
||||
AutoFrontmatterMarkdownFile,
|
||||
AutoFrontmatterObject,
|
||||
} from '../../shared/auto-frontmatter.js'
|
||||
import type { PlumeThemeLocaleOptions } from '../../shared/index.js'
|
||||
import { readMarkdown, readMarkdownList } from './readFile.js'
|
||||
import { resolveOptions } from './resolveOptions.js'
|
||||
|
||||
export interface Generate {
|
||||
globFilter: (id?: string) => boolean
|
||||
global: AutoFrontmatterObject
|
||||
rules: {
|
||||
include: string | string[]
|
||||
filter: (id?: string) => boolean
|
||||
frontmatter: AutoFrontmatterObject
|
||||
}[]
|
||||
}
|
||||
|
||||
let generate: Generate | null = null
|
||||
|
||||
export function initAutoFrontmatter(
|
||||
localeOptions: PlumeThemeLocaleOptions,
|
||||
autoFrontmatter: AutoFrontmatter = {},
|
||||
) {
|
||||
const { include, exclude, frontmatter = {} } = resolveOptions(localeOptions, autoFrontmatter)
|
||||
|
||||
const globFilter = createFilter(include, exclude, { resolve: false })
|
||||
|
||||
const userConfig: AutoFrontmatterArray = isArray(frontmatter)
|
||||
? frontmatter
|
||||
: [{ include: '*', frontmatter }]
|
||||
|
||||
const globalConfig: AutoFrontmatterObject
|
||||
= userConfig.find(({ include }) => include === '*')?.frontmatter || {}
|
||||
|
||||
const rules = userConfig
|
||||
.filter(({ include }) => include !== '*')
|
||||
.map(({ include, frontmatter }) => {
|
||||
return {
|
||||
include,
|
||||
filter: createFilter(toArray(include), undefined, { resolve: false }),
|
||||
frontmatter,
|
||||
}
|
||||
})
|
||||
|
||||
generate = {
|
||||
globFilter,
|
||||
global: globalConfig,
|
||||
rules,
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateAFrontmatter(app: App) {
|
||||
if (!generate)
|
||||
return
|
||||
const markdownList = await readMarkdownList(app.dir.source(), generate.globFilter)
|
||||
await promiseParallel(
|
||||
markdownList.map(file => () => generator(file)),
|
||||
64,
|
||||
)
|
||||
}
|
||||
|
||||
export async function watchAutoFrontmatter(app: App, watchers: any[]) {
|
||||
if (!generate)
|
||||
return
|
||||
|
||||
const watcher = chokidar.watch('**/*.md', {
|
||||
cwd: app.dir.source(),
|
||||
ignoreInitial: true,
|
||||
ignored: /(node_modules|\.vuepress)\//,
|
||||
})
|
||||
|
||||
watcher.on('add', async (relativePath) => {
|
||||
if (!generate!.globFilter(relativePath))
|
||||
return
|
||||
const file = await readMarkdown(app.dir.source(), relativePath)
|
||||
await generator(file)
|
||||
})
|
||||
|
||||
watchers.push(watcher)
|
||||
}
|
||||
|
||||
async function generator(file: AutoFrontmatterMarkdownFile): Promise<void> {
|
||||
if (!generate)
|
||||
return
|
||||
|
||||
const { filepath, relativePath } = file
|
||||
|
||||
const current = generate.rules.find(({ filter }) => filter(relativePath))
|
||||
const formatter = current?.frontmatter || generate.global
|
||||
const { data, content } = grayMatter(file.content)
|
||||
|
||||
for (const key in formatter) {
|
||||
const value = await formatter[key](data[key], file, data)
|
||||
data[key] = value ?? data[key]
|
||||
}
|
||||
|
||||
try {
|
||||
const yaml = isEmptyObject(data)
|
||||
? ''
|
||||
: jsonToYaml
|
||||
.stringify(data)
|
||||
.replace(/\n\s{2}/g, '\n')
|
||||
.replace(/"/g, '')
|
||||
.replace(/\s+\n/g, '\n')
|
||||
const newContent = yaml ? `${yaml}---\n${content}` : content
|
||||
|
||||
fs.writeFileSync(filepath, newContent, 'utf-8')
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
3
theme/src/node/autoFrontmatter/index.ts
Normal file
3
theme/src/node/autoFrontmatter/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './generator.js'
|
||||
export * from './readFile.js'
|
||||
export * from './resolveOptions.js'
|
||||
38
theme/src/node/autoFrontmatter/readFile.ts
Normal file
38
theme/src/node/autoFrontmatter/readFile.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import fg from 'fast-glob'
|
||||
import type { AutoFrontmatterMarkdownFile } from '../../shared/auto-frontmatter.js'
|
||||
|
||||
export async function readMarkdownList(
|
||||
sourceDir: string,
|
||||
filter: (id: string) => boolean,
|
||||
): Promise<AutoFrontmatterMarkdownFile[]> {
|
||||
const files: string[] = await fg(['**/*.md'], {
|
||||
cwd: sourceDir,
|
||||
ignore: ['node_modules', '.vuepress'],
|
||||
})
|
||||
|
||||
return await Promise.all(
|
||||
files
|
||||
.filter(filter)
|
||||
.map(file => readMarkdown(sourceDir, file)),
|
||||
)
|
||||
}
|
||||
|
||||
export async function readMarkdown(
|
||||
sourceDir: string,
|
||||
relativePath: string,
|
||||
): Promise<AutoFrontmatterMarkdownFile> {
|
||||
const filepath = path.join(sourceDir, relativePath)
|
||||
const stats = await fs.promises.stat(filepath)
|
||||
return {
|
||||
filepath,
|
||||
relativePath,
|
||||
content: await fs.promises.readFile(filepath, 'utf-8'),
|
||||
createTime: getFileCreateTime(stats),
|
||||
stats,
|
||||
}
|
||||
}
|
||||
|
||||
export function getFileCreateTime(stats: fs.Stats): Date {
|
||||
return stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime
|
||||
}
|
||||
@ -1,17 +1,15 @@
|
||||
import { path } from 'vuepress/utils'
|
||||
import { removeLeadingSlash, resolveLocalePath } from 'vuepress/shared'
|
||||
import { ensureLeadingSlash } from '@vuepress/helper'
|
||||
import type {
|
||||
AutoFrontmatterOptions,
|
||||
FrontmatterArray,
|
||||
FrontmatterObject,
|
||||
} from '@vuepress-plume/plugin-auto-frontmatter'
|
||||
import { format } from 'date-fns'
|
||||
import { uniq } from '@pengzhanbo/utils'
|
||||
import type { NotesSidebar } from '@vuepress-plume/plugin-notes-data'
|
||||
import type {
|
||||
AutoFrontmatter,
|
||||
AutoFrontmatterArray,
|
||||
AutoFrontmatterObject,
|
||||
PlumeThemeLocaleOptions,
|
||||
PlumeThemePluginOptions,
|
||||
SidebarItem,
|
||||
} from '../../shared/index.js'
|
||||
import {
|
||||
getCurrentDirname,
|
||||
@ -20,16 +18,15 @@ import {
|
||||
normalizePath,
|
||||
pathJoin,
|
||||
withBase,
|
||||
} from '../utils.js'
|
||||
} from '../utils/index.js'
|
||||
import { resolveNotesOptions } from '../config/index.js'
|
||||
|
||||
export function resolveAutoFrontmatterOptions(
|
||||
pluginOptions: PlumeThemePluginOptions,
|
||||
export function resolveOptions(
|
||||
localeOptions: PlumeThemeLocaleOptions,
|
||||
): AutoFrontmatterOptions {
|
||||
frontmatter: AutoFrontmatter,
|
||||
): AutoFrontmatter {
|
||||
const pkg = getPackage()
|
||||
const { locales = {}, article: articlePrefix = '/article/' } = localeOptions
|
||||
const { frontmatter } = pluginOptions
|
||||
|
||||
const resolveLocale = (relativeFilepath: string) => {
|
||||
const file = ensureLeadingSlash(relativeFilepath)
|
||||
@ -50,7 +47,7 @@ export function resolveAutoFrontmatterOptions(
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
const baseFrontmatter: FrontmatterObject = {
|
||||
const baseFrontmatter: AutoFrontmatterObject = {
|
||||
author(author: string, { relativePath }, data: any) {
|
||||
if (author)
|
||||
return author
|
||||
@ -197,26 +194,33 @@ export function resolveAutoFrontmatterOptions(
|
||||
},
|
||||
},
|
||||
},
|
||||
].filter(Boolean) as FrontmatterArray,
|
||||
].filter(Boolean) as AutoFrontmatterArray,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveLinkBySidebar(
|
||||
sidebar: NotesSidebar,
|
||||
prefix: string,
|
||||
sidebar: 'auto' | (string | SidebarItem)[],
|
||||
_prefix: string,
|
||||
) {
|
||||
const res: Record<string, string> = {}
|
||||
|
||||
if (sidebar === 'auto') {
|
||||
return res
|
||||
}
|
||||
|
||||
for (const item of sidebar) {
|
||||
if (typeof item !== 'string') {
|
||||
const { dir = '', link = '/', items, text = '' } = item
|
||||
SidebarLink(items, link, text, pathJoin(prefix, dir), res)
|
||||
const { prefix, dir = '', link = '/', items, text = '' } = item
|
||||
getSidebarLink(items, link, text, pathJoin(_prefix, prefix || dir), res)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function SidebarLink(items: NotesSidebar | undefined, link: string, text: string, dir = '', res: Record<string, string> = {}) {
|
||||
function getSidebarLink(items: 'auto' | (string | SidebarItem)[] | undefined, link: string, text: string, dir = '', res: Record<string, string> = {}) {
|
||||
if (items === 'auto')
|
||||
return
|
||||
|
||||
if (!items) {
|
||||
res[pathJoin(dir, `${text}.md`)] = link
|
||||
return
|
||||
@ -237,8 +241,8 @@ function SidebarLink(items: NotesSidebar | undefined, link: string, text: string
|
||||
res[dir] = link
|
||||
}
|
||||
else {
|
||||
const { dir: subDir = '', link: subLink = '/', items: subItems, text: subText = '' } = item
|
||||
SidebarLink(subItems, pathJoin(link, subLink), subText, pathJoin(dir, subDir), res)
|
||||
const { prefix, dir: subDir = '', link: subLink = '/', items: subItems, text: subText = '' } = item
|
||||
getSidebarLink(subItems, pathJoin(link, subLink), subText, pathJoin(prefix || dir, subDir), res)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user