From f6ae1a1149cdac191f8d779aefa3fe9ee9cdaa68 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Fri, 9 Aug 2024 01:12:13 +0800 Subject: [PATCH] perf(plugin-md-power): improve icons rules --- plugins/plugin-md-power/package.json | 9 - .../features/{icons/plugin.ts => icons.ts} | 37 ++-- .../src/node/features/icons/index.ts | 2 - .../src/node/features/icons/writer.ts | 174 ------------------ plugins/plugin-md-power/src/node/plugin.ts | 9 +- .../src/node/prepareConfigFile.ts | 2 - pnpm-lock.yaml | 15 +- 7 files changed, 14 insertions(+), 234 deletions(-) rename plugins/plugin-md-power/src/node/features/{icons/plugin.ts => icons.ts} (62%) delete mode 100644 plugins/plugin-md-power/src/node/features/icons/index.ts delete mode 100644 plugins/plugin-md-power/src/node/features/icons/writer.ts diff --git a/plugins/plugin-md-power/package.json b/plugins/plugin-md-power/package.json index fda22888..3b820977 100644 --- a/plugins/plugin-md-power/package.json +++ b/plugins/plugin-md-power/package.json @@ -37,19 +37,11 @@ "tsup": "tsup --config tsup.config.ts" }, "peerDependencies": { - "@iconify/json": "^2", "vuepress": "2.0.0-rc.14" }, - "peerDependenciesMeta": { - "@iconify/json": { - "optional": true - } - }, "dependencies": { - "@iconify/utils": "^2.1.30", "@vuepress/helper": "2.0.0-rc.40", "@vueuse/core": "^10.11.0", - "local-pkg": "^0.5.0", "markdown-it-container": "^4.0.0", "nanoid": "^5.0.7", "shiki": "^1.12.1", @@ -58,7 +50,6 @@ "vue": "^3.4.35" }, "devDependencies": { - "@iconify/json": "^2.2.234", "@types/markdown-it": "^14.1.2" }, "publishConfig": { diff --git a/plugins/plugin-md-power/src/node/features/icons/plugin.ts b/plugins/plugin-md-power/src/node/features/icons.ts similarity index 62% rename from plugins/plugin-md-power/src/node/features/icons/plugin.ts rename to plugins/plugin-md-power/src/node/features/icons.ts index fd531413..3f632f22 100644 --- a/plugins/plugin-md-power/src/node/features/icons/plugin.ts +++ b/plugins/plugin-md-power/src/node/features/icons.ts @@ -6,13 +6,10 @@ */ import type { PluginWithOptions } from 'markdown-it' import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs' -import { parseRect } from '../../utils/parseRect.js' - -type AddIcon = (iconName: string) => string | undefined const [openTag, endTag] = [':[', ']:'] -function createTokenizer(addIcon: AddIcon): RuleInline { +function createTokenizer(): RuleInline { return (state, silent) => { let found = false const max = state.posMax @@ -56,31 +53,20 @@ function createTokenizer(addIcon: AddIcon): RuleInline { state.posMax = state.pos state.pos = start + 2 - const [iconName, options = ''] = content.split(/\s+/) + const [name, options = ''] = content.split(/\s+/) const [size, color] = options.split('/') - const open = state.push('iconify_open', 'span', 1) - open.markup = openTag + const icon = state.push('vp_iconify_open', 'VPIcon', 1) + icon.markup = openTag - const className = addIcon(iconName) - - if (className) - open.attrSet('class', className) - - let style = '' + if (name) + icon.attrSet('name', name) if (size) - style += `width:${parseRect(size)};height:${parseRect(size)};` - + icon.attrSet('size', size) if (color) - style += `color:${color};` + icon.attrSet('color', color) - if (style) - open.attrSet('style', style) - - const text = state.push('text', '', 0) - text.content = className ? '' : iconName - - const close = state.push('iconify_close', 'span', -1) + const close = state.push('vp_iconify_close', 'VPIcon', -1) close.markup = endTag state.pos = state.posMax + 2 @@ -90,9 +76,8 @@ function createTokenizer(addIcon: AddIcon): RuleInline { } } -export const iconsPlugin: PluginWithOptions = ( +export const iconsPlugin: PluginWithOptions = ( md, - addIcon = () => '', ) => { - md.inline.ruler.before('emphasis', 'iconify', createTokenizer(addIcon)) + md.inline.ruler.before('emphasis', 'iconify', createTokenizer()) } diff --git a/plugins/plugin-md-power/src/node/features/icons/index.ts b/plugins/plugin-md-power/src/node/features/icons/index.ts deleted file mode 100644 index 714b313e..00000000 --- a/plugins/plugin-md-power/src/node/features/icons/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './writer.js' -export * from './plugin.js' diff --git a/plugins/plugin-md-power/src/node/features/icons/writer.ts b/plugins/plugin-md-power/src/node/features/icons/writer.ts deleted file mode 100644 index 44388a81..00000000 --- a/plugins/plugin-md-power/src/node/features/icons/writer.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { constants, promises as fsp } from 'node:fs' -import type { App } from 'vuepress/core' -import { getIconContentCSS, getIconData } from '@iconify/utils' -import { fs, logger } from 'vuepress/utils' -import { isPackageExists } from 'local-pkg' -import { customAlphabet } from 'nanoid' -import type { IconsOptions } from '../../../shared/index.js' -import { interopDefault } from '../../utils/package.js' -import { parseRect } from '../../utils/parseRect.js' - -export interface IconCacheItem { - className: string - background: boolean - content: string -} - -const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 8) -const iconDataCache = new Map() -const URL_CONTENT_RE = /(url\([\s\S]+?\))/ -const CSS_PATH = 'internal/md-power/icons.css' - -let locate: ((name: string) => any) | undefined - -function resolveOption(opt?: boolean | IconsOptions): Required { - const options = typeof opt === 'object' ? opt : {} - options.prefix ??= 'vp-mdi' - options.color = options.color === 'currentColor' || !options.color ? 'currentcolor' : options.color - options.size = options.size ? parseRect(`${options.size}`) : '1em' - return options as Required -} - -export function createIconCSSWriter(app: App, opt?: boolean | IconsOptions) { - const cache = new Map() - const isInstalled = isPackageExists('@iconify/json') - const currentPath = app.dir.temp(CSS_PATH) - - const write = async (content: string) => { - if (!content && app.env.isDev) { - if (existsSync(currentPath) && (await fsp.stat(currentPath)).isFile()) { - return - } - } - await app.writeTemp(CSS_PATH, content) - } - let timer: NodeJS.Timeout | null = null - - const options = resolveOption(opt) - const prefix = options.prefix - const defaultContent = getDefaultContent(options) - - async function writeCss() { - if (timer) - clearTimeout(timer) - - timer = setTimeout(async () => { - let css = defaultContent - - if (cache.size > 0) { - for (const [, { content, className }] of cache) - css += `.${className} {\n --svg: ${content};\n}\n` - - await write(css) - } - }, 100) - } - - function addIcon(iconName: string) { - if (!isInstalled) - return - - if (cache.has(iconName)) { - const item = cache.get(iconName)! - return `${item.className}${item.background ? ' bg' : ''}` - } - - const item: IconCacheItem = { - className: `${prefix}-${nanoid()}`, - ...genIcon(iconName), - } - cache.set(iconName, item) - writeCss() - return `${item.className}${item.background ? ' bg' : ''}` - } - - async function initIcon() { - if (!opt) - return await write('') - - if (!isInstalled) { - logger.error('[plugin-md-power]: `@iconify/json` not found! Please install `@iconify/json` first.') - return - } - - if (!locate) { - const mod = await interopDefault(import('@iconify/json')) - locate = mod.locate - } - - return await writeCss() - } - - return { addIcon, writeCss, initIcon } -} - -function getDefaultContent(options: Required) { - const { prefix, size, color } = options - return `[class^="${prefix}-"] { - display: inline-block; - width: ${size}; - height: ${size}; - vertical-align: middle; -} -[class^="${prefix}-"]:not(.bg) { - color: inherit; - background-color: ${color}; - -webkit-mask: var(--svg) no-repeat; - mask: var(--svg) no-repeat; - -webkit-mask-size: 100% 100%; - mask-size: 100% 100%; -} -[class^="${prefix}-"].bg { - background-color: transparent; - background-image: var(--svg); - background-repeat: no-repeat; - background-size: 100% 100%; -} -` -} - -function genIcon(iconName: string): { - content: string - background: boolean -} { - if (!locate) { - return { content: '', background: false } - } - const [collect, name] = iconName.split(':') - let iconJson: any = iconDataCache.get(collect) - if (!iconJson) { - const filename = locate(collect) - - try { - iconJson = JSON.parse(fs.readFileSync(filename, 'utf-8')) - iconDataCache.set(collect, iconJson) - } - catch { - logger.warn(`[plugin-md-power] Can not find icon, ${collect} is missing!`) - } - } - const data = getIconData(iconJson, name) - if (!data) { - logger.error(`[plugin-md-power] Can not read icon in ${collect}, ${name} is missing!`) - return { content: '', background: false } - } - - const content = getIconContentCSS(data, { - height: data.height || 24, - }) - const match = content.match(URL_CONTENT_RE) - return { - content: match ? match[1] : '', - background: !data.body.includes('currentColor'), - } -} - -function existsSync(fp: string) { - try { - fs.accessSync(fp, constants.R_OK) - return true - } - catch { - return false - } -} diff --git a/plugins/plugin-md-power/src/node/plugin.ts b/plugins/plugin-md-power/src/node/plugin.ts index b275a856..2f3f5714 100644 --- a/plugins/plugin-md-power/src/node/plugin.ts +++ b/plugins/plugin-md-power/src/node/plugin.ts @@ -4,7 +4,7 @@ import { addViteOptimizeDepsInclude } from '@vuepress/helper' import type { CanIUseOptions, MarkdownPowerPluginOptions } from '../shared/index.js' import { caniusePlugin, legacyCaniuse } from './features/caniuse.js' import { pdfPlugin } from './features/pdf.js' -import { createIconCSSWriter, iconsPlugin } from './features/icons/index.js' +import { iconsPlugin } from './features/icons.js' import { bilibiliPlugin } from './features/video/bilibili.js' import { youtubePlugin } from './features/video/youtube.js' import { codepenPlugin } from './features/codepen.js' @@ -17,20 +17,15 @@ import { prepareConfigFile } from './prepareConfigFile.js' export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): Plugin { return (app) => { - const { initIcon, addIcon } = createIconCSSWriter(app, options.icons) - return { name: 'vuepress-plugin-md-power', - // clientConfigFile: path.resolve(__dirname, '../client/config.js'), clientConfigFile: app => prepareConfigFile(app, options), define: { __MD_POWER_INJECT_OPTIONS__: options, }, - onInitialized: async () => await initIcon(), - extendsBundlerOptions(bundlerOptions) { if (options.repl) { addViteOptimizeDepsInclude( @@ -57,7 +52,7 @@ export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): P if (options.icons) { // :[collect:name]: - md.use(iconsPlugin, addIcon) + md.use(iconsPlugin) } if (options.bilibili) { diff --git a/plugins/plugin-md-power/src/node/prepareConfigFile.ts b/plugins/plugin-md-power/src/node/prepareConfigFile.ts index 518275cf..b1bdaca4 100644 --- a/plugins/plugin-md-power/src/node/prepareConfigFile.ts +++ b/plugins/plugin-md-power/src/node/prepareConfigFile.ts @@ -14,8 +14,6 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp const imports = new Set() const enhances = new Set() - imports.add(`import '@internal/md-power/icons.css'`) - if (options.pdf) { imports.add(`import PDFViewer from '${CLIENT_FOLDER}components/PDFViewer.vue'`) enhances.add(`app.component('PDFViewer', PDFViewer)`) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52044745..3c44833b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,18 +129,12 @@ importers: plugins/plugin-md-power: dependencies: - '@iconify/utils': - specifier: ^2.1.30 - version: 2.1.30 '@vuepress/helper': specifier: 2.0.0-rc.40 version: 2.0.0-rc.40(typescript@5.5.4)(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.4)(yaml@2.5.0))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))) '@vueuse/core': specifier: ^10.11.0 version: 10.11.0(vue@3.4.35(typescript@5.5.4)) - local-pkg: - specifier: ^0.5.0 - version: 0.5.0 markdown-it-container: specifier: ^4.0.0 version: 4.0.0 @@ -163,9 +157,6 @@ importers: specifier: 2.0.0-rc.14 version: 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.4)(yaml@2.5.0))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)) devDependencies: - '@iconify/json': - specifier: ^2.2.234 - version: 2.2.234 '@types/markdown-it': specifier: ^14.1.2 version: 14.1.2 @@ -391,9 +382,6 @@ packages: peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - peerDependenciesMeta: - '@algolia/client-search': - optional: true '@algolia/cache-browser-local-storage@4.20.0': resolution: {integrity: sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==} @@ -5776,9 +5764,8 @@ snapshots: '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.20.0)(algoliasearch@4.20.0)': dependencies: - algoliasearch: 4.20.0 - optionalDependencies: '@algolia/client-search': 4.20.0 + algoliasearch: 4.20.0 '@algolia/cache-browser-local-storage@4.20.0': dependencies: