perf: 优化主题配置解析
This commit is contained in:
parent
025a6bf680
commit
9a8df18286
20
theme/src/client/composables/blog-data.ts
Normal file
20
theme/src/client/composables/blog-data.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {
|
||||
blogPostData as blogPostDataRaw,
|
||||
} from '@internal/blogData'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { PlumeThemeBlogPostData } from '../../shared/index.js'
|
||||
|
||||
export type BlogDataRef = Ref<PlumeThemeBlogPostData>
|
||||
|
||||
export const blogPostData: BlogDataRef = ref(blogPostDataRaw)
|
||||
|
||||
export function useBlogPostData(): BlogDataRef {
|
||||
return blogPostData as BlogDataRef
|
||||
}
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateBlogPostData = (data: PlumeThemeBlogPostData) => {
|
||||
blogPostData.value = data
|
||||
}
|
||||
}
|
||||
55
theme/src/client/composables/encrypt-data.ts
Normal file
55
theme/src/client/composables/encrypt-data.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
encrypt as rawEncrypt,
|
||||
} from '@internal/encrypt'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export type EncryptConfig = readonly [
|
||||
boolean, // global
|
||||
string, // separator
|
||||
string, // admin
|
||||
string[], // keys
|
||||
Record<string, string>, // rules
|
||||
]
|
||||
|
||||
export interface EncryptData {
|
||||
global: boolean
|
||||
separator: string
|
||||
admins: string[]
|
||||
matches: string[]
|
||||
ruleList: {
|
||||
key: string
|
||||
match: string
|
||||
rules: string[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export type EncryptRef = Ref<EncryptData>
|
||||
|
||||
export const encrypt: EncryptRef = ref(resolveEncryptData(rawEncrypt))
|
||||
|
||||
export function useEncryptData(): EncryptRef {
|
||||
return encrypt as EncryptRef
|
||||
}
|
||||
|
||||
function resolveEncryptData(
|
||||
[global, separator, admin, matches, rules]: EncryptConfig,
|
||||
): EncryptData {
|
||||
return {
|
||||
global,
|
||||
separator,
|
||||
matches,
|
||||
admins: admin.split(separator),
|
||||
ruleList: Object.keys(rules).map(key => ({
|
||||
key,
|
||||
match: matches[key] as string,
|
||||
rules: rules[key].split(separator),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateEncrypt = (data: EncryptConfig) => {
|
||||
encrypt.value = resolveEncryptData(data)
|
||||
}
|
||||
}
|
||||
@ -9,5 +9,4 @@ export * from './templateBuildRenderer.js'
|
||||
|
||||
export * from './resolveSearchOptions.js'
|
||||
export * from './resolvePageHead.js'
|
||||
export * from './resolveEncrypt.js'
|
||||
export * from './resolveNotesOptions.js'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import { resolve } from '../utils.js'
|
||||
import { resolve } from '../utils/index.js'
|
||||
|
||||
export function resolveAlias() {
|
||||
return {
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
import { genSaltSync, hashSync } from 'bcrypt-ts'
|
||||
import { isNumber, isString, random, toArray } from '@pengzhanbo/utils'
|
||||
import type { Page } from 'vuepress/core'
|
||||
import type { PlumeThemeEncrypt, PlumeThemePageData } from '../../shared/index.js'
|
||||
|
||||
const isStringLike = (value: unknown): boolean => isString(value) || isNumber(value)
|
||||
const separator = ':'
|
||||
|
||||
export function resolveEncrypt(encrypt?: PlumeThemeEncrypt) {
|
||||
const salt = () => genSaltSync(random(8, 16))
|
||||
|
||||
const admin = encrypt?.admin
|
||||
? toArray(encrypt.admin)
|
||||
.filter(isStringLike)
|
||||
.map(item => hashSync(String(item), salt()))
|
||||
.join(separator)
|
||||
: ''
|
||||
|
||||
const rules: Record<string, string> = {}
|
||||
const keys = Object.keys(encrypt?.rules ?? {})
|
||||
|
||||
if (encrypt?.rules) {
|
||||
Object.keys(encrypt.rules).forEach((key) => {
|
||||
const index = keys.indexOf(key)
|
||||
|
||||
rules[String(index)] = toArray(encrypt.rules![key])
|
||||
.filter(isStringLike)
|
||||
.map(item => hashSync(String(item), salt()))
|
||||
.join(separator)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
__PLUME_ENCRYPT_GLOBAL__: encrypt?.global ?? false,
|
||||
__PLUME_ENCRYPT_SEPARATOR__: separator,
|
||||
__PLUME_ENCRYPT_ADMIN__: admin,
|
||||
__PLUME_ENCRYPT_KEYS__: keys,
|
||||
__PLUME_ENCRYPT_RULES__: rules,
|
||||
}
|
||||
}
|
||||
|
||||
export function isEncryptPage(page: Page<PlumeThemePageData>, encrypt?: PlumeThemeEncrypt) {
|
||||
if (!encrypt)
|
||||
return false
|
||||
|
||||
const rules = encrypt.rules ?? {}
|
||||
|
||||
return Object.keys(rules).some((match) => {
|
||||
const relativePath = page.data.filePathRelative || ''
|
||||
if (match[0] === '^') {
|
||||
const regex = new RegExp(match)
|
||||
return regex.test(page.path) || (relativePath && regex.test(relativePath))
|
||||
}
|
||||
if (match.endsWith('.md'))
|
||||
return relativePath && relativePath.endsWith(match)
|
||||
|
||||
return page.path.startsWith(match) || relativePath.startsWith(match)
|
||||
})
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { entries, fromEntries, getLocaleConfig } from '@vuepress/helper'
|
||||
import type { App } from 'vuepress'
|
||||
import { LOCALE_OPTIONS } from '../locales/index.js'
|
||||
import type { PlumeThemeLocaleData, PlumeThemeLocaleOptions } from '../../shared/index.js'
|
||||
import { THEME_NAME } from '../utils.js'
|
||||
import { THEME_NAME } from '../utils/index.js'
|
||||
|
||||
const FALLBACK_OPTIONS: PlumeThemeLocaleData = {
|
||||
appearance: true,
|
||||
@ -18,6 +18,10 @@ const FALLBACK_OPTIONS: PlumeThemeLocaleData = {
|
||||
editLink: true,
|
||||
contributors: true,
|
||||
|
||||
footer: {
|
||||
message:
|
||||
'Power by <a target="_blank" href="https://v2.vuepress.vuejs.org/">VuePress</a> & <a target="_blank" href="https://theme-plume.vuejs.press">vuepress-theme-plume</a>',
|
||||
},
|
||||
}
|
||||
|
||||
export function resolveLocaleOptions(app: App, { locales, ...options }: PlumeThemeLocaleOptions): PlumeThemeLocaleOptions {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { NotesDataOptions } from '@vuepress-plume/plugin-notes-data'
|
||||
import { entries } from '@vuepress/helper'
|
||||
import { uniq } from '@pengzhanbo/utils'
|
||||
import type { PlumeThemeLocaleOptions } from '../..//shared/index.js'
|
||||
import { withBase } from '../utils.js'
|
||||
import type { NotesOptions, PlumeThemeLocaleOptions } from '../../shared/index.js'
|
||||
import { withBase } from '../utils/index.js'
|
||||
|
||||
export function resolveNotesLinkList(localeOptions: PlumeThemeLocaleOptions) {
|
||||
const locales = localeOptions.locales || {}
|
||||
@ -22,9 +21,9 @@ export function resolveNotesLinkList(localeOptions: PlumeThemeLocaleOptions) {
|
||||
return uniq(notesLinks)
|
||||
}
|
||||
|
||||
export function resolveNotesOptions(localeOptions: PlumeThemeLocaleOptions): NotesDataOptions[] {
|
||||
export function resolveNotesOptions(localeOptions: PlumeThemeLocaleOptions): NotesOptions[] {
|
||||
const locales = localeOptions.locales || {}
|
||||
const notesOptionsList: NotesDataOptions[] = []
|
||||
const notesOptionsList: NotesOptions[] = []
|
||||
for (const [locale, opt] of entries(locales)) {
|
||||
const options = locale === '/' ? (opt.notes || localeOptions.notes) : opt.notes
|
||||
if (options) {
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
import type { App } from 'vuepress'
|
||||
import { entries, fromEntries, getRootLangPath, isPlainObject } from '@vuepress/helper'
|
||||
import type { PlumeThemeEncrypt, PlumeThemePluginOptions } from '../../shared/index.js'
|
||||
import type { PlumeThemePluginOptions } from '../../shared/index.js'
|
||||
import { PRESET_LOCALES } from '../locales/index.js'
|
||||
import { resolveEncrypt } from './resolveEncrypt.js'
|
||||
|
||||
export function resolveProvideData(
|
||||
app: App,
|
||||
plugins: PlumeThemePluginOptions,
|
||||
encrypt?: PlumeThemeEncrypt,
|
||||
|
||||
): Record<string, any> {
|
||||
const root = getRootLangPath(app)
|
||||
|
||||
return {
|
||||
// 注入 加密配置
|
||||
...resolveEncrypt(encrypt),
|
||||
// 注入水印配置
|
||||
__PLUME_WM_FP__: isPlainObject(plugins.watermark)
|
||||
? plugins.watermark.fullPage !== false
|
||||
|
||||
@ -2,9 +2,9 @@ import { entries, getRootLangPath } from '@vuepress/helper'
|
||||
import type { App } from 'vuepress'
|
||||
import type { NavItem, PlumeThemeLocaleOptions } from '../../shared/index.js'
|
||||
import { PRESET_LOCALES } from '../locales/index.js'
|
||||
import { withBase } from '../utils.js'
|
||||
import { withBase } from '../utils/index.js'
|
||||
|
||||
const EXCLUDE_LIST = ['locales', 'sidebar', 'navbar', 'notes', 'article', 'avatar']
|
||||
const EXCLUDE_LIST = ['locales', 'sidebar', 'navbar', 'notes', 'sidebar', 'article', 'avatar']
|
||||
// 过滤不需要出现在多语言配置中的字段
|
||||
const EXCLUDE_LOCALE_LIST = [...EXCLUDE_LIST, 'blog', 'appearance']
|
||||
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
import type { PlumeThemeOptions } from '../../shared/index.js'
|
||||
import { logger } from '../utils.js'
|
||||
import { logger } from '../utils/index.js'
|
||||
|
||||
export function resolveThemeOptions({ themePlugins, plugins, encrypt, hostname, ...localeOptions }: PlumeThemeOptions) {
|
||||
export function resolveThemeOptions({
|
||||
themePlugins,
|
||||
plugins,
|
||||
hostname,
|
||||
configFile,
|
||||
...localeOptions
|
||||
}: PlumeThemeOptions) {
|
||||
const pluginOptions = plugins ?? themePlugins ?? {}
|
||||
|
||||
if (themePlugins) {
|
||||
@ -11,8 +17,8 @@ export function resolveThemeOptions({ themePlugins, plugins, encrypt, hostname,
|
||||
}
|
||||
|
||||
return {
|
||||
configFile,
|
||||
pluginOptions,
|
||||
encrypt,
|
||||
hostname,
|
||||
localeOptions,
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type TemplateRendererContext, templateRenderer } from 'vuepress/utils'
|
||||
import { getThemePackage } from '../utils.js'
|
||||
import { getThemePackage } from '../utils/index.js'
|
||||
|
||||
export function templateBuildRenderer(template: string, context: TemplateRendererContext) {
|
||||
const pkg = getThemePackage()
|
||||
|
||||
@ -4,12 +4,8 @@ import { docsearchPlugin } from '@vuepress/plugin-docsearch'
|
||||
import { gitPlugin } from '@vuepress/plugin-git'
|
||||
import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
|
||||
import { nprogressPlugin } from '@vuepress/plugin-nprogress'
|
||||
import { themeDataPlugin } from '@vuepress/plugin-theme-data'
|
||||
import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter'
|
||||
import { baiduTongjiPlugin } from '@vuepress-plume/plugin-baidu-tongji'
|
||||
import { blogDataPlugin } from '@vuepress-plume/plugin-blog-data'
|
||||
import { iconifyPlugin } from '@vuepress-plume/plugin-iconify'
|
||||
import { notesDataPlugin } from '@vuepress-plume/plugin-notes-data'
|
||||
import { shikiPlugin } from '@vuepress-plume/plugin-shikiji'
|
||||
import { commentPlugin } from '@vuepress/plugin-comment'
|
||||
import { type MarkdownEnhancePluginOptions, mdEnhancePlugin } from 'vuepress-plugin-md-enhance'
|
||||
@ -21,54 +17,31 @@ import { searchPlugin } from '@vuepress-plume/plugin-search'
|
||||
import { markdownPowerPlugin } from 'vuepress-plugin-md-power'
|
||||
import { watermarkPlugin } from '@vuepress/plugin-watermark'
|
||||
import { fontsPlugin } from '@vuepress-plume/plugin-fonts'
|
||||
import type {
|
||||
PlumeThemeEncrypt,
|
||||
PlumeThemeLocaleOptions,
|
||||
PlumeThemePluginOptions,
|
||||
} from '../../shared/index.js'
|
||||
import type { PlumeThemeLocaleOptions, PlumeThemePluginOptions } from '../../shared/index.js'
|
||||
import {
|
||||
resolveDocsearchOptions,
|
||||
resolveNotesOptions,
|
||||
resolveSearchOptions,
|
||||
resolveThemeData,
|
||||
} from '../config/index.js'
|
||||
import { resolveAutoFrontmatterOptions } from './resolveAutoFrontmatterOptions.js'
|
||||
import { resolveBlogDataOptions } from './resolveBlogDataOptions.js'
|
||||
import { customContainerPlugins } from './containerPlugins.js'
|
||||
|
||||
export interface SetupPluginOptions {
|
||||
app: App
|
||||
pluginOptions: PlumeThemePluginOptions
|
||||
localeOptions: PlumeThemeLocaleOptions
|
||||
encrypt?: PlumeThemeEncrypt
|
||||
hostname?: string
|
||||
}
|
||||
|
||||
export function getPlugins({
|
||||
app,
|
||||
pluginOptions,
|
||||
localeOptions,
|
||||
encrypt,
|
||||
hostname,
|
||||
}: SetupPluginOptions): PluginConfig {
|
||||
const isProd = !app.env.isDev
|
||||
|
||||
const plugins: PluginConfig = [
|
||||
|
||||
themeDataPlugin({ themeData: resolveThemeData(app, localeOptions) }),
|
||||
|
||||
autoFrontmatterPlugin(resolveAutoFrontmatterOptions(pluginOptions, localeOptions)),
|
||||
|
||||
blogDataPlugin(resolveBlogDataOptions(localeOptions, encrypt)),
|
||||
|
||||
notesDataPlugin(resolveNotesOptions(localeOptions)),
|
||||
|
||||
iconifyPlugin(),
|
||||
|
||||
fontsPlugin(),
|
||||
|
||||
contentUpdatePlugin(),
|
||||
|
||||
activeHeaderLinksPlugin({
|
||||
headerLinkSelector: 'a.outline-link',
|
||||
headerAnchorSelector: '.header-anchor',
|
||||
@ -185,10 +158,7 @@ export function getPlugins({
|
||||
}
|
||||
|
||||
if (pluginOptions.seo !== false && hostname && isProd) {
|
||||
plugins.push(seoPlugin({
|
||||
hostname,
|
||||
author: localeOptions.locales?.['/'].profile?.name || localeOptions.profile?.name || localeOptions.locales?.['/'].avatar?.name || localeOptions.avatar?.name,
|
||||
}))
|
||||
plugins.push(seoPlugin({ hostname }))
|
||||
}
|
||||
|
||||
return plugins
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
import type { BlogDataPluginOptions } from '@vuepress-plume/plugin-blog-data'
|
||||
import { removeLeadingSlash } from '@vuepress/helper'
|
||||
import {
|
||||
isEncryptPage,
|
||||
resolveNotesOptions,
|
||||
} from '../config/index.js'
|
||||
import { normalizePath } from '../utils.js'
|
||||
import type { PlumeThemeEncrypt, PlumeThemeLocaleOptions } from '../..//shared/index.js'
|
||||
|
||||
export function resolveBlogDataOptions(
|
||||
localeOptions: PlumeThemeLocaleOptions,
|
||||
encrypt?: PlumeThemeEncrypt,
|
||||
): BlogDataPluginOptions {
|
||||
const blog = localeOptions.blog || {}
|
||||
const notesList = resolveNotesOptions(localeOptions)
|
||||
const notesDirList = notesList
|
||||
.map(notes => removeLeadingSlash(normalizePath(`${notes.dir}/**`)))
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
include: blog?.include ?? ['**/*.md'],
|
||||
exclude: [
|
||||
'**/{README,readme,index}.md',
|
||||
'.vuepress/',
|
||||
'node_modules/',
|
||||
...(blog.exclude ?? []),
|
||||
...notesDirList,
|
||||
].filter(Boolean),
|
||||
sortBy: 'createTime',
|
||||
excerpt: true,
|
||||
pageFilter: (page: any) => page.frontmatter.draft !== true,
|
||||
extendBlogData: (page: any) => {
|
||||
const tags = page.frontmatter.tags
|
||||
const data: Record<string, any> = {
|
||||
categoryList: page.data.categoryList,
|
||||
tags,
|
||||
sticky: page.frontmatter.sticky,
|
||||
createTime: page.data.frontmatter.createTime,
|
||||
lang: page.lang,
|
||||
}
|
||||
isEncryptPage(page, encrypt) && (data.encrypt = true)
|
||||
return data
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
import process from 'node:process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import { fs, getDirname, path } from 'vuepress/utils'
|
||||
import { Logger, ensureEndingSlash, ensureLeadingSlash } from '@vuepress/helper'
|
||||
|
||||
export const THEME_NAME = 'vuepress-theme-plume'
|
||||
|
||||
const __dirname = getDirname(import.meta.url)
|
||||
|
||||
export const resolve = (...args: string[]) => path.resolve(__dirname, '../', ...args)
|
||||
export const templates = (url: string) => resolve('../templates', url)
|
||||
|
||||
export const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8)
|
||||
|
||||
export const hash = (content: string) => createHash('md5').update(content).digest('hex')
|
||||
|
||||
export const logger = new Logger(THEME_NAME)
|
||||
|
||||
export function getPackage() {
|
||||
let pkg = {} as any
|
||||
try {
|
||||
const content = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8')
|
||||
pkg = JSON.parse(content)
|
||||
}
|
||||
catch { }
|
||||
return pkg
|
||||
}
|
||||
|
||||
export function getThemePackage() {
|
||||
let pkg = {} as any
|
||||
try {
|
||||
const content = fs.readFileSync(resolve('../package.json'), 'utf-8')
|
||||
pkg = JSON.parse(content)
|
||||
}
|
||||
catch {}
|
||||
return pkg
|
||||
}
|
||||
|
||||
const RE_SLASH = /(\\|\/)+/g
|
||||
export function normalizePath(path: string) {
|
||||
return path.replace(RE_SLASH, '/')
|
||||
}
|
||||
|
||||
export function pathJoin(...args: string[]) {
|
||||
return normalizePath(path.join(...args))
|
||||
}
|
||||
|
||||
const RE_START_END_SLASH = /^\/|\/$/g
|
||||
export function getCurrentDirname(basePath: string | undefined, filepath: string) {
|
||||
const dirList = normalizePath(basePath || path.dirname(filepath))
|
||||
.replace(RE_START_END_SLASH, '')
|
||||
.split('/')
|
||||
return dirList.length > 0 ? dirList[dirList.length - 1] : ''
|
||||
}
|
||||
|
||||
export function withBase(path = '', base = '/'): string {
|
||||
path = ensureEndingSlash(ensureLeadingSlash(path))
|
||||
if (path.startsWith(base))
|
||||
return normalizePath(path)
|
||||
return normalizePath(`${base}${path}`)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user