From 58c9cdb529f6d37d0e2691b0128b4070f9d99ba1 Mon Sep 17 00:00:00 2001 From: sakurayinfei <970412446@qq.com> Date: Tue, 4 Nov 2025 11:51:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(doc):=20=E4=BC=98=E5=8C=96=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E7=83=AD=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=B5=8F=E8=A7=88=E5=99=A8=E4=B8=8D=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0=EF=BC=9B?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=9D=97=E9=87=8D=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E7=9A=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/plugins/generateComponentRouter.ts | 26 ++-- packages/docs/plugins/injectDemoAndApi.ts | 112 +++++++----------- packages/docs/plugins/injectDemoSource.ts | 18 ++- 3 files changed, 74 insertions(+), 82 deletions(-) diff --git a/packages/docs/plugins/generateComponentRouter.ts b/packages/docs/plugins/generateComponentRouter.ts index 75b549a74..244e7f892 100644 --- a/packages/docs/plugins/generateComponentRouter.ts +++ b/packages/docs/plugins/generateComponentRouter.ts @@ -11,6 +11,7 @@ import { getLangByFileName } from '../helper/utils'; const __fileName = fileURLToPath(import.meta.url); const searchBase = resolve(__fileName, '../../../opendesign/src'); const output = resolve(__fileName, '../../src/router/components.ts'); +let preRouteContent = ''; function debounce) => any>(fn: T, wait: number = 0, runFirst: boolean = true) { let handler = null; return (...args: Array) => { @@ -35,11 +36,13 @@ const emit = debounce(() => { */ glob('**/__docs__/index.*.md', { cwd: searchBase, posix: true }) .then((files) => { - return files.map((file) => { - const fullPath = resolve(searchBase, file); - const content = fse.readFileSync(fullPath).toString(); - return { content, file, fullPath, name: file.match(/([^/]+)\/__docs__\/?/)?.[1], lang: getLangByFileName(file).lang }; - }); + return Promise.all( + files.sort().map(async (file) => { + const fullPath = resolve(searchBase, file); + const content = await fse.readFile(fullPath, 'utf-8'); + return { content, file, fullPath, name: file.match(/([^/]+)\/__docs__\/?/)?.[1], lang: getLangByFileName(file).lang }; + }), + ); }) .then((fileContents) => { /** @@ -54,7 +57,7 @@ const emit = debounce(() => { meta: { ...matterData.data, lang: info.lang, - sidebarName: 'components' + sidebarName: 'components', }, }; }); @@ -76,12 +79,19 @@ ${res `; }) .then((res) => { + if (res === preRouteContent) { + // 避免不必要的更新导致页面自动刷新 + return; + } + preRouteContent = res; // 使用prettier格式化输出的代码 return prettier.format(res, { parser: 'typescript', plugins: [tsPlugin], singleQuote: true, printWidth: 160 }); }) .then((res) => { - // 写代码文件 - return fse.writeFile(output, res); + if (res) { + // 写代码文件 + return fse.writeFile(output, res); + } }); }, 1000); diff --git a/packages/docs/plugins/injectDemoAndApi.ts b/packages/docs/plugins/injectDemoAndApi.ts index cd9ede937..d808b5058 100644 --- a/packages/docs/plugins/injectDemoAndApi.ts +++ b/packages/docs/plugins/injectDemoAndApi.ts @@ -6,24 +6,24 @@ import { parse } from '@vue/compiler-sfc'; import { parseDocsCode, generateCode, asyncReplace } from '../helper/utils'; const entryFileRegex = /index\.([\w-]+)\.md$/; -const apiFileRegex = /api\.([\w-]+)\.md$/; const virtualModules = new Map(); +type ImportMeta = { path: string; default?: string; all?: string; lang?: string }; +type ImportRecord = Record; /** 生成 import 语句 */ -const genImportedExpression = (imported: Array<{ path: string; default?: string; all?: string }>) => { - return `${imported +const genImportedExpression = (imported: ImportRecord) => { + return Object.values(imported) .map((item) => { let importStr = 'import '; if (item.default) { importStr += `${item.default} `; - } - if (item.all) { + } else if (item.all) { importStr += `* as ${item.all} `; } importStr += `from ${JSON.stringify(item.path)};`; return importStr; }) - .join('\n')}`; + .join('\n'); }; const resolveActiveTheme = (theme: string) => { let _theme = theme.toLowerCase(); @@ -56,7 +56,7 @@ const resolveActiveTheme = (theme: string) => { }; /** - * 转化 usage 指令导入的组件 + * 转化 case usage 指令导入的组件 * @param code 组件源代码 * @param id 入口文件id * @param mode 插件运行环境 @@ -65,12 +65,7 @@ const resolveActiveTheme = (theme: string) => { * @returns 转化后的代码 */ const transformVueDemo = (code: string, id: string, mode: 'dev' | 'build' | 'unknown', activeThemes?: string[], viteDevServer?: ViteDevServer) => { - const imported: { - default?: string; - all?: string; - lang?: string; - path: string; - }[] = []; + const imported: ImportRecord = {}; const { customBlocks: _customBlocks, scriptSetup: _scriptSetup, styles } = parse(code).descriptor; // 因为要修改 customBlocks 和 scriptSetup,所以复制一份 const customBlocks = [..._customBlocks]; @@ -85,14 +80,18 @@ const transformVueDemo = (code: string, id: string, mode: 'dev' | 'build' | 'unk // 将 docs 自定义块按 zh-CN,en-US 分隔为多个虚拟模块然后导入。这些虚拟模块后经由 vueMdTranslate 插件编译为 vue 组件 parseDocsCode(customBlocks[docsBlockIdx].content).forEach(({ lang: docsLang, code: docCode }, index) => { const virtualId = join(dirname(id), `virtual-${docsLang}.md`).replace(/\\/g, '/'); - imported.push({ path: virtualId, default: `AutoInjectDocs${index}`, lang: docsLang }); - if (virtualModules.has(virtualId) && mode === 'dev' && viteDevServer) { + const defaultName = `AutoInjectDocs${index}`; + imported[defaultName] = { path: virtualId, default: `AutoInjectDocs${index}`, lang: docsLang }; + const preDocCode = virtualModules.get(virtualId); + if (mode === 'dev' && viteDevServer && preDocCode && preDocCode !== docCode) { + // 通知虚拟文件更新 viteDevServer.watcher.emit('change', virtualId); } virtualModules.set(virtualId, docCode); }); customBlocks.splice(docsBlockIdx, 1); if (scriptSetup) { + // 导入 docs 虚拟模块 scriptSetup.content = `${scriptSetup.content}\n;${genImportedExpression(imported)}`; } } @@ -103,7 +102,9 @@ const transformVueDemo = (code: string, id: string, mode: 'dev' | 'build' | 'unk }); scriptSetup.content = `${scriptSetup.content}\n;const _style = ${JSON.stringify(styleCode)};\n`; } - const docsJson = `{${imported.map((item) => `'${item.lang}': ${item.default}`).join(',')}}`; + const docsJson = `{${Object.values(imported) + .map((item) => `'${item.lang}': ${item.default}`) + .join(',')}}`; // 补充 usage 的 vue 文件的 template 块 const template = ``; return `${[...customBlocks, scriptSetup, ...styles] @@ -113,22 +114,11 @@ const transformVueDemo = (code: string, id: string, mode: 'dev' | 'build' | 'unk }) .join('\n')}\n${template}`; }; -const transformMdEntry = async ( - code: string, - id: string, - usageFiles: Map, - mode: 'dev' | 'build' | 'unknown', - viteDevServer?: ViteDevServer, -) => { - const imported: { - default?: string; - all?: string; - lang?: string; - path: string; - }[] = []; + +/** 将指令case usage api转化为文件导入 */ +const transformMdEntry = async (code: string, id: string, usageFiles: Map) => { + const imported: ImportRecord = {}; const lang = getLangByFileName(id); - // 避免引入同一个文件出现同名符号 - let importedId = 0; // 将 注释替换成 // 将 注释替换成 let newCode = await asyncReplace(code, //gi, async (match) => { @@ -138,44 +128,43 @@ const transformMdEntry = async ( const fileName = paths[paths.length - 1]; const activeThemes = resolveActiveTheme(_activeTheme || ''); if (directive === 'api') { - // 拼接 api 文件 + // 导入 api 文件 + // 通过导入而非字符串替换的原因是为了将 api 文件作为一个独立的模块, + // 当 api 文件自身更新时才能独立触发热更新 const apiFile = join(dirname(id), ...dirs, `${fileName}-api.${lang.lang}.md`); + const defaultName = `AutoInjectApi${fileName}`; if (await fsp.stat(apiFile).catch(() => false)) { - return fsp.readFile(apiFile, 'utf-8'); + imported[defaultName] = { path: apiFile, default: defaultName }; + return `<${defaultName} />`; } } + // 导入 demo 文件 const demoFile = join(dirname(id), ...dirs, `./__case__/${fileName}.vue`); if (await fsp.stat(demoFile).catch(() => false)) { - importedId++; - const importedName = `AutoInject${fileName}${importedId}`; - if (directive === 'case') { - imported.push({ - default: importedName, - path: demoFile, - }); - return ``; - } else { - // 此处只导入 usage 指令指定的 vue 模块。该 vue 模块的还需在 transformVueDemo 函数中转换 - imported.push({ + const importedName = `AutoInject${fileName}`; + if (!imported[importedName]) { + imported[importedName] = { path: demoFile, default: importedName, - }); - const usageFileId = demoFile.replace(/\\/g, '/'); - if (mode === 'dev' && viteDevServer) { - viteDevServer.watcher.emit('change', usageFileId); - } - usageFiles.set(usageFileId, activeThemes); - return `<${importedName} />`; + }; } + if (directive === 'case') { + return ``; + } + const usageFileId = demoFile.replace(/\\/g, '/'); + usageFiles.set(usageFileId, activeThemes); + return `<${importedName} />`; } return match[0]; }); // 插入需要导入的模块 - if (imported.length) { - newCode += `\n`; + const importExp = genImportedExpression(imported); + if (importExp) { + newCode += `\n\n\n`; } return newCode; }; + /** * vite 插件,在 index..md 添加 /__docs__/__case__ 组件;拼接 api.zh-CN.md 文件 * @returns Plugin @@ -209,22 +198,7 @@ export function injectDemoAndApi(): Plugin { // 处理 md 入口文件 if (entryFileRegex.test(id) && !id.startsWith('virtual:')) { - return transformMdEntry(code, id, usageFiles, this.environment.mode, viteDevServer); - } - }, - // 处理 api.*.md 文件的热更新 - async handleHotUpdate(ctx) { - const { file } = ctx; - const match = file.match(apiFileRegex); - if (match) { - const entryFile = join(dirname(file), `index.${match[1]}.md`); - if (await fsp.stat(entryFile).catch(() => false)) { - const module = ctx.server.moduleGraph.getModuleById(entryFile.replace(/\\/g, '/')); - if (module) { - ctx.server.moduleGraph.invalidateModule(module); - ctx.server.ws.send({ type: 'full-reload' }); - } - } + return transformMdEntry(code, id, usageFiles); } }, }; diff --git a/packages/docs/plugins/injectDemoSource.ts b/packages/docs/plugins/injectDemoSource.ts index dcf84c13d..9c87029ae 100644 --- a/packages/docs/plugins/injectDemoSource.ts +++ b/packages/docs/plugins/injectDemoSource.ts @@ -1,6 +1,6 @@ import fsp from 'node:fs/promises'; import { createFilter, type Plugin } from 'vite'; -import { parse } from '@vue/compiler-sfc'; +import { parse, type SFCDescriptor } from '@vue/compiler-sfc'; import { generateCode } from '../helper/utils'; const virtualModules = new Map(); @@ -13,8 +13,7 @@ const getVirtualId = (id: string) => { * @param source case文件源代码 * @returns 经过清理后的源代码组件 */ -const generateVirtualModule = (source: string) => { - const { descriptor } = parse(source); +const generateVirtualModule = (descriptor: SFCDescriptor) => { let cleanedSource = ''; if (descriptor.script) { @@ -59,7 +58,12 @@ export function injectDemoSource(): Plugin { if (await fsp.stat(id).then((stat) => stat.isFile())) { const source = await fsp.readFile(id, 'utf-8'); const virtualId = getVirtualId(id); - virtualModules.set(virtualId, generateVirtualModule(source)); + const { descriptor } = parse(source); + if (!descriptor.template) { + // 无 template 块,属于 Usage 运行时编译组件,不需要生成 DemoSource + return; + } + virtualModules.set(virtualId, generateVirtualModule(descriptor)); // Case 组件引入虚拟模块 virtualId,该虚拟模块就是 Case 组件的源代码 return `${code} ;import _DemoSource from ${JSON.stringify(virtualId)}; @@ -70,7 +74,11 @@ _sfc_main.DemoSource = _DemoSource;`; const virtualId = getVirtualId(ctx.file); if (virtualModules.has(virtualId)) { // 当Case组件更新时,同时更新对应的虚拟模块,以实现源码的热更新 - virtualModules.set(virtualId, generateVirtualModule(await ctx.read())); + const { descriptor } = parse(await ctx.read()); + if (!descriptor.template) { + return; + } + virtualModules.set(virtualId, generateVirtualModule(descriptor)); ctx.server.watcher.emit('change', virtualId); } }, -- Gitee From 50b9e1f937c5a3acf5684a762888e4d40ffd9fe2 Mon Sep 17 00:00:00 2001 From: sakurayinfei <970412446@qq.com> Date: Tue, 4 Nov 2025 17:43:59 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(doc):=20=E5=BD=93popover=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E4=B8=AD=E6=B2=A1=E6=9C=89info=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=83=8F=E8=BD=AFOPopover=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/docs/plugins/markdown/popover.ts | 9 ++++++--- packages/docs/src/assets/style/markdown.scss | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/docs/plugins/markdown/popover.ts b/packages/docs/plugins/markdown/popover.ts index bc6f5cde9..cd81df43b 100644 --- a/packages/docs/plugins/markdown/popover.ts +++ b/packages/docs/plugins/markdown/popover.ts @@ -26,10 +26,13 @@ export default function popover(md: MarkdownItAsync) { const token = tokens[idx]; const content = escapeHtml(token.content); const info = escapeHtml(token.info); - return ` - + const tagCode = `${content}`; + return info + ? ` + ${info} -`; +` + : tagCode; }; md.inline.ruler.before('emphasis', 'popover', function (state, silent) { diff --git a/packages/docs/src/assets/style/markdown.scss b/packages/docs/src/assets/style/markdown.scss index d6b7ca8d9..be0e62e1c 100644 --- a/packages/docs/src/assets/style/markdown.scss +++ b/packages/docs/src/assets/style/markdown.scss @@ -113,3 +113,7 @@ li { padding: 0; } } + +.tooltip { + cursor: help; +} -- Gitee From a7dfb399d9ac789c9d21c5a4d1e96170b6e860ab Mon Sep 17 00:00:00 2001 From: sakurayinfei <970412446@qq.com> Date: Tue, 4 Nov 2025 17:45:26 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(doc):=20=E5=88=A0=E9=99=A4CodeContainer?= =?UTF-8?q?=E4=B8=AD=E4=B8=8D=E9=9C=80=E8=A6=81=E7=9A=84=E5=B1=9E=E6=80=A7?= =?UTF-8?q?line-numbers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/docs/plugins/markdown/wrapCodeContainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs/plugins/markdown/wrapCodeContainer.ts b/packages/docs/plugins/markdown/wrapCodeContainer.ts index 290a3507d..f5b805cff 100644 --- a/packages/docs/plugins/markdown/wrapCodeContainer.ts +++ b/packages/docs/plugins/markdown/wrapCodeContainer.ts @@ -13,7 +13,7 @@ export default function wrapCodeContainer(md: MarkdownItAsync) { } const preCode = fence(tokens, idx, options, env, self); return ` - + ${preCode} `; }; -- Gitee