refactor(plugin-auto-frontmatter): 重构代码

This commit is contained in:
pengzhanbo 2023-02-09 23:04:33 +08:00
parent e1141bcea0
commit fa5f758b54
8 changed files with 82 additions and 61 deletions

View File

@ -30,13 +30,15 @@ export default {
### options
`{ glob?: string | string[]; formatter: Formatter }`
`{ include?: string | string[]; exclude?: string | string[]; formatter: Formatter }`
- `glob`
glob 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。
默认预设为 `['**/*.md', '!.vuepress/', '!node_modules/']`
自定义匹配将被合并到预设配置中
example: `['blog/**']`
- `include`
include 匹配字符串或数组,匹配需要自动生成 `frontmatter` 的 md文件。
默认预设为 `['**/*.md']`
- `exclude`
exclude 排除不需要的文件
默认预设为: `['!.vuepress/', '!node_modules/']`
- `formatter`
配置`frontmatter`每个字段的生成规则。
@ -58,7 +60,7 @@ export default {
>
type FormatterArray = {
glob: string
include: string
formatter: FormatterObject
}[]
@ -82,7 +84,7 @@ export default {
const formatterArr: Formatter = [
{
// 更精细化的匹配某个 md文件支持glob 匹配字符串
glob: '**/{README,index}.md',
include: '**/{README,index}.md',
// formatter 仅对 glob命中的文件有效
formatter: {
home(value, matter, file) {
@ -92,8 +94,8 @@ export default {
{
// 通配如果文件没有被其他精细glob命中
// 则使用 通配 formatter
// 如果是数组,必须有且用一个 glob为 * 的 项
glob: '*',
// 如果是数组,必须有且用一个 include 为 * 的 项
include: '*',
formatter: {
title(title) {
return title || '默认标题'

View File

@ -31,10 +31,11 @@
"dependencies": {
"@vuepress/core": "2.0.0-beta.60",
"@vuepress/shared": "2.0.0-beta.60",
"@vuepress/utils": "2.0.0-beta.60",
"chokidar": "^3.5.3",
"glob-to-regexp": "^0.4.1",
"gray-matter": "^4.0.3"
"create-filter": "^1.0.0",
"fast-glob": "^3.2.12",
"gray-matter": "^4.0.3",
"json2yaml": "^1.1.0"
},
"publishConfig": {
"access": "public"

View File

@ -1,16 +1,5 @@
declare module 'glob-to-regexp' {
interface GlobToRegexp {
(
glob: string,
options?: {
globstar?: boolean
extended?: boolean
flags?: string
}
): RegExp
}
declare module 'json2yaml' {
const result: any
const globToRegexp: GlobToRegexp
export default globToRegexp
export default result
}

View File

@ -1,8 +1,9 @@
import fs from 'node:fs'
import type { Plugin } from '@vuepress/core'
import { fs } from '@vuepress/utils'
import chokidar from 'chokidar'
import globToRegexp from 'glob-to-regexp'
import { createFilter } from 'create-filter'
import grayMatter from 'gray-matter'
import jsonToYaml from 'json2yaml'
import type {
AutoFrontmatterOptions,
FormatterArray,
@ -10,30 +11,31 @@ import type {
MarkdownFile,
} from '../shared/index.js'
import { readMarkdown, readMarkdownList } from './readFiles.js'
import { ensureArray } from './utils.js'
export const autoFrontmatterPlugin = ({
glob = '',
include = ['**/*.{md,MD}'],
exclude = ['.vuepress/**/*', 'node_modules'],
formatter = {},
}: AutoFrontmatterOptions = {}): Plugin => {
glob = glob ? (Array.isArray(glob) ? glob : [glob]) : []
glob = ['**/*.{md,MD}', '!.vuepress/', '!node_modules/', ...glob]
include = ensureArray(include)
exclude = ensureArray(exclude)
const globFilter = createFilter(include, exclude, { resolve: false })
const matterFormatter: FormatterArray = Array.isArray(formatter)
? formatter
: [{ glob: '*', formatter }]
: [{ include: '*', formatter }]
const globFormatter: FormatterObject =
matterFormatter.find(({ glob }) => glob === '*')?.formatter || {}
matterFormatter.find(({ include }) => include === '*')?.formatter || {}
const otherFormatters = matterFormatter
.filter(({ glob }) => glob !== '*')
.map(({ glob, formatter }) => {
.filter(({ include }) => include !== '*')
.map(({ include, formatter }) => {
return {
glob,
regexp: globToRegexp(glob, {
globstar: true,
extended: true,
}),
include,
filter: createFilter(include),
formatter,
}
})
@ -42,26 +44,27 @@ export const autoFrontmatterPlugin = ({
const { filepath, relativePath } = file
const formatter =
otherFormatters.find(({ regexp }) => regexp.test(relativePath))
?.formatter || globFormatter
otherFormatters.find(({ filter }) => filter(relativePath))?.formatter ||
globFormatter
const { data, content } = grayMatter(file.content)
Object.keys(formatter).forEach((key) => {
const value = formatter[key](data[key], data, file)
data[key] = value ?? data[key]
})
const newContent = grayMatter.stringify({ content }, data)
const yaml = jsonToYaml
.stringify(data)
.replace(/\n\s{2}/g, '\n')
.replace(/"/g, '')
const newContent = `${yaml}---\n${content}`
fs.writeFileSync(filepath, newContent)
fs.writeFileSync(filepath, newContent, 'utf-8')
}
return {
name: '@vuepress-plume/vuepress-plugin-auto-frontmatter',
onInitialized: async (app) => {
const markdownList = await readMarkdownList(
app.dir.source(),
glob as string[]
)
const markdownList = await readMarkdownList(app.dir.source(), globFilter)
markdownList.forEach((file) => formatMarkdown(file))
},
onWatched: async (app, watchers) => {
@ -72,8 +75,7 @@ export const autoFrontmatterPlugin = ({
})
watcher.on('add', (relativePath) => {
if ((glob as string[]).some((_) => !globToRegexp(_).test(relativePath)))
return
if (!globFilter(relativePath)) return
formatMarkdown(readMarkdown(app.dir.source(), relativePath))
})

View File

@ -1,18 +1,22 @@
import { fs, globby, path } from '@vuepress/utils'
import fs from 'node:fs'
import path from 'node:path'
import fg from 'fast-glob'
import type { MarkdownFile } from '../shared/index.js'
type MarkdownFileList = MarkdownFile[]
export const readMarkdownList = async (
sourceDir: string,
glob: string[]
filter: (id: string) => boolean
): Promise<MarkdownFileList> => {
const files: string[] = await globby(glob, {
const files: string[] = await fg(['**/*.md'], {
cwd: sourceDir,
gitignore: true,
ignore: ['node_modules/', '.vuepress/'],
})
return files.map((file) => readMarkdown(sourceDir, file))
return files
.filter((file) => filter(file))
.map((file) => readMarkdown(sourceDir, file))
}
export const readMarkdown = (

View File

@ -0,0 +1,5 @@
export function ensureArray<T>(thing: T | T[] | null | undefined): T[] {
if (Array.isArray(thing)) return thing
if (thing === null || thing === undefined) return []
return [thing]
}

View File

@ -15,15 +15,17 @@ export type FormatterObject<K = object, T = any> = Record<
>
export type FormatterArray = {
glob: string
include: string
formatter: FormatterObject
}[]
export interface AutoFrontmatterOptions {
/**
* glob string
* FilterPattern
*/
glob?: string | string[]
include?: string | string[]
exclude?: string | string[]
/**
* {

20
pnpm-lock.yaml generated
View File

@ -102,15 +102,19 @@ importers:
'@vuepress/shared': 2.0.0-beta.60
'@vuepress/utils': 2.0.0-beta.60
chokidar: ^3.5.3
glob-to-regexp: ^0.4.1
create-filter: ^1.0.0
fast-glob: ^3.2.12
gray-matter: ^4.0.3
json2yaml: ^1.1.0
dependencies:
'@vuepress/core': 2.0.0-beta.60
'@vuepress/shared': 2.0.0-beta.60
'@vuepress/utils': 2.0.0-beta.60
chokidar: 3.5.3
glob-to-regexp: 0.4.1
create-filter: 1.0.0
fast-glob: 3.2.12
gray-matter: 4.0.3
json2yaml: 1.1.0
packages/plugin-baidu-tongji:
specifiers:
@ -10156,6 +10160,14 @@ packages:
/json-stringify-safe/5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
/json2yaml/1.1.0:
resolution: {integrity: sha512-/xse+m0SlllfZahQrNOelmLrFNfeZv4QG0QKlvg7VsPSGIxpB3X+ggLkdffwmI1DdQ3o9XjZX+K+EOI1epdKgg==}
engines: {node: '>= 0.2.0'}
hasBin: true
dependencies:
remedial: 1.0.8
dev: false
/json5/1.0.1:
resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==}
hasBin: true
@ -13369,6 +13381,10 @@ packages:
engines: {node: '>= 0.10'}
dev: false
/remedial/1.0.8:
resolution: {integrity: sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==}
dev: false
/remove-trailing-separator/1.1.0:
resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
dev: false