From b32a7007f133caa03d64331ed7b8c65b0534fdcf Mon Sep 17 00:00:00 2001 From: gimmyhehe <975402925@qq.com> Date: Wed, 7 Aug 2024 09:27:25 +0800 Subject: [PATCH] ci: add automate script to transform demos to add "Tiny" prefix (#1832) --- internals/automate/package.json | 9 +- internals/automate/src/doc-prefix.ts | 172 +++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 internals/automate/src/doc-prefix.ts diff --git a/internals/automate/package.json b/internals/automate/package.json index 5b4ed79da..ba3a502fc 100644 --- a/internals/automate/package.json +++ b/internals/automate/package.json @@ -7,6 +7,8 @@ "scripts": { "start": "esno ./src/index.ts", "scan": "esno ./src/demos-scan.ts", + "// ---------- 给文档组件加上Tiny前缀 ----------": "", + "doc-prefix": "esno ./src/doc-prefix.ts", "diff": "esno ./src/aui-diff.ts", "add-dep": "esno ./src/add-dependencies.ts", "auto-build-pub": "esno ./src/publish/index.ts" @@ -22,7 +24,12 @@ "sheetjs-style": "^0.15.8", "commander": "^10.0.0", "semver": "^7.3.8", - "chalk": "2.4.2" + "chalk": "2.4.2", + "@babel/parser": "^7.18.13", + "@babel/traverse": "^7.18.13", + "@babel/generator": "^7.18.13", + "@babel/template": "^7.18.13", + "@vue/compiler-sfc": "^3.4.21" }, "keywords": [], "author": "", diff --git a/internals/automate/src/doc-prefix.ts b/internals/automate/src/doc-prefix.ts new file mode 100644 index 000000000..24a5cdf7a --- /dev/null +++ b/internals/automate/src/doc-prefix.ts @@ -0,0 +1,172 @@ +import fg from 'fast-glob' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { parse } from '@vue/compiler-sfc' +import { parse as babelParse } from '@babel/parser' +import traverse from '@babel/traverse' +import generate from '@babel/generator' +import * as prettier from 'prettier' +import { writeFile } from 'node:fs/promises' +import { safeReadFile } from './utils/files' + +const __filename = fileURLToPath(import.meta.url) + +const __dirname = path.dirname(__filename) + +const docPath = path.join(__dirname, '../../../examples/sites/demos') + +const generateTraverse = traverse.default + +const addPrefix = async (code) => { + const ast = babelParse(code, { + sourceType: 'module', + plugins: ['typescript', 'jsx'] + }) + + const renameVar = {} + generateTraverse( + ast, + { + ImportDeclaration(path) { + // 解析@opentiny/vue的引入 + const depName = path.node?.source?.value + if (depName === '@opentiny/vue') { + const specifiers = path.node.specifiers + specifiers?.forEach((importSpecifier) => { + const { local, imported } = importSpecifier + if (local.name.startsWith('Tiny')) { + if (!imported.name.startsWith('Tiny')) { + imported.name = 'Tiny' + imported.name + } + } else { + renameVar[local.name] = 'Tiny' + local.name + local.name = 'Tiny' + local.name + imported.name = 'Tiny' + imported.name + } + }) + } + }, + Identifier(path) { + const name = path.node.name + // 不能用renameVar[name]代替,否则代码中包含原型链上方法会报错 + /* eslint-disable no-prototype-builtins */ + if (renameVar.hasOwnProperty(name)) { + path.node.name = renameVar[name] + } + const parent = path.parentPath?.node + if (parent.type === 'ObjectProperty') { + const { key, value } = parent + if (key.name === value.name) { + parent.shorthand = true + } + } + }, + ObjectProperty(path) { + const node = path.node + const { key, value } = node + if (key.name === value.name) { + node.shorthand = true + } + } + }, + {} + ) + let newCode + + newCode = generate.default(ast, { retainLines: true }).code || '' + const formatCode = await prettier.format(newCode, { + semi: false, + parser: 'typescript', + singleQuote: true, + trailingComma: 'none', + 'printWidth': 120, + 'endOfLine': 'auto', + 'bracketSpacing': true, + 'jsxBracketSameLine': true + }) + return formatCode +} + +const getAttrsString = (attrs) => { + let attrStr = '' + if (!attrs) { + return attrStr + } + + Object.keys(attrs).forEach((key) => { + const val = attrs[key] + if (val === true) { + attrStr += ` ${key}` + } else { + attrStr += ` ${key}="${val}"` + } + }) + return attrStr +} + +const generateTagContent = (tagDescriptor) => { + if (!tagDescriptor) { + return '' + } + const { attrs, content, type } = tagDescriptor + return `<${type}${getAttrsString(attrs)}>${content}` +} + +// 拼接完整的SFC文件 +const generateSFC = (descriptor) => { + const { script, scriptSetup, styles = [], template } = descriptor + const contentArr = [ + generateTagContent(template), + generateTagContent(script), + generateTagContent(scriptSetup), + ...styles.map(generateTagContent) + ] + return contentArr.filter((i) => i).join('\n\n') + '\n' +} + +// 解析SFC文件,获取其中的script标签内容后转换 +const transformSFC = async (code) => { + const { descriptor } = parse(code) + const { script, scriptSetup } = descriptor + if (script) { + script.content = '\n' + (await addPrefix(script.content)) || script.content + } + if (scriptSetup) { + scriptSetup.content = '\n' + (await addPrefix(scriptSetup.content)) || scriptSetup.content + } + return generateSFC(descriptor) +} + +const transformDemos = async () => { + const demoFiles = await fg(['**/*.vue'], { + dot: true, + cwd: docPath, + ignore: ['**/node_modules', '__test__'] + }) + + let handelResolve + + const len = demoFiles.length + let finishNum = 0 + const promise = new Promise((resolve) => { + handelResolve = resolve + }) + + demoFiles.forEach(async (filename, index) => { + if (index >= len) return + const filePath = path.join(docPath, filename) + const fileContent = await safeReadFile(filePath) + const newContent = await transformSFC(fileContent) + await writeFile(filePath, newContent) + ++finishNum + if (finishNum === len) { + handelResolve(finishNum) + } + }) + return promise +} + +transformDemos().then((finishNum) => { + /* eslint-disable no-console */ + console.log(`${finishNum}个demos组件添加Tiny前缀任务完成`) +})