diff --git a/package.json b/package.json index fa2b72ac2878915fcd6099cb44b408f99a092a38..30e7b5b824f86a8c74446398191e945389696877 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "dev": "pnpm -C packages/portal dev", "dev:ak": "pnpm -C packages/portal-ak dev", - "docs:install": "pnpm i && pnpm -C packages/scripts build && pnpm i && pnpm -C packages/opendesign gen:icon && pnpm -C packages/themes gen:tokens && pnpm -C packages/docs gen:icon && pnpm -C packages/docs gen:api ", + "docs:install": "pnpm i && pnpm -C packages/scripts build && pnpm i && pnpm -C packages/opendesign gen:icon && pnpm -C packages/docs gen:icon && pnpm -C packages/docs gen:api ", "docs:dev": "pnpm -C packages/docs dev", "docs:build": "pnpm -C packages/docs build" }, diff --git a/packages/docs/plugins/injectDemoDocs.ts b/packages/docs/plugins/injectDemoDocs.ts index 4014cf9de1473a827afe9ca765cfe29a2bd1688e..3a3827c0bac5c4b793031e4b000fbc6fc8514779 100644 --- a/packages/docs/plugins/injectDemoDocs.ts +++ b/packages/docs/plugins/injectDemoDocs.ts @@ -1,6 +1,4 @@ import { type Plugin, type ViteDevServer } from 'vite'; -import { MarkdownItAsync } from 'markdown-it-async'; -import { markdownItPlugins, markdownItOptions } from './markdown/common'; import { parseDocsCode } from '../helper/utils'; const parseVueQuery = (id: string) => { @@ -33,8 +31,6 @@ const genVirtualId = (id: string, lang: string) => { * @returns Plugin */ export function injectDemoDocs(): Plugin { - const md = new MarkdownItAsync(markdownItOptions); - markdownItPlugins.forEach((plugin) => md.use(plugin)); let viteDevServer: ViteDevServer | null = null; return { name: 'portal:inject-demo-docs', diff --git a/packages/docs/plugins/injectDemoSource.ts b/packages/docs/plugins/injectDemoSource.ts index 3d807891c95c70f567683efcbfdee91956f15f9c..dcf84c13dd156b7f7b8d393257a354b42ca4a473 100644 --- a/packages/docs/plugins/injectDemoSource.ts +++ b/packages/docs/plugins/injectDemoSource.ts @@ -1,12 +1,12 @@ import fsp from 'node:fs/promises'; import { createFilter, type Plugin } from 'vite'; import { parse } from '@vue/compiler-sfc'; -import { md } from './markdown/common'; import { generateCode } from '../helper/utils'; -const VIRTUAL_PREFIX = 'virtual:demo-source:'; const virtualModules = new Map(); - +const getVirtualId = (id: string) => { + return `${id}-demo-source.md`; +}; /** * 使用@vue/compiler-sfc库,只保留case源代码中的 script, scriptSetup, template, styles 块, * 并将清理后的源代码渲染为 vue 组件 @@ -32,14 +32,8 @@ const generateVirtualModule = (source: string) => { }); } cleanedSource = cleanedSource.trimEnd(); - const result = `${md.render(`\`\`\`vue:line-numbers\n${cleanedSource}\n\`\`\``)}`.replace( - /()([\s\S]*?)<\/code><\/pre>/, - (_, pre, codeAttr, codeContent) => { - return `${pre}${codeContent}`; - } - ); // 返回组件源码 - return ``; + return `\`\`\`vue:line-numbers\n${cleanedSource}\n\`\`\``; }; /** * vite 插件,用于将 Case 组件的源代码保存到 _sfc_main 对象中 @@ -59,12 +53,12 @@ export function injectDemoSource(): Plugin { return virtualModules.get(id); }, async transform(code, id) { - if (!filter(id) || id.startsWith(VIRTUAL_PREFIX)) { + if (!filter(id)) { return; } if (await fsp.stat(id).then((stat) => stat.isFile())) { const source = await fsp.readFile(id, 'utf-8'); - const virtualId = `${VIRTUAL_PREFIX}${id}`; + const virtualId = getVirtualId(id); virtualModules.set(virtualId, generateVirtualModule(source)); // Case 组件引入虚拟模块 virtualId,该虚拟模块就是 Case 组件的源代码 return `${code} @@ -73,7 +67,7 @@ _sfc_main.DemoSource = _DemoSource;`; } }, async handleHotUpdate(ctx) { - const virtualId = `${VIRTUAL_PREFIX}${ctx.file}`; + const virtualId = getVirtualId(ctx.file); if (virtualModules.has(virtualId)) { // 当Case组件更新时,同时更新对应的虚拟模块,以实现源码的热更新 virtualModules.set(virtualId, generateVirtualModule(await ctx.read())); diff --git a/packages/docs/plugins/markdown/common.ts b/packages/docs/plugins/markdown/common.ts index ef2683f5ef51859c6a3449a5ad3aa9c0198e1fd7..7bc256567a680c79b6f2b52ad09c3da8a1ca4658 100644 --- a/packages/docs/plugins/markdown/common.ts +++ b/packages/docs/plugins/markdown/common.ts @@ -1,21 +1,13 @@ -import { MarkdownItAsync, type MarkdownItAsyncOptions } from 'markdown-it-async'; +import { type MarkdownItAsyncOptions } from 'markdown-it-async'; import lineNumber from './lineNumber'; import popover from './popover'; import wrapTable from './wrapTable'; import wrapCodeContainer from './wrapCodeContainer'; import link from './link'; -import { createHighlighter } from './highlight'; -export const highlight = createHighlighter(); export const markdownItOptions: MarkdownItAsyncOptions = { html: true, linkify: true, typographer: true, - highlight, }; export const markdownItPlugins = [lineNumber, popover, wrapCodeContainer, wrapTable, link]; -/** - * 引入项目中所有 markdown 插件,并导出 MarkdownItAsync 实例,以便在其他模块中调用 - */ -export const md = new MarkdownItAsync(markdownItOptions); -markdownItPlugins.forEach((plugin) => md.use(plugin)); diff --git a/packages/docs/plugins/markdown/highlight.ts b/packages/docs/plugins/markdown/highlight.ts index 2b4273bb24fac3573f4f2b891599d0bca6bad2e7..010ed5950a734f6027265a6708909fecfedba211 100644 --- a/packages/docs/plugins/markdown/highlight.ts +++ b/packages/docs/plugins/markdown/highlight.ts @@ -1,44 +1,51 @@ // 优化后代码结构示例 -import { parse } from '@vue/compiler-sfc'; import { escapeHtml } from 'markdown-it/lib/common/utils.mjs'; -import { createHighlighterCoreSync, createJavaScriptRegexEngine } from 'shiki'; -import darkPlus from '@shikijs/themes/dark-plus'; -import lightPlus from '@shikijs/themes/light-plus'; -import js from '@shikijs/langs/javascript'; -import ts from '@shikijs/langs/typescript'; -import json from '@shikijs/langs/json'; -import html from '@shikijs/langs/html'; -import css from '@shikijs/langs/css'; -import bash from '@shikijs/langs/bash'; -import shell from '@shikijs/langs/shell'; -import vue from '@shikijs/langs/vue'; -import md from '@shikijs/langs/markdown'; -import yaml from '@shikijs/langs/yaml'; -import jsx from '@shikijs/langs/jsx'; -import tsx from '@shikijs/langs/tsx'; -import scss from '@shikijs/langs/scss'; -import less from '@shikijs/langs/less'; +import { createHighlighterCore, createJavaScriptRegexEngine } from 'shiki'; import { generateCode } from '../../helper/utils'; const baseConfig = { - themes: [lightPlus, darkPlus], + themes: [import('@shikijs/themes/light-plus'), import('@shikijs/themes/dark-plus')], engine: createJavaScriptRegexEngine(), }; /** * 创建高亮函数 * @returns 高亮函数 */ -export const createHighlighter = () => { - const mainHighlighter = createHighlighterCoreSync({ - ...baseConfig, - langs: [js, ts, json, html, css, bash, shell, vue, md, yaml, jsx, tsx, scss, less], - }); - // 创建Vue模板专用高亮器 - // 实测发现 markdown, yaml, jsx, tsx, scss, less 语言包会影响 vue 的 template 块高亮,导致 vue 的特殊语法高亮不准确,因此创建一个专用的模板高亮器 - const vueTemplateHighlighter = createHighlighterCoreSync({ - ...baseConfig, - langs: [vue, js, ts, html, css], - }); +export const createHighlighter = async () => { + const [mainHighlighter, vueTemplateHighlighter, parse] = await Promise.all([ + createHighlighterCore({ + ...baseConfig, + langs: [ + import('@shikijs/langs/js'), + import('@shikijs/langs/ts'), + import('@shikijs/langs/json'), + import('@shikijs/langs/html'), + import('@shikijs/langs/css'), + import('@shikijs/langs/bash'), + import('@shikijs/langs/shell'), + import('@shikijs/langs/vue'), + import('@shikijs/langs/md'), + import('@shikijs/langs/yaml'), + import('@shikijs/langs/jsx'), + import('@shikijs/langs/tsx'), + import('@shikijs/langs/scss'), + import('@shikijs/langs/less'), + ], + }), + // 创建Vue模板专用高亮器 + // 实测发现 markdown, yaml, jsx, tsx, scss, less 语言包会影响 vue 的 template 块高亮,导致 vue 的特殊语法高亮不准确,因此创建一个专用的模板高亮器 + createHighlighterCore({ + ...baseConfig, + langs: [ + import('@shikijs/langs/vue'), + import('@shikijs/langs/js'), + import('@shikijs/langs/ts'), + import('@shikijs/langs/html'), + import('@shikijs/langs/css'), + ], + }), + import('@vue/compiler-sfc').then((r) => r.parse), + ]); const stripPreCodeReg = /([\s\S]*?)<\/code><\/pre>/; /** 去除高亮代码的首尾
...
*/ diff --git a/packages/docs/plugins/markdown/vueMdTranslate.ts b/packages/docs/plugins/markdown/vueMdTranslate.ts index cbbcec9bde47e1104481196c50d1c5694b4e4045..376905b2a4ea1a67a0343634055c83e1f0a0e870 100644 --- a/packages/docs/plugins/markdown/vueMdTranslate.ts +++ b/packages/docs/plugins/markdown/vueMdTranslate.ts @@ -1,9 +1,11 @@ import Markdown from 'unplugin-vue-markdown/vite'; import { MarkdownItAsync } from 'markdown-it-async'; -import { markdownItOptions, markdownItPlugins } from './common' +import { markdownItOptions, markdownItPlugins } from './common'; +import { createHighlighter } from './highlight'; -export const markdownItSetup = function (md: MarkdownItAsync) { +export const markdownItSetup = async function (md: MarkdownItAsync) { markdownItPlugins.forEach((item) => md.use(item)); + md.options.highlight = await createHighlighter(); }; /** * vite 插件,markdown中可导入并使用vue组件;同时将 markdown 转换为 vue 组件 @@ -11,5 +13,5 @@ export const markdownItSetup = function (md: MarkdownItAsync) { export const plugin = Markdown({ markdownItOptions, markdownItSetup, - exclude: /\?vue&type=docs/ + exclude: /\?vue&type=docs/, }); diff --git a/packages/docs/plugins/markdown/wrapTable.ts b/packages/docs/plugins/markdown/wrapTable.ts index 07b998e06bc37c2db511eb04b1a92fe85cf2298d..91bb656eb880132a4d1d52866c2ef43b7208829e 100644 --- a/packages/docs/plugins/markdown/wrapTable.ts +++ b/packages/docs/plugins/markdown/wrapTable.ts @@ -10,6 +10,6 @@ export default function wrapTable(md: MarkdownItAsync) { return `
`; }; md.renderer.rules.table_close = function () { - return `
`; + return ''; }; } diff --git a/packages/docs/src/components/DemoUsage.vue b/packages/docs/src/components/DemoUsage.vue index 213e433d9bf3d31ac696375fc5f9968c6f398ef6..4d42d1ecb97cdcff15ab641606d6e6bf3cae8bc6 100644 --- a/packages/docs/src/components/DemoUsage.vue +++ b/packages/docs/src/components/DemoUsage.vue @@ -1,15 +1,8 @@ diff --git a/packages/docs/src/utils/code.ts b/packages/docs/src/utils/code.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f586c0a03bedb5ef3068743ba8d67b5a603b6b1 --- /dev/null +++ b/packages/docs/src/utils/code.ts @@ -0,0 +1,56 @@ +import { type Component } from 'vue'; +import { type CompilerOptions, type CodegenResult } from '@vue/compiler-dom'; +import { createHighlighter } from '../../plugins/markdown/highlight'; + +let Vue: any; +let highlighter: undefined | ((code: string, lang: string) => string); +let compile: undefined | ((template: string, options?: CompilerOptions) => CodegenResult); +let prettierModule: any; +let htmlPlugin: any; +let babelPlugin: any; +let postPlugin: any; +let tsPlugin: any; +let esTreePlugin: any; +/** + * 将模板编译为组件 + * @param template 模板字符串 + * @param ctx 在模板中使用的数据 + * @param options + * @returns 组件 + */ +export async function compileComponent(template: string, ctx: any = {}, options: Omit = {}): Promise { + Vue = Vue ?? (await import('vue')); + compile = compile ?? (await import('@vue/compiler-dom').then((m) => m.compile)); + const { code } = compile!(template, { + ...options, + mode: 'function', + }); + const component = new Function('Vue', 'ctx', code)(Vue, ctx); + return component; +} + +export async function highlight(code: string, lang: string) { + if (!highlighter) { + highlighter = await createHighlighter(); + } + return highlighter(code, lang); +} + +export async function prettier(code: string, parser: string): Promise { + if (!prettierModule) { + [prettierModule, htmlPlugin, babelPlugin, postPlugin, tsPlugin, esTreePlugin] = await Promise.all([ + import('prettier'), + import('prettier/plugins/html'), + import('prettier/plugins/babel'), + import('prettier/plugins/postcss'), + import('prettier/plugins/typescript'), + import('prettier/plugins/estree'), + ]); + } + return prettierModule.format(code, { + parser, + plugins: [htmlPlugin, esTreePlugin, babelPlugin, postPlugin, tsPlugin], + singleQuote: true, + printWidth: 120, + }); +} diff --git a/packages/docs/src/utils/compileComponent.ts b/packages/docs/src/utils/compileComponent.ts deleted file mode 100644 index 61fb54d02b0cd95566bd611a780608f0855c6cf9..0000000000000000000000000000000000000000 --- a/packages/docs/src/utils/compileComponent.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as Vue from 'vue'; -import { compile, type CompilerOptions } from '@vue/compiler-dom'; - -/** - * 将模板编译为组件 - * @param template 模板字符串 - * @param ctx 在模板中使用的数据 - * @param options - * @returns 组件 - */ -export function compileComponent(template: string, ctx: any = {}, options: Omit = {}): Vue.Component { - const { code } = compile(template, { - ...options, - mode: 'function', - }); - const component = new Function('Vue', 'ctx', code)(Vue, ctx); - return component; -} diff --git a/packages/docs/vite.config.ts b/packages/docs/vite.config.ts index 45cedcbf18bf2885c821905623dc3f39a453ea76..a3ff014cf2b7f683869c0489cfe6752d6b99106e 100644 --- a/packages/docs/vite.config.ts +++ b/packages/docs/vite.config.ts @@ -16,9 +16,7 @@ export default defineConfig({ outDir: './dist', }, plugins: [ - vue({ - include: [/\.vue$/, /\.md$/], - }), + vue({ include: [/\.vue$/, /\.md$/] }), injectDemoAndApi(), injectDemoSource(), injectDemoDocs(),