diff --git a/devui/vue-devui.ts b/devui/vue-devui.ts index 2a0a2d82e5fdcb3e24f3903733d4ed1491650413..f189af5a72f71d4aa3560d488852ad090373d35b 100644 --- a/devui/vue-devui.ts +++ b/devui/vue-devui.ts @@ -1,38 +1,85 @@ -import { App } from 'vue'; - -// 通用 -import Button from './button'; -import Icon from './icon'; -import Panel from './panel'; - -// 导航 -import Tabs from './tabs'; - -// 反馈 -import Alert from './alert/alert'; -import DLoading, { LoadingService, Loading } from './loading'; - -// 数据录入 -import Checkbox from './checkbox'; -import Radio from './radio'; -import Switch from './switch'; -import TagsInput from './tags-input'; -import TextInput from './text-input'; - -// 数据展示 -import Avatar from './avatar'; -import Carousel from './carousel'; - -function install(app: App): void { - const packages = [ Button, Icon, Panel, Tabs, Alert, DLoading, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel ]; - packages.forEach((item: any) => { - if (item.install) { - app.use(item); - } else if (item.name) { - app.component(item.name, item); + +import { App } from 'vue' + +import DAccordion from './accordion' +import DAlert from './alert' +import DAnchor from './anchor' +import DAvatar from './avatar' +import DButton from './button' +import DCard from './card' +import DCarousel from './carousel' +import DCheckbox from './checkbox' +import DIcon from './icon' +import DLoading from './loading' +import DPanel from './panel' +import DRadio from './radio' +import DSearch from './search' +import DSwitch from './switch' +import DTabs from './tabs' +import DTagsInput from './tags-input' +import DTextInput from './text-input' + +const packages = [ + DAccordion, + DAlert, + DAnchor, + DAvatar, + DButton, + DCard, + DCarousel, + DCheckbox, + DIcon, + DLoading, + DPanel, + DRadio, + DSearch, + DSwitch, + DTabs, + DTagsInput, + DTextInput +].map(registerInstall) + +function registerInstall(pkg: any) { + if ('install' in pkg) return pkg + + pkg.install = function(app: App) { + if (pkg.installed) return + + if ('setup' in pkg) { + app.component(pkg.name, pkg) + } else if ([].includes(pkg.name)) { + app.directive(pkg.name, pkg) + } else { + app.config.globalProperties[`$${pkg.name.replace(/^use/, (c: any) => c.slice(-1).toLowerCase())}`] = pkg } - }); + } + + return pkg } -export { Button, Icon, Panel, Tabs, Alert, LoadingService, Loading, Checkbox, Radio, Switch, TagsInput, TextInput, Avatar, Carousel }; -export default { install, version: '0.0.1' }; +export { + DAccordion, + DAlert, + DAnchor, + DAvatar, + DButton, + DCard, + DCarousel, + DCheckbox, + DIcon, + DLoading, + DPanel, + DRadio, + DSearch, + DSwitch, + DTabs, + DTagsInput, + DTextInput +} + +export default { + version: '0.0.1', + install(app: App) { + packages.forEach((p) => app.use(p)) + } +} diff --git a/package.json b/package.json index 394425958b56d79e8e41ddabe2b47aece1aa18ba..6a077f51b2158d6b5eeafe0204963503c9cf491c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "app:dev": "vite", "app:build": "vite build", "build:lib": "yarn run generate:devui && vite build --config vite.config.build.ts && cp package.json dist && cp README.md dist", + "build:devui": "node scripts/create-devui", "test": "jest --config jest.config.js", "ls-lint": "ls-lint", "lint": "eslint \"{src,devui}/**/*.{vue,js,ts,jsx,tsx}\"", @@ -47,6 +48,7 @@ "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", "@ls-lint/ls-lint": "^1.10.0", + "@types/fs-extra": "^9.0.12", "@types/jest": "^26.0.23", "@typescript-eslint/eslint-plugin": "^4.27.0", "@typescript-eslint/parser": "^4.27.0", @@ -62,9 +64,11 @@ "esbuild-register": "^2.6.0", "eslint": "^7.28.0", "eslint-plugin-vue": "^7.11.1", + "fs-extra": "^10.0.0", "husky": "^4.3.7", "jest": "^27.0.4", "lint-staged": "^11.0.0", + "lodash": "^4.17.21", "sass": "^1.32.2", "shelljs": "^0.8.4", "stylelint": "^13.13.1", diff --git a/scripts/create-devui.js b/scripts/create-devui.js new file mode 100644 index 0000000000000000000000000000000000000000..f62375f16385e1b97501643a86ada72082653a70 --- /dev/null +++ b/scripts/create-devui.js @@ -0,0 +1,186 @@ +const fs = require('fs-extra') +const _ = require('lodash') +const { resolve, relative } = require('path') + +const DEVUI_DIR = resolve(__dirname, '../devui') +const DEVUI_DIRECTIVE_DIR = resolve(DEVUI_DIR, 'directives') +const DEVUI_FILE = resolve(DEVUI_DIR, 'vue-devui.ts') +const DEVUI_PREFIX = 'D' +const DEVUI_IGNORE_DIRS = ['shared', 'style', 'directives'] +const DEVUI_DIRECTIVE_IGNORE_DIRS = [] +const INDEX_FILE = 'index.ts' + +function bigCamelCase(str) { + return _.upperFirst(_.camelCase(str)) +} + +function getUseName(p) { + return p.startsWith('use') ? p.slice(3) : p +} + +function resolveDirFileInfo(targetDir, ignoreDirs = []) { + return fs + .readdirSync(targetDir) + .filter( + (dir) => + // 过滤:必须是目录,且不存在与忽略目录内,拥有 INDEX_FILE + fs.statSync(resolve(targetDir, dir)).isDirectory() && + !ignoreDirs.includes(dir) && + fs.existsSync(resolve(targetDir, dir, INDEX_FILE)) + ) + .map((dir) => ({ + name: DEVUI_PREFIX + bigCamelCase(dir), + dirname: dir, + path: resolve(targetDir, dir, INDEX_FILE) + })) +} + +function parseExportByFileInfo(fileInfo) { + const exportModule = {} + const componentIndexContent = fs.readFileSync(fileInfo.path, { encoding: 'utf-8' }) + + const exportRe = /export/ + const defaultRe = /export (\{[\s\n])?(\s*)default/ + const useRe = /export {([\s\n])?(\s*)use[A-Z]/ + + if (!exportRe.test(componentIndexContent)) { + return exportModule + } + + if (defaultRe.test(componentIndexContent)) { + exportModule.default = fileInfo.name + } + + if (useRe.test(componentIndexContent)) { + const useArr = [] + + let searchContent = componentIndexContent + while (searchContent.search(useRe) !== -1) { + const reStartIndex = componentIndexContent.search(useRe) + const useStartIndex = componentIndexContent.indexOf('use', reStartIndex) + const useEndIndex = componentIndexContent.indexOf('}', useStartIndex) + + const useContent = componentIndexContent.slice(useStartIndex, useEndIndex) + + useContent + .replace(/(\s|\r|\n)/g, '') + .split(',') + .forEach((use) => useArr.push(use)) + + searchContent = componentIndexContent.slice(useEndIndex) + } + + exportModule.use = useArr + } + + if (!_.isEmpty(exportModule)) { + exportModule.fileInfo = fileInfo + } + + return exportModule +} + +function createTemplate(exportModules = []) { + const packages = [] + + const importPackageStr = exportModules + .map((module) => { + const hasDefault = 'default' in module + const hasUse = 'use' in module + + const component = hasDefault ? module.default : '' + const use = hasUse ? module.use.join(', ') : '' + const relativePath = relative(DEVUI_FILE, module.fileInfo.path) + .replace(/\\/g, '/') + .replace('..', '.') + .replace('/' + INDEX_FILE, '') + + const importStr = `import ${component}${ + hasUse ? `${hasDefault ? ', {' : '{'} ${use} }` : '' + } from '${relativePath}'` + + if (hasDefault) { + packages.push(module.default) + } + + if (hasUse) { + packages.push(...module.use) + } + + return importStr + }) + .join('\n') + + const uses = packages.filter((p) => p.startsWith('use')) + + const directives = packages.filter((p) => !/^(D|use)/.test(p)) + + const template = ` +import { App } from 'vue' + +${importPackageStr} +${uses.length > 0 ? '\n' + uses.map((p) => `const ${getUseName(p)} = registerInstall(${p})`).join('\n') + '\n' : ''} +const packages = [ + ${packages.map((p) => getUseName(p)).join(',\n ')} +].map(registerInstall) + +function registerInstall(pkg: any) { + if ('install' in pkg) return pkg + + pkg.install = function(app: App) { + if (pkg.installed) return + + if ('setup' in pkg) { + app.component(pkg.name, pkg) + } else if ([${directives.length > 0 ? "'" + directives.join("', '") + "'" : ''}].includes(pkg.name)) { + app.directive(pkg.name, pkg) + } else { + app.config.globalProperties[\`\$\${pkg.name.replace(/^use/, (c: any) => c.slice(-1).toLowerCase())}\`] = pkg + } + } + + return pkg +} + +export { + ${packages.map((p) => (p.startsWith('use') ? `${p},\n ${getUseName(p)}` : p)).join(',\n ')} +} + +export default { + version: '0.0.1', + install(app: App) { + packages.forEach((p) => app.use(p)) + } +} +` + + return template +} + +function createDevui() { + const componentFileInfo = resolveDirFileInfo(DEVUI_DIR, DEVUI_IGNORE_DIRS) + const directiveFileInfo = resolveDirFileInfo(DEVUI_DIRECTIVE_DIR, DEVUI_DIRECTIVE_IGNORE_DIRS) + const exportModules = [] + + componentFileInfo.forEach((f) => { + const em = parseExportByFileInfo(f) + + if (_.isEmpty(em)) return + + exportModules.push(em) + }) + + directiveFileInfo.forEach((f) => { + const em = parseExportByFileInfo(f) + + if (_.isEmpty(em)) return + + exportModules.push(_.update(em, 'default', (n) => n.slice(1))) + }) + + const template = createTemplate(exportModules) + + fs.writeFile(DEVUI_FILE, template, { encoding: 'utf-8' }) +} + +createDevui() diff --git a/yarn.lock b/yarn.lock index 45d6914ca1992cd465067b948bd70c4a9caf367b..f563a2576217be789f8306a91b652295dbaee60f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1666,6 +1666,13 @@ resolved "https://registry.nlark.com/@types/estree/download/@types/estree-0.0.48.tgz#18dc8091b285df90db2f25aa7d906cfc394b7f74" integrity sha1-GNyAkbKF35DbLyWqfZBs/DlLf3Q= +"@types/fs-extra@^9.0.12": + version "9.0.12" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz#9b8f27973df8a7a3920e8461517ebf8a7d4fdfaf" + integrity sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw== + dependencies: + "@types/node" "*" + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.nlark.com/@types/graceful-fs/download/@types/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -3627,8 +3634,8 @@ form-data@^3.0.0: fs-extra@^10.0.0: version "10.0.0" - resolved "https://registry.nlark.com/fs-extra/download/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha1-n/YbZV3eU/s0qC34S7IUzoAuF8E= + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1"