From 9692ac74c25a5dcec0fec7903c74cc6e8e3e8479 Mon Sep 17 00:00:00 2001 From: kagol Date: Wed, 27 Oct 2021 22:10:32 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=E8=A7=A3=E5=86=B3logo=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7032a70e..a930fe3a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- DevUI Logo + DevUI Logo

-- Gitee From 66b9ffff3cd2033d5deb89e323af096f731ca342 Mon Sep 17 00:00:00 2001 From: TinsFox <1414849373@qq.com> Date: Thu, 28 Oct 2021 13:39:53 +0800 Subject: [PATCH 2/7] feat(devui-cli): use typescript to rewrite cli - use typescript to rewrite cli - make cli a package --- .gitignore | 1 + package.json | 4 +- packages/devui-cli/README.md | 7 + packages/devui-cli/bin.js | 2 + packages/devui-cli/package.json | 30 +++ packages/devui-cli/src/bin.ts | 40 +++ packages/devui-cli/src/commands/build.ts | 91 +++++++ packages/devui-cli/src/commands/create.ts | 233 +++++++++++++++++ packages/devui-cli/src/commands/dev.ts | 90 +++++++ .../devui-cli/src/commands/generate-theme.ts | 18 ++ packages/devui-cli/src/index.ts | 14 ++ .../src/inquirers/component.ts} | 12 +- .../src/inquirers/create.ts} | 5 +- packages/devui-cli/src/shared/constant.ts | 46 ++++ packages/devui-cli/src/shared/logger.ts | 16 ++ packages/devui-cli/src/shared/utils.ts | 133 ++++++++++ .../src/templates/component.ts} | 26 +- .../src/templates/vitepress-sidebar.ts | 35 +++ packages/devui-cli/src/templates/vue-devui.ts | 45 ++++ packages/devui-cli/tsconfig.json | 13 + .../devui-vue/devui-cli/commands/build.js | 87 ------- .../devui-vue/devui-cli/commands/create.js | 234 ------------------ .../devui-cli/commands/generate-theme.js | 16 -- packages/devui-vue/devui-cli/index.js | 28 --- .../devui-vue/devui-cli/shared/constant.js | 46 ---- packages/devui-vue/devui-cli/shared/logger.js | 16 -- packages/devui-vue/devui-cli/shared/utils.js | 124 ---------- .../devui-cli/templates/vitepress-sidebar.js | 35 --- .../devui-cli/templates/vue-devui.js | 45 ---- packages/devui-vue/package.json | 13 +- 30 files changed, 848 insertions(+), 657 deletions(-) create mode 100644 packages/devui-cli/README.md create mode 100755 packages/devui-cli/bin.js create mode 100644 packages/devui-cli/package.json create mode 100644 packages/devui-cli/src/bin.ts create mode 100644 packages/devui-cli/src/commands/build.ts create mode 100644 packages/devui-cli/src/commands/create.ts create mode 100644 packages/devui-cli/src/commands/dev.ts create mode 100644 packages/devui-cli/src/commands/generate-theme.ts create mode 100644 packages/devui-cli/src/index.ts rename packages/{devui-vue/devui-cli/inquirers/component.js => devui-cli/src/inquirers/component.ts} (80%) rename packages/{devui-vue/devui-cli/inquirers/create.js => devui-cli/src/inquirers/create.ts} (57%) create mode 100644 packages/devui-cli/src/shared/constant.ts create mode 100644 packages/devui-cli/src/shared/logger.ts create mode 100644 packages/devui-cli/src/shared/utils.ts rename packages/{devui-vue/devui-cli/templates/component.js => devui-cli/src/templates/component.ts} (88%) create mode 100644 packages/devui-cli/src/templates/vitepress-sidebar.ts create mode 100644 packages/devui-cli/src/templates/vue-devui.ts create mode 100644 packages/devui-cli/tsconfig.json delete mode 100644 packages/devui-vue/devui-cli/commands/build.js delete mode 100644 packages/devui-vue/devui-cli/commands/create.js delete mode 100644 packages/devui-vue/devui-cli/commands/generate-theme.js delete mode 100755 packages/devui-vue/devui-cli/index.js delete mode 100644 packages/devui-vue/devui-cli/shared/constant.js delete mode 100644 packages/devui-vue/devui-cli/shared/logger.js delete mode 100644 packages/devui-vue/devui-cli/shared/utils.js delete mode 100644 packages/devui-vue/devui-cli/templates/vitepress-sidebar.js delete mode 100644 packages/devui-vue/devui-cli/templates/vue-devui.js diff --git a/.gitignore b/.gitignore index 7db53e38..fa4da9be 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ yarn-error.log packages/devui-vue/devui/vue-devui.ts packages/devui-vue/devui/theme/theme.scss packages/devui-vue/docs/.vitepress/config/sidebar.ts +/packages/devui-cli/lib/ diff --git a/package.json b/package.json index 05e54180..89d20a48 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,9 @@ "name": "root", "private": true, "scripts": { - "dev": "lerna run dev", + "prepare": "yarn build:cli", + "build:cli": "lerna exec --scope @devui/cli yarn build", + "dev": "lerna exec --scope vue-devui yarn dev", "build": "lerna run build", "build:lib": "lerna run build:lib" }, diff --git a/packages/devui-cli/README.md b/packages/devui-cli/README.md new file mode 100644 index 00000000..3b668baf --- /dev/null +++ b/packages/devui-cli/README.md @@ -0,0 +1,7 @@ +# react-vant-cli + +react vant cli 是一个 React 组件库构建工具,通过 react vant cli 可以快速搭建一套 React 组件库 + +## 特性 + +- 提供丰富的命令,涵盖从开发测试到构建发布的完整流程 diff --git a/packages/devui-cli/bin.js b/packages/devui-cli/bin.js new file mode 100755 index 00000000..4c86924e --- /dev/null +++ b/packages/devui-cli/bin.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./lib/bin'); diff --git a/packages/devui-cli/package.json b/packages/devui-cli/package.json new file mode 100644 index 00000000..607d1c3c --- /dev/null +++ b/packages/devui-cli/package.json @@ -0,0 +1,30 @@ +{ + "name": "@devui/cli", + "version": "1.0.0", + "license": "MIT", + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "bin": { + "devui-cli": "./bin.js" + }, + "scripts": { + "dev": "tsc --watch", + "build": "tsc" + }, + "devDependencies": { + "@types/chalk": "^2.2.0", + "@types/commander": "^2.12.2", + "@types/ora": "^3.2.0", + "@types/node": "^14.11.8", + "@babel/traverse": "^7.15.4", + "@babel/parser": "^7.15.5", + "vite": "^2.4.4" + }, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^8.1.0", + "inquirer": "^8.1.2", + "ora": "^5.4.1", + "typescript": "^4.3.2" + } +} diff --git a/packages/devui-cli/src/bin.ts b/packages/devui-cli/src/bin.ts new file mode 100644 index 00000000..bdb9c74e --- /dev/null +++ b/packages/devui-cli/src/bin.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env node +import {Command} from 'commander'; + +import {create, validateCreateType} from './commands/create'; + + +const {build} = require('./commands/build') +import {dev} from '.' + +import {generateTheme} from './commands/generate-theme'; + +import {CREATE_SUPPORT_TYPES, VERSION} from './shared/constant'; + + +const program = new Command() + +program + .command('create') + .description('创建一个组件模板或配置文件') + .option('-t --type ', `创建类型,可选值:${CREATE_SUPPORT_TYPES.join(', ')}`, validateCreateType) + .option('--ignore-parse-error', '忽略解析错误', false) + .option('--cover', '覆盖原文件', false) + .action(create) + +program + .command('dev') + .description('开发组件库') + .action(dev) + +program + .command('build') + .description('打包组件库') + .action(build) + +program + .command('generate:theme') + .description('生成主题变量文件') + .action(generateTheme) + +program.parse().version(VERSION) diff --git a/packages/devui-cli/src/commands/build.ts b/packages/devui-cli/src/commands/build.ts new file mode 100644 index 00000000..2cf9e286 --- /dev/null +++ b/packages/devui-cli/src/commands/build.ts @@ -0,0 +1,91 @@ +import path from 'path'; +import fs from 'fs'; +import fsExtra from 'fs-extra'; +import {build, defineConfig} from 'vite'; + +import type {UserConfig, InlineConfig} from 'vite' +import {CWD} from '../shared/constant'; + +const vue = require('@vitejs/plugin-vue') +const vueJsx = require('@vitejs/plugin-vue-jsx') + +const entryDir = path.join(CWD, '/devui') +const outputDir = path.join(CWD, 'build') + +const baseConfig: InlineConfig = { + configFile: false, + publicDir: false, + plugins: [vue(), vueJsx()] +} + +const rollupOptions = { + external: ['vue', 'vue-router'], + output: { + globals: { + vue: 'Vue' + } + } +} + +const buildSingle = async (name) => { + await build(({ + ...baseConfig, + build: { + rollupOptions, + lib: { + entry: path.resolve(entryDir, name), + name: 'index', + fileName: 'index', + formats: ['es', 'umd'] + }, + outDir: path.resolve(outputDir, name) + } + })) +} + +const buildAll = async () => { + await build(({ + ...baseConfig, + build: { + rollupOptions, + lib: { + entry: path.resolve(entryDir, 'vue-devui.ts'), + name: 'vue-devui', + fileName: 'vue-devui', + formats: ['es', 'umd'] + }, + outDir: outputDir + } + })) +} + +const createPackageJson = (name) => { + const fileStr = `{ + "name": "${name}", + "version": "0.0.0", + "main": "index.umd.js", + "module": "index.es.js", + "style": "style.css" +}` + + fsExtra.outputFile( + path.resolve(outputDir, `${name}/package.json`), + fileStr, + 'utf-8' + ) +} + +exports.build = async () => { + await buildAll() + + const components = fs.readdirSync(entryDir).filter(name => { + const componentDir = path.resolve(entryDir, name) + const isDir = fs.lstatSync(componentDir).isDirectory() + return isDir && fs.readdirSync(componentDir).includes('index.ts') + }) + + for (const name of components) { + await buildSingle(name) + createPackageJson(name) + } +} diff --git a/packages/devui-cli/src/commands/create.ts b/packages/devui-cli/src/commands/create.ts new file mode 100644 index 00000000..6585360b --- /dev/null +++ b/packages/devui-cli/src/commands/create.ts @@ -0,0 +1,233 @@ +import {resolve} from 'path'; +import fs from 'fs-extra'; +import ora from 'ora'; +import inquirer from 'inquirer'; +import {isEmpty, kebabCase} from 'lodash'; + +import { + COMPONENT_PARTS_MAP, + CREATE_SUPPORT_TYPE_MAP, + CREATE_SUPPORT_TYPES, + CREATE_UNFINISHED_TYPES, + DEVUI_DIR, + DEVUI_NAMESPACE, + DOCS_FILE_NAME, + INDEX_FILE_NAME, + SITES_COMPONENTS_DIR, + TESTS_DIR_NAME, + VITEPRESS_SIDEBAR_FILE, + VITEPRESS_SIDEBAR_FILE_NAME, + VUE_DEVUI_FILE, + VUE_DEVUI_FILE_NAME, + VUE_DEVUI_IGNORE_DIRS +} from '../shared/constant'; +import {bigCamelCase, parseComponentInfo, parseExportByFileInfo, resolveDirFilesInfo} from '../shared/utils'; + +import {createVitepressSidebarTemplate} from '../templates/vitepress-sidebar'; + +import {createVueDevuiTemplate} from '../templates/vue-devui'; + +import {selectCreateType} from '../inquirers/create'; + +import {selectCategory, selectParts, typeName, typeTitle} from '../inquirers/component'; + +import {logger} from '../shared/logger'; + +import { + createComponentTemplate, + createDirectiveTemplate, createDocumentTemplate, createIndexTemplate, createServiceTemplate, + createStyleTemplate, createTestsTemplate, + createTypesTemplate +} from '../templates/component'; + + +export const validateCreateType = (type) => { + const re = new RegExp('^(' + CREATE_SUPPORT_TYPES.map((t) => `(${t})`).join('|') + ')$') + const flag = re.test(type) + + !flag && logger.error(`类型错误,可选类型为:${CREATE_SUPPORT_TYPES.join(', ')}`) + + return flag ? type : null +} + +// TODO: 待优化代码结构 +export const create = async (cwd) => { + let {type} = cwd + + if (isEmpty(type)) { + const result = await inquirer.prompt([selectCreateType()]) + type = result.type + } + + if (CREATE_UNFINISHED_TYPES.includes(type)) { + logger.info('抱歉,该功能暂未完成!') + process.exit(0) + } + + let params:any = {} + + try { + switch (type) { + case CREATE_SUPPORT_TYPE_MAP.component: + params = await inquirer.prompt([typeName(), typeTitle(), selectCategory(), selectParts()]) + params.hasComponent = params.parts.includes('component') + params.hasDirective = params.parts.includes('directive') + params.hasService = params.parts.includes('service') + + await createComponent({params, cwd}) + break + case CREATE_SUPPORT_TYPE_MAP['vue-devui']: + // 创建 devui/vue-devui.ts + await createVueDevui(params, cwd) + // 创建 docs/.vitepress/config/sidebar.ts + await createVitepressSidebar() + break + default: + break + } + } catch (e) { + logger.error(e.toString()) + process.exit(1) + } +} + +async function createComponent(params) { + let {name, hasComponent, hasDirective, hasService} = params + + const componentName = kebabCase(name) + const styleName = kebabCase(name) + const typesName = kebabCase(name) + '-types' + const directiveName = kebabCase(name) + '-directive' + const serviceName = kebabCase(name) + '-service' + const testName = kebabCase(name) + '.spec' + + const _params = { + ...params, + componentName, + typesName, + directiveName, + serviceName, + styleName, + testName + } + + const componentTemplate = createComponentTemplate(_params) + const styleTemplate = createStyleTemplate(_params) + const typesTemplate = createTypesTemplate(_params) + const directiveTemplate = createDirectiveTemplate() + const serviceTemplate = createServiceTemplate(_params) + const indexTemplate = createIndexTemplate(_params) + // 增加测试模板 + const testsTemplate = createTestsTemplate(_params) + // 增加文档模板 + const docTemplate = createDocumentTemplate(_params) + + const componentDir = resolve(DEVUI_DIR, componentName) + const srcDir = resolve(componentDir, 'src') + const testsDir = resolve(DEVUI_DIR, componentName, TESTS_DIR_NAME) + const docsDir = resolve(SITES_COMPONENTS_DIR, componentName) + + if (fs.pathExistsSync(componentDir)) { + logger.error(`${bigCamelCase(componentName)} 组件目录已存在!`) + process.exit(1) + } + + let spinner = ora(`创建组件 ${bigCamelCase(componentName)} 开始...`).start() + + try { + await Promise.all([fs.mkdirs(componentDir), fs.mkdirs(srcDir), fs.mkdirs(testsDir)]) + + const writeFiles = [ + fs.writeFile(resolve(componentDir, INDEX_FILE_NAME), indexTemplate), + fs.writeFile(resolve(testsDir, `${testName}.ts`), testsTemplate), + ] + + if (!fs.existsSync(docsDir)) { + fs.mkdirSync(docsDir) + writeFiles.push(fs.writeFile(resolve(docsDir, DOCS_FILE_NAME), docTemplate)) + } else { + logger.warning(`\n${bigCamelCase(componentName)} 组件文档已存在:${resolve(docsDir, DOCS_FILE_NAME)}`) + } + + if (hasComponent || hasService) { + writeFiles.push(fs.writeFile(resolve(srcDir, `${typesName}.ts`), typesTemplate)) + } + + if (hasComponent) { + writeFiles.push( + fs.writeFile(resolve(srcDir, `${componentName}.tsx`), componentTemplate), + fs.writeFile(resolve(srcDir, `${styleName}.scss`), styleTemplate) + ) + } + + if (hasDirective) { + writeFiles.push(fs.writeFile(resolve(srcDir, `${directiveName}.ts`), directiveTemplate)) + } + + if (hasService) { + writeFiles.push(fs.writeFile(resolve(srcDir, `${serviceName}.ts`), serviceTemplate)) + } + + await Promise.all(writeFiles) + + spinner.succeed(`创建组件 ${bigCamelCase(componentName)} 成功!`) + logger.info(`组件目录:${componentDir}`) + } catch (e) { + spinner.fail(e.toString()) + process.exit(1) + } +} + +async function createVueDevui(params, {ignoreParseError}) { + const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) + const exportModules = [] + + fileInfo.forEach((f) => { + const em = parseExportByFileInfo(f, ignoreParseError) + + if (isEmpty(em)) return + + exportModules.push(em) + }) + + const template = createVueDevuiTemplate(exportModules) + + let spinner = ora(`创建 ${VUE_DEVUI_FILE_NAME} 文件开始...`).start() + + try { + await fs.writeFile(VUE_DEVUI_FILE, template, {encoding: 'utf-8'}) + + spinner.succeed(`创建 ${VUE_DEVUI_FILE_NAME} 文件成功!`) + logger.info(`文件地址:${VUE_DEVUI_FILE}`) + } catch (e) { + spinner.fail(e.toString()) + process.exit(1) + } +} + +async function createVitepressSidebar() { + const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) + const componentsInfo = [] + + fileInfo.forEach((f) => { + const info = parseComponentInfo(f.dirname) + + if (isEmpty(info)) return + + componentsInfo.push(info) + }) + + const template = createVitepressSidebarTemplate(componentsInfo) + + let spinner = ora(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件开始...`).start() + + try { + await fs.writeFile(VITEPRESS_SIDEBAR_FILE, template, {encoding: 'utf-8'}) + + spinner.succeed(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件成功!`) + logger.info(`文件地址:${VITEPRESS_SIDEBAR_FILE}`) + } catch (e) { + spinner.fail(e.toString()) + process.exit(1) + } +} diff --git a/packages/devui-cli/src/commands/dev.ts b/packages/devui-cli/src/commands/dev.ts new file mode 100644 index 00000000..932a50a7 --- /dev/null +++ b/packages/devui-cli/src/commands/dev.ts @@ -0,0 +1,90 @@ +import path from 'path'; +import fs from 'fs'; +import fsExtra from 'fs-extra'; +import {build, defineConfig} from 'vite'; + +import type {UserConfig, InlineConfig} from 'vite' +import {CWD, DEVUI_DIR} from '../shared/constant'; + +const vue = require('@vitejs/plugin-vue') +const vueJsx = require('@vitejs/plugin-vue-jsx') + +const entryDir = path.join(CWD, '/devui') +const outputDir = path.join(CWD, 'build') + +const baseConfig: InlineConfig = { + configFile: false, + publicDir: false, + plugins: [vue(), vueJsx()] +} + +const rollupOptions = { + external: ['vue', 'vue-router'], + output: { + globals: { + vue: 'Vue' + } + } +} + +const buildSingle = async (name) => { + await build(({ + ...baseConfig, + build: { + rollupOptions, + lib: { + entry: path.join(entryDir, name), + name: 'index', + fileName: 'index', + formats: ['es', 'umd'] + }, + outDir: path.join(outputDir, name) + } + })) +} + +const buildAll = async () => { + await build(({ + ...baseConfig, + build: { + rollupOptions, + lib: { + entry: path.join(entryDir, 'vue-devui.ts'), + name: 'vue-devui', + fileName: 'vue-devui', + formats: ['es', 'umd'] + }, + outDir: outputDir + } + })) +} + +const createPackageJson = (name) => { + const fileStr = `{ + "name": "${name}", + "version": "0.0.0", + "main": "index.umd.js", + "module": "index.es.js", + "style": "style.css" +}` + + fsExtra.outputFile( + path.join(outputDir, `${name}/package.json`), + fileStr, + 'utf-8' + ) +} + +export async function dev() { + await buildAll() + const components = fs.readdirSync(entryDir).filter(name => { + const componentDir = path.join(entryDir, name) + const isDir = fs.lstatSync(componentDir).isDirectory() + return isDir && fs.readdirSync(componentDir).includes('index.ts') + }) + + for (const name of components) { + await buildSingle(name) + createPackageJson(name) + } +} diff --git a/packages/devui-cli/src/commands/generate-theme.ts b/packages/devui-cli/src/commands/generate-theme.ts new file mode 100644 index 00000000..e1012e66 --- /dev/null +++ b/packages/devui-cli/src/commands/generate-theme.ts @@ -0,0 +1,18 @@ +import path from 'path'; +import fs from 'fs-extra'; +import {CWD} from '../shared/constant'; + +require('esbuild-register') +const theme = require(path.join(CWD, '/devui/theme/themes/light')) +const themeFilePath = path.join(CWD, '/devui/theme/theme.scss') +const fileStr = Object.entries(theme) + .map(([key, value]) => `$${key}: var(--${key}, ${value})`) + .join(';\n') + +export const generateTheme = async () => { + await fs.outputFile( + themeFilePath, + fileStr, + 'utf-8' + ) +} diff --git a/packages/devui-cli/src/index.ts b/packages/devui-cli/src/index.ts new file mode 100644 index 00000000..4da5cd11 --- /dev/null +++ b/packages/devui-cli/src/index.ts @@ -0,0 +1,14 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import packageJson from '../package.json'; +import {dev} from './commands/dev'; +import {create} from './commands/create'; + +export const cliVersion: string = packageJson.version; + +process.env.REACT_VANT_CLI_VERSION = cliVersion; + +export { + dev, + create +}; diff --git a/packages/devui-vue/devui-cli/inquirers/component.js b/packages/devui-cli/src/inquirers/component.ts similarity index 80% rename from packages/devui-vue/devui-cli/inquirers/component.js rename to packages/devui-cli/src/inquirers/component.ts index 355a2b09..9dd0335f 100644 --- a/packages/devui-vue/devui-cli/inquirers/component.js +++ b/packages/devui-cli/src/inquirers/component.ts @@ -1,6 +1,6 @@ -const { COMPONENT_PARTS_MAP, VITEPRESS_SIDEBAR_CATEGORY } = require('../shared/constant') +import {COMPONENT_PARTS_MAP, VITEPRESS_SIDEBAR_CATEGORY} from '../shared/constant'; -exports.typeName = () => ({ +export const typeName = () => ({ name: 'name', type: 'input', message: '(必填)请输入组件 name ,将用作目录及文件名:', @@ -12,7 +12,7 @@ exports.typeName = () => ({ } }) -exports.typeTitle = () => ({ +export const typeTitle = () => ({ name: 'title', type: 'input', message: '(必填)请输入组件中文名称,将用作文档列表显示:', @@ -24,7 +24,7 @@ exports.typeTitle = () => ({ } }) -exports.selectCategory = () => ({ +export const selectCategory = () => ({ name: 'category', type: 'list', message: '(必填)请选择组件分类,将用作文档列表分类:', @@ -32,13 +32,13 @@ exports.selectCategory = () => ({ default: 0 }) -exports.typeAliasName = () => ({ +export const typeAliasName = () => ({ name: 'alias', type: 'input', message: '(选填)请输入组件 name 别名,将用作组件别名被导出:' }) -exports.selectParts = () => ({ +export const selectParts = () => ({ name: 'parts', type: 'checkbox', message: '(必填)请选择包含部件,将自动生成部件文件:', diff --git a/packages/devui-vue/devui-cli/inquirers/create.js b/packages/devui-cli/src/inquirers/create.ts similarity index 57% rename from packages/devui-vue/devui-cli/inquirers/create.js rename to packages/devui-cli/src/inquirers/create.ts index 5bc07766..55cd752e 100644 --- a/packages/devui-vue/devui-cli/inquirers/create.js +++ b/packages/devui-cli/src/inquirers/create.ts @@ -1,6 +1,7 @@ -const { CREATE_SUPPORT_TYPES } = require('../shared/constant') +import {CREATE_SUPPORT_TYPES} from '../shared/constant'; -exports.selectCreateType = () => ({ + +export const selectCreateType = () => ({ name: 'type', type: 'list', message: '(必填)请选择创建类型:', diff --git a/packages/devui-cli/src/shared/constant.ts b/packages/devui-cli/src/shared/constant.ts new file mode 100644 index 00000000..c3bb2908 --- /dev/null +++ b/packages/devui-cli/src/shared/constant.ts @@ -0,0 +1,46 @@ +const { resolve } = require('path') +const { version } = require('../../package.json') + +export const VERSION = version +export const CWD = process.cwd() +export const DEVUI_DIR = resolve(CWD, 'devui') +export const DEVUI_NAMESPACE = 'd' +export const CSS_CLASS_PREFIX = 'devui' +export const TESTS_DIR_NAME = '__tests__' +export const INDEX_FILE_NAME = 'index.ts' +export const DOCS_FILE_NAME = 'index.md' +export const VUE_DEVUI_IGNORE_DIRS = ['shared', 'style'] +export const VUE_DEVUI_FILE_NAME = 'vue-devui.ts' +export const VUE_DEVUI_FILE = resolve(DEVUI_DIR, VUE_DEVUI_FILE_NAME) +export const SITES_DIR = resolve(CWD, 'docs') +export const SITES_COMPONENTS_DIR_NAME = 'components' +export const SITES_COMPONENTS_DIR = resolve(SITES_DIR, SITES_COMPONENTS_DIR_NAME) +export const VITEPRESS_DIR = resolve(SITES_DIR, '.vitepress') +export const VITEPRESS_SIDEBAR_FILE_NAME = 'sidebar.ts' +export const VITEPRESS_SIDEBAR_FILE = resolve(VITEPRESS_DIR, `config/${VITEPRESS_SIDEBAR_FILE_NAME}`) + +// 这里的分类顺序将会影响最终生成的页面侧边栏顺序 +export const VITEPRESS_SIDEBAR_CATEGORY = ['通用', '导航', '反馈', '数据录入', '数据展示', '布局'] + +export const COMPONENT_PARTS_MAP = [ + { + name: 'component(组件)', + value: 'component' + }, + { + name: 'directive(指令)', + value: 'directive' + }, + { + name: 'service(服务)', + value: 'service' + } +] + +export const CREATE_SUPPORT_TYPE_MAP = Object.freeze({ + component: 'component', + 'vue-devui': 'vue-devui', + 'theme-variable': 'theme-variable', +}) +export const CREATE_SUPPORT_TYPES = Object.keys(CREATE_SUPPORT_TYPE_MAP) +export const CREATE_UNFINISHED_TYPES = [] diff --git a/packages/devui-cli/src/shared/logger.ts b/packages/devui-cli/src/shared/logger.ts new file mode 100644 index 00000000..7c12ee2a --- /dev/null +++ b/packages/devui-cli/src/shared/logger.ts @@ -0,0 +1,16 @@ +import chalk from 'chalk'; + +export const logger = { + info(text) { + console.log(chalk.hex('#00afef')(text)) + }, + success(text) { + console.log(chalk.hex('#00c48f')(text)) + }, + warning(text) { + console.log(chalk.hex('#ff9800')(text)) + }, + error(text) { + console.log(chalk.hex('#f44336')(text)) + } +} diff --git a/packages/devui-cli/src/shared/utils.ts b/packages/devui-cli/src/shared/utils.ts new file mode 100644 index 00000000..4e269278 --- /dev/null +++ b/packages/devui-cli/src/shared/utils.ts @@ -0,0 +1,133 @@ +const {camelCase, upperFirst} = require('lodash') +const {INDEX_FILE_NAME, DEVUI_DIR} = require('./constant') +import {resolve} from 'path' + +import fs from 'fs-extra'; + +import {logger} from './logger'; + +const traverse = require('@babel/traverse').default +const babelParser = require('@babel/parser') + +export const bigCamelCase = (str) => { + return upperFirst(camelCase(str)) +} + +export const resolveDirFilesInfo = (targetDir, ignoreDirs = []) => { + return fs + .readdirSync(targetDir) + .filter( + (dir) => + // 过滤:必须是目录,且不存在与忽略目录内,拥有 INDEX_FILE_NAME + fs.statSync(resolve(targetDir, dir)).isDirectory() && + !ignoreDirs.includes(dir) && + fs.existsSync(resolve(targetDir, dir, INDEX_FILE_NAME)) + ) + .map((dir) => ({ + name: bigCamelCase(dir), + dirname: dir, + path: resolve(targetDir, dir, INDEX_FILE_NAME) + })) +} + +interface exportModuleI { + default: string; + parts: Array; + fileInfo: string +} + +export const parseExportByFileInfo = (fileInfo, ignoreParseError) => { + const exportModule: exportModuleI = {default: '', fileInfo: '', parts: []} + const indexContent = fs.readFileSync(fileInfo.path, {encoding: 'utf-8'}) + + const ast = babelParser.parse(indexContent, { + sourceType: 'module', + plugins: [ + 'typescript' + ] + }) + + const exportName = [] + let exportDefault = null + + traverse(ast, { + ExportNamedDeclaration({node}) { + if (node.specifiers.length) { + node.specifiers.forEach(specifier => { + exportName.push(specifier.local.name) + }) + } else if (node.declaration) { + if (node.declaration.declarations) { + node.declaration.declarations.forEach(dec => { + exportName.push(dec.id.name) + }) + } else if (node.declaration.id) { + exportName.push(node.declaration.id.name) + } + } + }, + ExportDefaultDeclaration() { + exportDefault = fileInfo.name + 'Install' + } + }) + + if (!exportDefault) { + logger.error(`${fileInfo.path} must have "export default".`) + + if (ignoreParseError) { + return exportModule + } else { + process.exit(1) + } + } + + if (!exportName.length) { + logger.error(`${fileInfo.path} must have "export xxx".`) + + if (ignoreParseError) { + return exportModule + } else { + process.exit(1) + } + } + + exportModule.default = exportDefault + exportModule.parts = exportName + exportModule.fileInfo = fileInfo + + return exportModule +} + +export const parseComponentInfo = (name) => { + const componentInfo = { + name: bigCamelCase(name) + } + let hasExportDefault = false + const indexContent = fs.readFileSync(resolve(DEVUI_DIR, name, INDEX_FILE_NAME), {encoding: 'utf-8'}) + + const ast = babelParser.parse(indexContent, { + sourceType: 'module', + plugins: [ + 'typescript' + ] + }) + traverse(ast, { + ExportDefaultDeclaration({node}) { + hasExportDefault = true + if (node.declaration && node.declaration.properties) { + const properties = node.declaration.properties + properties.forEach(pro => { + if (pro.type === 'ObjectProperty') { + componentInfo[pro.key.name] = pro.value.value + } + }) + } + } + }) + + if (!hasExportDefault) { + logger.warning(`${componentInfo.name} must have "export default" and component info.`) + } + + return componentInfo +} diff --git a/packages/devui-vue/devui-cli/templates/component.js b/packages/devui-cli/src/templates/component.ts similarity index 88% rename from packages/devui-vue/devui-cli/templates/component.js rename to packages/devui-cli/src/templates/component.ts index 8e0909c4..52edb6ba 100644 --- a/packages/devui-vue/devui-cli/templates/component.js +++ b/packages/devui-cli/src/templates/component.ts @@ -1,9 +1,13 @@ -const { DEVUI_NAMESPACE, CSS_CLASS_PREFIX } = require('../shared/constant') -const { camelCase } = require('lodash') -const { bigCamelCase } = require('../shared/utils') +import {CSS_CLASS_PREFIX, DEVUI_NAMESPACE} from '../shared/constant'; + + +import {camelCase} from 'lodash'; + +import {bigCamelCase} from '../shared/utils'; + // 创建组件模板 -exports.createComponentTemplate = ({ styleName, componentName, typesName }) => `\ +export const createComponentTemplate = ({ styleName, componentName, typesName }) => `\ import { defineComponent } from 'vue' import { ${camelCase(componentName)}Props, ${bigCamelCase(componentName)}Props } from './${typesName}' import './${styleName}.scss' @@ -21,7 +25,7 @@ export default defineComponent({ ` // 创建类型声明模板 -exports.createTypesTemplate = ({ componentName }) => `\ +export const createTypesTemplate = ({ componentName }) => `\ import type { PropType, ExtractPropTypes } from 'vue' export const ${camelCase(componentName)}Props = { @@ -34,7 +38,7 @@ export type ${bigCamelCase(componentName)}Props = ExtractPropTypes `\ +export const createDirectiveTemplate = () => `\ // can export function. export default { created() { }, @@ -47,7 +51,7 @@ export default { } ` // 创建server模板 -exports.createServiceTemplate = ({ componentName, typesName, serviceName }) => `\ +export const createServiceTemplate = ({ componentName, typesName, serviceName }) => `\ import { ${bigCamelCase(componentName)}Props } from './${typesName}' const ${bigCamelCase(serviceName)} = { @@ -58,14 +62,14 @@ export default ${bigCamelCase(serviceName)} ` // 创建scss模板 -exports.createStyleTemplate = ({ componentName }) => `\ +export const createStyleTemplate = ({ componentName }) => `\ .${CSS_CLASS_PREFIX}-${componentName} { // } ` // 创建index模板 -exports.createIndexTemplate = ({ +export const createIndexTemplate = ({ title, category, hasComponent, @@ -125,7 +129,7 @@ export default { } // 创建测试模板 -exports.createTestsTemplate = ({ +export const createTestsTemplate = ({ componentName, directiveName, serviceName, @@ -150,7 +154,7 @@ describe('${componentName} test', () => { ` // 创建文档模板 -exports.createDocumentTemplate = ({ +export const createDocumentTemplate = ({ componentName, title }) => `\ diff --git a/packages/devui-cli/src/templates/vitepress-sidebar.ts b/packages/devui-cli/src/templates/vitepress-sidebar.ts new file mode 100644 index 00000000..87df74b3 --- /dev/null +++ b/packages/devui-cli/src/templates/vitepress-sidebar.ts @@ -0,0 +1,35 @@ +import {kebabCase} from 'lodash'; +import {SITES_COMPONENTS_DIR_NAME, VITEPRESS_SIDEBAR_CATEGORY} from '../shared/constant'; +import {logger} from '../shared/logger'; + +function buildComponentOptions(text, name, status) { + return {text, link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/`, status} +} + +function buildCategoryOptions(text, children = []) { + return {text, children} +} + +export const createVitepressSidebarTemplate = (componentsInfo = []) => { + const rootNav = {text: '快速开始', link: '/'} + const categoryMap = VITEPRESS_SIDEBAR_CATEGORY.reduce((map, cate) => map.set(cate, []), new Map()) + + componentsInfo.forEach((info) => { + if (categoryMap.has(info.category)) { + categoryMap.get(info.category).push(buildComponentOptions(info.title, info.name, info.status)) + } else { + logger.warning(`组件 ${info.name} 的分类 ${info.category} 不存在!`) + } + }) + + const sidebar = [].concat( + rootNav, + Array.from(categoryMap).map(([k, v]) => buildCategoryOptions(k, v)) + ) + + return `\ +export default { + '/': ${JSON.stringify(sidebar, null, 2).replace(/\n/g, '\n\t')} +} +` +} diff --git a/packages/devui-cli/src/templates/vue-devui.ts b/packages/devui-cli/src/templates/vue-devui.ts new file mode 100644 index 00000000..12a629c1 --- /dev/null +++ b/packages/devui-cli/src/templates/vue-devui.ts @@ -0,0 +1,45 @@ +import {relative} from 'path'; +import {INDEX_FILE_NAME, VERSION, VUE_DEVUI_FILE} from '../shared/constant'; + +export const createVueDevuiTemplate = (exportModules = []) => { + const packages = [] + const imports = [] + const installs = [] + + exportModules.forEach((m) => { + const {fileInfo} = m + const relativePath = relative(VUE_DEVUI_FILE, fileInfo.path) + .replace(/\\/g, '/') + .replace('..', '.') + .replace('/' + INDEX_FILE_NAME, '') + + const importStr = `import ${m.default}, { ${m.parts.join(', ')} } from '${relativePath}'` + + packages.push(...m.parts) + imports.push(importStr) + installs.push(m.default) + }) + + const template = `\ +import type { App } from 'vue' + +${imports.join('\n')} + +const installs = [ + ${installs.join(',\n\t')} +] + +export { + ${packages.join(',\n\t')} +} + +export default { + version: '${VERSION}', + install(app: App): void { + installs.forEach((p) => app.use(p as any)) + } +} +` + + return template +} diff --git a/packages/devui-cli/tsconfig.json b/packages/devui-cli/tsconfig.json new file mode 100644 index 00000000..2d6eda1d --- /dev/null +++ b/packages/devui-cli/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2017", + "outDir": "./lib", + "module": "commonjs", + "declaration": true, + "skipLibCheck": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"] + }, + "include": ["src/**/*", "site"], + "exclude": ["node_modules"] +} diff --git a/packages/devui-vue/devui-cli/commands/build.js b/packages/devui-vue/devui-cli/commands/build.js deleted file mode 100644 index 48c472f4..00000000 --- a/packages/devui-vue/devui-cli/commands/build.js +++ /dev/null @@ -1,87 +0,0 @@ -const path = require('path') -const fs = require('fs') -const fsExtra = require('fs-extra') -const { defineConfig, build } = require('vite') -const vue = require('@vitejs/plugin-vue') -const vueJsx = require('@vitejs/plugin-vue-jsx') - -const entryDir = path.resolve(__dirname, '../../devui') -const outputDir = path.resolve(__dirname, '../../build') - -const baseConfig = defineConfig({ - configFile: false, - publicDir: false, - plugins: [ vue(), vueJsx() ] -}) - -const rollupOptions = { - external: ['vue', 'vue-router'], - output: { - globals: { - vue: 'Vue' - } - } -} - -const buildSingle = async (name) => { - await build(defineConfig({ - ...baseConfig, - build: { - rollupOptions, - lib: { - entry: path.resolve(entryDir, name), - name: 'index', - fileName: 'index', - formats: ['es', 'umd'] - }, - outDir: path.resolve(outputDir, name) - } - })) -} - -const buildAll = async () => { - await build(defineConfig({ - ...baseConfig, - build: { - rollupOptions, - lib: { - entry: path.resolve(entryDir, 'vue-devui.ts'), - name: 'vue-devui', - fileName: 'vue-devui', - formats: ['es', 'umd'] - }, - outDir: outputDir - } - })) -} - -const createPackageJson = (name) => { - const fileStr = `{ - "name": "${name}", - "version": "0.0.0", - "main": "index.umd.js", - "module": "index.es.js", - "style": "style.css" -}` - - fsExtra.outputFile( - path.resolve(outputDir, `${name}/package.json`), - fileStr, - 'utf-8' - ) -} - -exports.build = async () => { - await buildAll() - - const components = fs.readdirSync(entryDir).filter(name => { - const componentDir = path.resolve(entryDir, name) - const isDir = fs.lstatSync(componentDir).isDirectory() - return isDir && fs.readdirSync(componentDir).includes('index.ts') - }) - - for(const name of components) { - await buildSingle(name) - createPackageJson(name) - } -} diff --git a/packages/devui-vue/devui-cli/commands/create.js b/packages/devui-vue/devui-cli/commands/create.js deleted file mode 100644 index 91433ada..00000000 --- a/packages/devui-vue/devui-cli/commands/create.js +++ /dev/null @@ -1,234 +0,0 @@ -const logger = require('../shared/logger') -const { - bigCamelCase, - resolveDirFilesInfo, - parseExportByFileInfo, - parseComponentInfo -} = require('../shared/utils') -const fs = require('fs-extra') -const { resolve } = require('path') -const { - DEVUI_NAMESPACE, - DEVUI_DIR, - TESTS_DIR_NAME, - COMPONENT_PARTS_MAP, - INDEX_FILE_NAME, - DOCS_FILE_NAME, - VUE_DEVUI_FILE, - VUE_DEVUI_IGNORE_DIRS, - VUE_DEVUI_FILE_NAME, - CREATE_SUPPORT_TYPES, - CREATE_UNFINISHED_TYPES, - CREATE_SUPPORT_TYPE_MAP, - SITES_COMPONENTS_DIR, - VITEPRESS_SIDEBAR_FILE, - VITEPRESS_SIDEBAR_FILE_NAME -} = require('../shared/constant') -const { isEmpty, kebabCase } = require('lodash') -const inquirer = require('inquirer') -const { selectCreateType } = require('../inquirers/create') -const { selectCategory, selectParts, typeName, typeTitle } = require('../inquirers/component') -const { - createComponentTemplate, - createStyleTemplate, - createTypesTemplate, - createDirectiveTemplate, - createServiceTemplate, - createIndexTemplate, - createTestsTemplate, - createDocumentTemplate -} = require('../templates/component') -const { createVueDevuiTemplate } = require('../templates/vue-devui') -const ora = require('ora') -const { createVitepressSidebarTemplate } = require('../templates/vitepress-sidebar') - -exports.validateCreateType = (type) => { - const re = new RegExp('^(' + CREATE_SUPPORT_TYPES.map((t) => `(${t})`).join('|') + ')$') - const flag = re.test(type) - - !flag && logger.error(`类型错误,可选类型为:${CREATE_SUPPORT_TYPES.join(', ')}`) - - return flag ? type : null -} - -// TODO: 待优化代码结构 -exports.create = async (cwd) => { - let { type } = cwd - - if (isEmpty(type)) { - const result = await inquirer.prompt([selectCreateType()]) - type = result.type - } - - if (CREATE_UNFINISHED_TYPES.includes(type)) { - logger.info('抱歉,该功能暂未完成!') - process.exit(0) - } - - let params = {} - - try { - switch (type) { - case CREATE_SUPPORT_TYPE_MAP.component: - params = await inquirer.prompt([typeName(), typeTitle(), selectCategory(), selectParts()]) - params.hasComponent = params.parts.includes('component') - params.hasDirective = params.parts.includes('directive') - params.hasService = params.parts.includes('service') - - await createComponent(params, cwd) - break - case CREATE_SUPPORT_TYPE_MAP['vue-devui']: - // 创建 devui/vue-devui.ts - await createVueDevui(params, cwd) - // 创建 docs/.vitepress/config/sidebar.ts - await createVitepressSidebar() - break - default: - break - } - } catch (e) { - logger.error(e.toString()) - process.exit(1) - } -} - -async function createComponent(params = {}) { - let { name, hasComponent, hasDirective, hasService } = params - - const componentName = kebabCase(name) - const styleName = kebabCase(name) - const typesName = kebabCase(name) + '-types' - const directiveName = kebabCase(name) + '-directive' - const serviceName = kebabCase(name) + '-service' - const testName = kebabCase(name) + '.spec' - - const _params = { - ...params, - componentName, - typesName, - directiveName, - serviceName, - styleName, - testName - } - - const componentTemplate = createComponentTemplate(_params) - const styleTemplate = createStyleTemplate(_params) - const typesTemplate = createTypesTemplate(_params) - const directiveTemplate = createDirectiveTemplate(_params) - const serviceTemplate = createServiceTemplate(_params) - const indexTemplate = createIndexTemplate(_params) - // 增加测试模板 - const testsTemplate = createTestsTemplate(_params) - // 增加文档模板 - const docTemplate = createDocumentTemplate(_params) - - const componentDir = resolve(DEVUI_DIR, componentName) - const srcDir = resolve(componentDir, 'src') - const testsDir = resolve(DEVUI_DIR, componentName, TESTS_DIR_NAME) - const docsDir = resolve(SITES_COMPONENTS_DIR, componentName) - - if (fs.pathExistsSync(componentDir)) { - logger.error(`${bigCamelCase(componentName)} 组件目录已存在!`) - process.exit(1) - } - - let spinner = ora(`创建组件 ${bigCamelCase(componentName)} 开始...`).start() - - try { - await Promise.all([fs.mkdirs(componentDir), fs.mkdirs(srcDir), fs.mkdirs(testsDir)]) - - const writeFiles = [ - fs.writeFile(resolve(componentDir, INDEX_FILE_NAME), indexTemplate), - fs.writeFile(resolve(testsDir, `${testName}.ts`), testsTemplate), - ] - - if (!fs.existsSync(docsDir)) { - fs.mkdirSync(docsDir) - writeFiles.push(fs.writeFile(resolve(docsDir, DOCS_FILE_NAME), docTemplate)) - } else { - logger.warning(`\n${bigCamelCase(componentName)} 组件文档已存在:${resolve(docsDir, DOCS_FILE_NAME)}`) - } - - if (hasComponent || hasService) { - writeFiles.push(fs.writeFile(resolve(srcDir, `${typesName}.ts`), typesTemplate)) - } - - if (hasComponent) { - writeFiles.push( - fs.writeFile(resolve(srcDir, `${componentName}.tsx`), componentTemplate), - fs.writeFile(resolve(srcDir, `${styleName}.scss`), styleTemplate) - ) - } - - if (hasDirective) { - writeFiles.push(fs.writeFile(resolve(srcDir, `${directiveName}.ts`), directiveTemplate)) - } - - if (hasService) { - writeFiles.push(fs.writeFile(resolve(srcDir, `${serviceName}.ts`), serviceTemplate)) - } - - await Promise.all(writeFiles) - - spinner.succeed(`创建组件 ${bigCamelCase(componentName)} 成功!`) - logger.info(`组件目录:${componentDir}`) - } catch (e) { - spinner.fail(e.toString()) - process.exit(1) - } -} - -async function createVueDevui(params, { ignoreParseError }) { - const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) - const exportModules = [] - - fileInfo.forEach((f) => { - const em = parseExportByFileInfo(f, ignoreParseError) - - if (isEmpty(em)) return - - exportModules.push(em) - }) - - const template = createVueDevuiTemplate(exportModules) - - let spinner = ora(`创建 ${VUE_DEVUI_FILE_NAME} 文件开始...`).start() - - try { - await fs.writeFile(VUE_DEVUI_FILE, template, { encoding: 'utf-8' }) - - spinner.succeed(`创建 ${VUE_DEVUI_FILE_NAME} 文件成功!`) - logger.info(`文件地址:${VUE_DEVUI_FILE}`) - } catch (e) { - spinner.fail(e.toString()) - process.exit(1) - } -} - -async function createVitepressSidebar() { - const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) - const componentsInfo = [] - - fileInfo.forEach((f) => { - const info = parseComponentInfo(f.dirname) - - if (isEmpty(info)) return - - componentsInfo.push(info) - }) - - const template = createVitepressSidebarTemplate(componentsInfo) - - let spinner = ora(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件开始...`).start() - - try { - await fs.writeFile(VITEPRESS_SIDEBAR_FILE, template, { encoding: 'utf-8' }) - - spinner.succeed(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件成功!`) - logger.info(`文件地址:${VITEPRESS_SIDEBAR_FILE}`) - } catch (e) { - spinner.fail(e.toString()) - process.exit(1) - } -} diff --git a/packages/devui-vue/devui-cli/commands/generate-theme.js b/packages/devui-vue/devui-cli/commands/generate-theme.js deleted file mode 100644 index 3acfa026..00000000 --- a/packages/devui-vue/devui-cli/commands/generate-theme.js +++ /dev/null @@ -1,16 +0,0 @@ -require('esbuild-register') -const path = require('path') -const fs = require('fs-extra') -const theme = require('../../devui/theme/themes/light.ts').default - -const fileStr = Object.entries(theme) -.map(([key, value]) => `$${key}: var(--${key}, ${value})`) -.join(';\n') - -exports.generateTheme = async () => { - await fs.outputFile( - path.resolve(__dirname, '../../devui/theme/theme.scss'), - fileStr, - 'utf-8' - ) -} \ No newline at end of file diff --git a/packages/devui-vue/devui-cli/index.js b/packages/devui-vue/devui-cli/index.js deleted file mode 100755 index d4ed9906..00000000 --- a/packages/devui-vue/devui-cli/index.js +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node -const { Command } = require('commander') -const { create, validateCreateType } = require('./commands/create') -const { build } = require('./commands/build') -const { generateTheme } = require('./commands/generate-theme') -const { VERSION, CREATE_SUPPORT_TYPES } = require('./shared/constant') - -const program = new Command() - -program - .command('create') - .description('创建一个组件模板或配置文件') - .option('-t --type ', `创建类型,可选值:${CREATE_SUPPORT_TYPES.join(', ')}`, validateCreateType) - .option('--ignore-parse-error', '忽略解析错误', false) - .option('--cover', '覆盖原文件', false) - .action(create) - -program - .command('build') - .description('打包组件库') - .action(build) - -program - .command('generate:theme') - .description('生成主题变量文件') - .action(generateTheme) - -program.parse().version(VERSION) diff --git a/packages/devui-vue/devui-cli/shared/constant.js b/packages/devui-vue/devui-cli/shared/constant.js deleted file mode 100644 index 62884097..00000000 --- a/packages/devui-vue/devui-cli/shared/constant.js +++ /dev/null @@ -1,46 +0,0 @@ -const { resolve } = require('path') -const { version } = require('../../package.json') - -exports.VERSION = version -exports.CWD = process.cwd() -exports.DEVUI_DIR = resolve(this.CWD, 'devui') -exports.DEVUI_NAMESPACE = 'd' -exports.CSS_CLASS_PREFIX = 'devui' -exports.TESTS_DIR_NAME = '__tests__' -exports.INDEX_FILE_NAME = 'index.ts' -exports.DOCS_FILE_NAME = 'index.md' -exports.VUE_DEVUI_IGNORE_DIRS = ['shared', 'style'] -exports.VUE_DEVUI_FILE_NAME = 'vue-devui.ts' -exports.VUE_DEVUI_FILE = resolve(this.DEVUI_DIR, this.VUE_DEVUI_FILE_NAME) -exports.SITES_DIR = resolve(this.CWD, 'docs') -exports.SITES_COMPONENTS_DIR_NAME = 'components' -exports.SITES_COMPONENTS_DIR = resolve(this.SITES_DIR, this.SITES_COMPONENTS_DIR_NAME) -exports.VITEPRESS_DIR = resolve(this.SITES_DIR, '.vitepress') -exports.VITEPRESS_SIDEBAR_FILE_NAME = 'sidebar.ts' -exports.VITEPRESS_SIDEBAR_FILE = resolve(this.VITEPRESS_DIR, `config/${this.VITEPRESS_SIDEBAR_FILE_NAME}`) - -// 这里的分类顺序将会影响最终生成的页面侧边栏顺序 -exports.VITEPRESS_SIDEBAR_CATEGORY = ['通用', '导航', '反馈', '数据录入', '数据展示', '布局'] - -exports.COMPONENT_PARTS_MAP = [ - { - name: 'component(组件)', - value: 'component' - }, - { - name: 'directive(指令)', - value: 'directive' - }, - { - name: 'service(服务)', - value: 'service' - } -] - -exports.CREATE_SUPPORT_TYPE_MAP = Object.freeze({ - component: 'component', - 'vue-devui': 'vue-devui', - 'theme-variable': 'theme-variable', -}) -exports.CREATE_SUPPORT_TYPES = Object.keys(this.CREATE_SUPPORT_TYPE_MAP) -exports.CREATE_UNFINISHED_TYPES = [] diff --git a/packages/devui-vue/devui-cli/shared/logger.js b/packages/devui-vue/devui-cli/shared/logger.js deleted file mode 100644 index 4b97da0e..00000000 --- a/packages/devui-vue/devui-cli/shared/logger.js +++ /dev/null @@ -1,16 +0,0 @@ -const chalk = require('chalk') - -module.exports = { - info(text) { - console.log(chalk.hex('#00afef')(text)) - }, - success(text) { - console.log(chalk.hex('#00c48f')(text)) - }, - warning(text) { - console.log(chalk.hex('#ff9800')(text)) - }, - error(text) { - console.log(chalk.hex('#f44336')(text)) - } -} diff --git a/packages/devui-vue/devui-cli/shared/utils.js b/packages/devui-vue/devui-cli/shared/utils.js deleted file mode 100644 index 11fb60c7..00000000 --- a/packages/devui-vue/devui-cli/shared/utils.js +++ /dev/null @@ -1,124 +0,0 @@ -const { camelCase, upperFirst } = require('lodash') -const { INDEX_FILE_NAME, DEVUI_DIR } = require('./constant') -const { resolve } = require('path') -const logger = require('./logger') -const fs = require('fs-extra') -const traverse = require("@babel/traverse").default -const babelParser = require("@babel/parser") - -exports.bigCamelCase = (str) => { - return upperFirst(camelCase(str)) -} - -exports.resolveDirFilesInfo = (targetDir, ignoreDirs = []) => { - return fs - .readdirSync(targetDir) - .filter( - (dir) => - // 过滤:必须是目录,且不存在与忽略目录内,拥有 INDEX_FILE_NAME - fs.statSync(resolve(targetDir, dir)).isDirectory() && - !ignoreDirs.includes(dir) && - fs.existsSync(resolve(targetDir, dir, INDEX_FILE_NAME)) - ) - .map((dir) => ({ - name: this.bigCamelCase(dir), - dirname: dir, - path: resolve(targetDir, dir, INDEX_FILE_NAME) - })) -} - -exports.parseExportByFileInfo = (fileInfo, ignoreParseError) => { - const exportModule = {} - const indexContent = fs.readFileSync(fileInfo.path, { encoding: 'utf-8' }) - - const ast = babelParser.parse(indexContent, { - sourceType: 'module', - plugins: [ - 'typescript' - ] - }) - - const exportName = [] - let exportDefault = null - - traverse(ast, { - ExportNamedDeclaration({node}) { - if (node.specifiers.length) { - node.specifiers.forEach(specifier => { - exportName.push(specifier.local.name) - }) - } else if (node.declaration) { - if (node.declaration.declarations) { - node.declaration.declarations.forEach(dec => { - exportName.push(dec.id.name) - }) - } else if (node.declaration.id) { - exportName.push(node.declaration.id.name) - } - } - }, - ExportDefaultDeclaration() { - exportDefault = fileInfo.name + 'Install' - } - }) - - if (!exportDefault) { - logger.error(`${fileInfo.path} must have "export default".`) - - if (ignoreParseError) { - return exportModule - } else { - process.exit(1) - } - } - - if (!exportName.length) { - logger.error(`${fileInfo.path} must have "export xxx".`) - - if (ignoreParseError) { - return exportModule - } else { - process.exit(1) - } - } - - exportModule.default = exportDefault - exportModule.parts = exportName - exportModule.fileInfo = fileInfo - - return exportModule -} - -exports.parseComponentInfo = (name) => { - const componentInfo = { - name: this.bigCamelCase(name) - } - let hasExportDefault = false - const indexContent = fs.readFileSync(resolve(DEVUI_DIR, name, INDEX_FILE_NAME), { encoding: 'utf-8' }) - - const ast = babelParser.parse(indexContent, { - sourceType: 'module', - plugins: [ - 'typescript' - ] - }) - traverse(ast, { - ExportDefaultDeclaration({node}) { - hasExportDefault = true - if (node.declaration && node.declaration.properties) { - const properties = node.declaration.properties - properties.forEach(pro => { - if (pro.type === 'ObjectProperty') { - componentInfo[pro.key.name] = pro.value.value - } - }) - } - } - }) - - if (!hasExportDefault) { - logger.warning(`${componentInfo.name} must have "export default" and component info.`) - } - - return componentInfo -} diff --git a/packages/devui-vue/devui-cli/templates/vitepress-sidebar.js b/packages/devui-vue/devui-cli/templates/vitepress-sidebar.js deleted file mode 100644 index ee079340..00000000 --- a/packages/devui-vue/devui-cli/templates/vitepress-sidebar.js +++ /dev/null @@ -1,35 +0,0 @@ -const { kebabCase } = require('lodash') -const { SITES_COMPONENTS_DIR_NAME, VITEPRESS_SIDEBAR_CATEGORY } = require('../shared/constant') -const logger = require('../shared/logger') - -function buildComponentOptions(text, name, status) { - return { text, link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/`, status } -} - -function buildCategoryOptions(text, children = []) { - return { text, children } -} - -exports.createVitepressSidebarTemplate = (componentsInfo = []) => { - const rootNav = { text: '快速开始', link: '/' } - const categoryMap = VITEPRESS_SIDEBAR_CATEGORY.reduce((map, cate) => map.set(cate, []), new Map()) - - componentsInfo.forEach((info) => { - if (categoryMap.has(info.category)) { - categoryMap.get(info.category).push(buildComponentOptions(info.title, info.name, info.status)) - } else { - logger.warning(`组件 ${info.name} 的分类 ${info.category} 不存在!`) - } - }) - - const sidebar = [].concat( - rootNav, - Array.from(categoryMap).map(([k, v]) => buildCategoryOptions(k, v)) - ) - - return `\ -export default { - '/': ${JSON.stringify(sidebar, null, 2).replace(/\n/g, '\n\t')} -} -` -} diff --git a/packages/devui-vue/devui-cli/templates/vue-devui.js b/packages/devui-vue/devui-cli/templates/vue-devui.js deleted file mode 100644 index c6def01b..00000000 --- a/packages/devui-vue/devui-cli/templates/vue-devui.js +++ /dev/null @@ -1,45 +0,0 @@ -const { relative } = require('path') -const { INDEX_FILE_NAME, VERSION, VUE_DEVUI_FILE } = require('../shared/constant') - -exports.createVueDevuiTemplate = (exportModules = []) => { - const packages = [] - const imports = [] - const installs = [] - - exportModules.forEach((m) => { - const { fileInfo } = m - const relativePath = relative(VUE_DEVUI_FILE, fileInfo.path) - .replace(/\\/g, '/') - .replace('..', '.') - .replace('/' + INDEX_FILE_NAME, '') - - const importStr = `import ${m.default}, { ${m.parts.join(', ')} } from '${relativePath}'` - - packages.push(...m.parts) - imports.push(importStr) - installs.push(m.default) - }) - - const template = `\ -import type { App } from 'vue' - -${imports.join('\n')} - -const installs = [ - ${installs.join(',\n\t')} -] - -export { - ${packages.join(',\n\t')} -} - -export default { - version: '${VERSION}', - install(app: App): void { - installs.forEach((p) => app.use(p as any)) - } -} -` - - return template -} diff --git a/packages/devui-vue/package.json b/packages/devui-vue/package.json index 991512f6..00356e61 100644 --- a/packages/devui-vue/package.json +++ b/packages/devui-vue/package.json @@ -22,7 +22,8 @@ "devui-cli": "./devui-cli/index.js" }, "scripts": { - "dev": "yarn generate:theme && vitepress dev docs", + "predev": "devui-cli create -t vue-devui --ignore-parse-error && yarn generate:theme", + "dev": "vitepress dev docs", "build": "yarn generate:theme && vitepress build docs && cp public/* docs/.vitepress/dist/assets", "serve": "vitepress serve docs", "app:dev": "vite", @@ -33,15 +34,15 @@ "lint": "eslint \"{src,devui}/**/*.{vue,js,ts,jsx,tsx}\"", "lint:fix": "eslint --fix \"{src,devui}/**/*.{vue,js,ts,jsx,tsx}\"", "stylelint": "stylelint --fix \"{devui,src}/**/*.{scss,css}\"", - "build:components": "node ./devui-cli/index.js build", - "generate:theme": "node ./devui-cli/index.js generate:theme", + "build:components": "devui-cli build", + "generate:theme": "devui-cli generate:theme", "copy": "cp package.json build && cp README.md build && cp devui/theme/theme.scss build/theme", "clean:cli": "npm uninstall -g devui-cli & npm uninstall -g vue-devui", - "cli:create": "node ./devui-cli/index.js create -t component", - "predev": "node ./devui-cli/index.js create -t vue-devui --ignore-parse-error", - "prebuild": "node ./devui-cli/index.js create -t vue-devui --ignore-parse-error" + "cli:create": "devui-cli create -t component", + "prebuild": "devui-cli create -t vue-devui --ignore-parse-error" }, "dependencies": { + "@devui/cli": "^1.0.0", "@devui-design/icons": "^1.3.0", "@types/lodash-es": "^4.17.4", "async-validator": "^4.0.2", -- Gitee From 13505f5735e12361d6dee77c52f37dc117c5d8f9 Mon Sep 17 00:00:00 2001 From: TinsFox <1414849373@qq.com> Date: Thu, 28 Oct 2021 15:20:53 +0800 Subject: [PATCH 3/7] docs: update README --- packages/devui-cli/README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/devui-cli/README.md b/packages/devui-cli/README.md index 3b668baf..396c1563 100644 --- a/packages/devui-cli/README.md +++ b/packages/devui-cli/README.md @@ -1,7 +1,3 @@ -# react-vant-cli +# devui-cli -react vant cli 是一个 React 组件库构建工具,通过 react vant cli 可以快速搭建一套 React 组件库 - -## 特性 - -- 提供丰富的命令,涵盖从开发测试到构建发布的完整流程 +devui-cli -- Gitee From 0d564fdd7166b0f05f7fb49aa80aea7f0f41edc1 Mon Sep 17 00:00:00 2001 From: TinsFox <1414849373@qq.com> Date: Thu, 28 Oct 2021 15:27:34 +0800 Subject: [PATCH 4/7] refactor: Modified variable name --- packages/devui-cli/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devui-cli/src/index.ts b/packages/devui-cli/src/index.ts index 4da5cd11..4e81f2ca 100644 --- a/packages/devui-cli/src/index.ts +++ b/packages/devui-cli/src/index.ts @@ -6,7 +6,7 @@ import {create} from './commands/create'; export const cliVersion: string = packageJson.version; -process.env.REACT_VANT_CLI_VERSION = cliVersion; +process.env.DEVUI_CLI_VERSION = cliVersion; export { dev, -- Gitee From dfffffc84dc57c08d4a7466e709f4eeb7020bc15 Mon Sep 17 00:00:00 2001 From: TinsFox <1414849373@qq.com> Date: Thu, 28 Oct 2021 15:29:08 +0800 Subject: [PATCH 5/7] chore: remove dependencies --- packages/devui-cli/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/devui-cli/package.json b/packages/devui-cli/package.json index 607d1c3c..1ccdc7bc 100644 --- a/packages/devui-cli/package.json +++ b/packages/devui-cli/package.json @@ -18,9 +18,7 @@ "@types/node": "^14.11.8", "@babel/traverse": "^7.15.4", "@babel/parser": "^7.15.5", - "vite": "^2.4.4" - }, - "dependencies": { + "vite": "^2.4.4", "chalk": "^4.1.2", "commander": "^8.1.0", "inquirer": "^8.1.2", -- Gitee From 5f42263134e91bfb089bc33f73148b5f9c29b681 Mon Sep 17 00:00:00 2001 From: TinsFox <1414849373@qq.com> Date: Thu, 28 Oct 2021 16:44:12 +0800 Subject: [PATCH 6/7] refactor: remove unless command --- packages/devui-cli/bin.js | 2 +- packages/devui-cli/package.json | 2 + packages/devui-cli/src/bin.ts | 53 +-- packages/devui-cli/src/commands/build.ts | 139 +++--- packages/devui-cli/src/commands/create.ts | 418 ++++++++++-------- packages/devui-cli/src/commands/dev.ts | 90 ---- .../devui-cli/src/commands/generate-theme.ts | 24 +- packages/devui-cli/src/index.ts | 16 +- packages/devui-cli/src/inquirers/component.ts | 69 +-- packages/devui-cli/src/inquirers/create.ts | 13 +- packages/devui-cli/src/shared/constant.ts | 80 ++-- packages/devui-cli/src/shared/logger.ts | 28 +- packages/devui-cli/src/shared/utils.ts | 245 +++++----- packages/devui-cli/src/templates/component.ts | 121 ++--- .../src/templates/vitepress-sidebar.ts | 56 ++- packages/devui-cli/src/templates/vue-devui.ts | 56 +-- packages/devui-cli/tsconfig.json | 1 + 17 files changed, 691 insertions(+), 722 deletions(-) delete mode 100644 packages/devui-cli/src/commands/dev.ts diff --git a/packages/devui-cli/bin.js b/packages/devui-cli/bin.js index 4c86924e..3bd8a423 100755 --- a/packages/devui-cli/bin.js +++ b/packages/devui-cli/bin.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -require('./lib/bin'); +require("./lib/bin"); diff --git a/packages/devui-cli/package.json b/packages/devui-cli/package.json index 1ccdc7bc..187c7a92 100644 --- a/packages/devui-cli/package.json +++ b/packages/devui-cli/package.json @@ -16,8 +16,10 @@ "@types/commander": "^2.12.2", "@types/ora": "^3.2.0", "@types/node": "^14.11.8", + "@types/fs-extra": "^9.0.2", "@babel/traverse": "^7.15.4", "@babel/parser": "^7.15.5", + "@types/babel-traverse": "^6.25.7", "vite": "^2.4.4", "chalk": "^4.1.2", "commander": "^8.1.0", diff --git a/packages/devui-cli/src/bin.ts b/packages/devui-cli/src/bin.ts index bdb9c74e..79eaabc8 100644 --- a/packages/devui-cli/src/bin.ts +++ b/packages/devui-cli/src/bin.ts @@ -1,40 +1,33 @@ #!/usr/bin/env node -import {Command} from 'commander'; +import { Command } from "commander"; +import { create, validateCreateType } from "./commands/create"; +import { generateTheme } from "./commands/generate-theme"; +import { CREATE_SUPPORT_TYPES, VERSION } from "./shared/constant"; -import {create, validateCreateType} from './commands/create'; +import {build} from '.'; -const {build} = require('./commands/build') -import {dev} from '.' +const program = new Command(); -import {generateTheme} from './commands/generate-theme'; - -import {CREATE_SUPPORT_TYPES, VERSION} from './shared/constant'; - - -const program = new Command() - -program - .command('create') - .description('创建一个组件模板或配置文件') - .option('-t --type ', `创建类型,可选值:${CREATE_SUPPORT_TYPES.join(', ')}`, validateCreateType) - .option('--ignore-parse-error', '忽略解析错误', false) - .option('--cover', '覆盖原文件', false) - .action(create) - -program - .command('dev') - .description('开发组件库') - .action(dev) +program.version(VERSION); program - .command('build') - .description('打包组件库') - .action(build) + .command("create") + .description("创建一个组件模板或配置文件") + .option( + "-t --type ", + `创建类型,可选值:${CREATE_SUPPORT_TYPES.join(", ")}`, + validateCreateType + ) + .option("--ignore-parse-error", "忽略解析错误", false) + .option("--cover", "覆盖原文件", false) + .action(create); + +program.command("build").description("打包组件库").action(build); program - .command('generate:theme') - .description('生成主题变量文件') - .action(generateTheme) + .command("generate:theme") + .description("生成主题变量文件") + .action(generateTheme); -program.parse().version(VERSION) +program.parse().version(VERSION); diff --git a/packages/devui-cli/src/commands/build.ts b/packages/devui-cli/src/commands/build.ts index 2cf9e286..362dd984 100644 --- a/packages/devui-cli/src/commands/build.ts +++ b/packages/devui-cli/src/commands/build.ts @@ -1,91 +1,90 @@ -import path from 'path'; -import fs from 'fs'; -import fsExtra from 'fs-extra'; -import {build, defineConfig} from 'vite'; +import fs from "fs"; +import fsExtra from "fs-extra"; +import path from "path"; +import type { InlineConfig } from "vite"; +import { build as ViteBuild } from "vite"; +import { CWD } from "../shared/constant"; -import type {UserConfig, InlineConfig} from 'vite' -import {CWD} from '../shared/constant'; +const vue = require("@vitejs/plugin-vue"); +const vueJsx = require("@vitejs/plugin-vue-jsx"); -const vue = require('@vitejs/plugin-vue') -const vueJsx = require('@vitejs/plugin-vue-jsx') - -const entryDir = path.join(CWD, '/devui') -const outputDir = path.join(CWD, 'build') +const entryDir = path.join(CWD, "/devui"); +const outputDir = path.join(CWD, "build"); const baseConfig: InlineConfig = { - configFile: false, - publicDir: false, - plugins: [vue(), vueJsx()] -} + configFile: false, + publicDir: false, + plugins: [vue(), vueJsx()], +}; const rollupOptions = { - external: ['vue', 'vue-router'], - output: { - globals: { - vue: 'Vue' - } - } -} + external: ["vue", "vue-router"], + output: { + globals: { + vue: "Vue", + }, + }, +}; const buildSingle = async (name) => { - await build(({ - ...baseConfig, - build: { - rollupOptions, - lib: { - entry: path.resolve(entryDir, name), - name: 'index', - fileName: 'index', - formats: ['es', 'umd'] - }, - outDir: path.resolve(outputDir, name) - } - })) -} + await ViteBuild({ + ...baseConfig, + build: { + rollupOptions, + lib: { + entry: path.resolve(entryDir, name), + name: "index", + fileName: "index", + formats: ["es", "umd"], + }, + outDir: path.resolve(outputDir, name), + }, + }); +}; const buildAll = async () => { - await build(({ - ...baseConfig, - build: { - rollupOptions, - lib: { - entry: path.resolve(entryDir, 'vue-devui.ts'), - name: 'vue-devui', - fileName: 'vue-devui', - formats: ['es', 'umd'] - }, - outDir: outputDir - } - })) -} + await ViteBuild({ + ...baseConfig, + build: { + rollupOptions, + lib: { + entry: path.resolve(entryDir, "vue-devui.ts"), + name: "vue-devui", + fileName: "vue-devui", + formats: ["es", "umd"], + }, + outDir: outputDir, + }, + }); +}; const createPackageJson = (name) => { - const fileStr = `{ + const fileStr = `{ "name": "${name}", "version": "0.0.0", "main": "index.umd.js", "module": "index.es.js", "style": "style.css" -}` +}`; - fsExtra.outputFile( - path.resolve(outputDir, `${name}/package.json`), - fileStr, - 'utf-8' - ) -} + fsExtra.outputFile( + path.resolve(outputDir, `${name}/package.json`), + fileStr, + "utf-8" + ); +}; -exports.build = async () => { - await buildAll() +export const build = async () => { + await buildAll(); - const components = fs.readdirSync(entryDir).filter(name => { - const componentDir = path.resolve(entryDir, name) - const isDir = fs.lstatSync(componentDir).isDirectory() - return isDir && fs.readdirSync(componentDir).includes('index.ts') - }) + const components = fs.readdirSync(entryDir).filter((name) => { + const componentDir = path.resolve(entryDir, name); + const isDir = fs.lstatSync(componentDir).isDirectory(); + return isDir && fs.readdirSync(componentDir).includes("index.ts"); + }); - for (const name of components) { - await buildSingle(name) - createPackageJson(name) - } -} + for (const name of components) { + await buildSingle(name); + createPackageJson(name); + } +}; diff --git a/packages/devui-cli/src/commands/create.ts b/packages/devui-cli/src/commands/create.ts index 6585360b..08042ef5 100644 --- a/packages/devui-cli/src/commands/create.ts +++ b/packages/devui-cli/src/commands/create.ts @@ -1,233 +1,265 @@ -import {resolve} from 'path'; -import fs from 'fs-extra'; -import ora from 'ora'; -import inquirer from 'inquirer'; -import {isEmpty, kebabCase} from 'lodash'; - +import fs from "fs-extra"; +import inquirer from "inquirer"; +import { isEmpty, kebabCase } from "lodash"; +import ora from "ora"; +import { resolve } from "path"; import { - COMPONENT_PARTS_MAP, - CREATE_SUPPORT_TYPE_MAP, - CREATE_SUPPORT_TYPES, - CREATE_UNFINISHED_TYPES, - DEVUI_DIR, - DEVUI_NAMESPACE, - DOCS_FILE_NAME, - INDEX_FILE_NAME, - SITES_COMPONENTS_DIR, - TESTS_DIR_NAME, - VITEPRESS_SIDEBAR_FILE, - VITEPRESS_SIDEBAR_FILE_NAME, - VUE_DEVUI_FILE, - VUE_DEVUI_FILE_NAME, - VUE_DEVUI_IGNORE_DIRS -} from '../shared/constant'; -import {bigCamelCase, parseComponentInfo, parseExportByFileInfo, resolveDirFilesInfo} from '../shared/utils'; - -import {createVitepressSidebarTemplate} from '../templates/vitepress-sidebar'; - -import {createVueDevuiTemplate} from '../templates/vue-devui'; - -import {selectCreateType} from '../inquirers/create'; - -import {selectCategory, selectParts, typeName, typeTitle} from '../inquirers/component'; - -import {logger} from '../shared/logger'; - + selectCategory, + selectParts, + typeName, + typeTitle, +} from "../inquirers/component"; +import { selectCreateType } from "../inquirers/create"; import { - createComponentTemplate, - createDirectiveTemplate, createDocumentTemplate, createIndexTemplate, createServiceTemplate, - createStyleTemplate, createTestsTemplate, - createTypesTemplate -} from '../templates/component'; - + CREATE_SUPPORT_TYPES, + CREATE_SUPPORT_TYPE_MAP, + CREATE_UNFINISHED_TYPES, + DEVUI_DIR, + DOCS_FILE_NAME, + INDEX_FILE_NAME, + SITES_COMPONENTS_DIR, + TESTS_DIR_NAME, + VITEPRESS_SIDEBAR_FILE, + VITEPRESS_SIDEBAR_FILE_NAME, + VUE_DEVUI_FILE, + VUE_DEVUI_FILE_NAME, + VUE_DEVUI_IGNORE_DIRS, +} from "../shared/constant"; +import { logger } from "../shared/logger"; +import { + bigCamelCase, + parseComponentInfo, + parseExportByFileInfo, + resolveDirFilesInfo, +} from "../shared/utils"; +import { + createComponentTemplate, + createDirectiveTemplate, + createDocumentTemplate, + createIndexTemplate, + createServiceTemplate, + createStyleTemplate, + createTestsTemplate, + createTypesTemplate, +} from "../templates/component"; +import { createVitepressSidebarTemplate } from "../templates/vitepress-sidebar"; +import { createVueDevuiTemplate } from "../templates/vue-devui"; export const validateCreateType = (type) => { - const re = new RegExp('^(' + CREATE_SUPPORT_TYPES.map((t) => `(${t})`).join('|') + ')$') - const flag = re.test(type) + const re = new RegExp( + "^(" + CREATE_SUPPORT_TYPES.map((t) => `(${t})`).join("|") + ")$" + ); + const flag = re.test(type); - !flag && logger.error(`类型错误,可选类型为:${CREATE_SUPPORT_TYPES.join(', ')}`) + !flag && + logger.error(`类型错误,可选类型为:${CREATE_SUPPORT_TYPES.join(", ")}`); - return flag ? type : null -} + return flag ? type : null; +}; // TODO: 待优化代码结构 export const create = async (cwd) => { - let {type} = cwd - - if (isEmpty(type)) { - const result = await inquirer.prompt([selectCreateType()]) - type = result.type + let { type } = cwd; + + if (isEmpty(type)) { + const result = await inquirer.prompt([selectCreateType()]); + type = result.type; + } + + if (CREATE_UNFINISHED_TYPES.includes(type)) { + logger.info("抱歉,该功能暂未完成!"); + process.exit(0); + } + + let params: any = {}; + + try { + switch (type) { + case CREATE_SUPPORT_TYPE_MAP.component: + params = await inquirer.prompt([ + typeName(), + typeTitle(), + selectCategory(), + selectParts(), + ]); + params.hasComponent = params.parts.includes("component"); + params.hasDirective = params.parts.includes("directive"); + params.hasService = params.parts.includes("service"); + + await createComponent({ params, cwd }); + break; + case CREATE_SUPPORT_TYPE_MAP["vue-devui"]: + // 创建 devui/vue-devui.ts + await createVueDevui(params, cwd); + // 创建 docs/.vitepress/config/sidebar.ts + await createVitepressSidebar(); + break; + default: + break; } + } catch (e) { + logger.error(e.toString()); + process.exit(1); + } +}; - if (CREATE_UNFINISHED_TYPES.includes(type)) { - logger.info('抱歉,该功能暂未完成!') - process.exit(0) +async function createComponent(params) { + let { name, hasComponent, hasDirective, hasService } = params; + + const componentName = kebabCase(name); + const styleName = kebabCase(name); + const typesName = kebabCase(name) + "-types"; + const directiveName = kebabCase(name) + "-directive"; + const serviceName = kebabCase(name) + "-service"; + const testName = kebabCase(name) + ".spec"; + + const _params = { + ...params, + componentName, + typesName, + directiveName, + serviceName, + styleName, + testName, + }; + + const componentTemplate = createComponentTemplate(_params); + const styleTemplate = createStyleTemplate(_params); + const typesTemplate = createTypesTemplate(_params); + const directiveTemplate = createDirectiveTemplate(); + const serviceTemplate = createServiceTemplate(_params); + const indexTemplate = createIndexTemplate(_params); + // 增加测试模板 + const testsTemplate = createTestsTemplate(_params); + // 增加文档模板 + const docTemplate = createDocumentTemplate(_params); + + const componentDir = resolve(DEVUI_DIR, componentName); + const srcDir = resolve(componentDir, "src"); + const testsDir = resolve(DEVUI_DIR, componentName, TESTS_DIR_NAME); + const docsDir = resolve(SITES_COMPONENTS_DIR, componentName); + + if (fs.pathExistsSync(componentDir)) { + logger.error(`${bigCamelCase(componentName)} 组件目录已存在!`); + process.exit(1); + } + + let spinner = ora(`创建组件 ${bigCamelCase(componentName)} 开始...`).start(); + + try { + await Promise.all([ + fs.mkdirs(componentDir), + fs.mkdirs(srcDir), + fs.mkdirs(testsDir), + ]); + + const writeFiles = [ + fs.writeFile(resolve(componentDir, INDEX_FILE_NAME), indexTemplate), + fs.writeFile(resolve(testsDir, `${testName}.ts`), testsTemplate), + ]; + + if (!fs.existsSync(docsDir)) { + fs.mkdirSync(docsDir); + writeFiles.push( + fs.writeFile(resolve(docsDir, DOCS_FILE_NAME), docTemplate) + ); + } else { + logger.warning( + `\n${bigCamelCase(componentName)} 组件文档已存在:${resolve( + docsDir, + DOCS_FILE_NAME + )}` + ); } - let params:any = {} - - try { - switch (type) { - case CREATE_SUPPORT_TYPE_MAP.component: - params = await inquirer.prompt([typeName(), typeTitle(), selectCategory(), selectParts()]) - params.hasComponent = params.parts.includes('component') - params.hasDirective = params.parts.includes('directive') - params.hasService = params.parts.includes('service') - - await createComponent({params, cwd}) - break - case CREATE_SUPPORT_TYPE_MAP['vue-devui']: - // 创建 devui/vue-devui.ts - await createVueDevui(params, cwd) - // 创建 docs/.vitepress/config/sidebar.ts - await createVitepressSidebar() - break - default: - break - } - } catch (e) { - logger.error(e.toString()) - process.exit(1) + if (hasComponent || hasService) { + writeFiles.push( + fs.writeFile(resolve(srcDir, `${typesName}.ts`), typesTemplate) + ); } -} -async function createComponent(params) { - let {name, hasComponent, hasDirective, hasService} = params - - const componentName = kebabCase(name) - const styleName = kebabCase(name) - const typesName = kebabCase(name) + '-types' - const directiveName = kebabCase(name) + '-directive' - const serviceName = kebabCase(name) + '-service' - const testName = kebabCase(name) + '.spec' - - const _params = { - ...params, - componentName, - typesName, - directiveName, - serviceName, - styleName, - testName + if (hasComponent) { + writeFiles.push( + fs.writeFile( + resolve(srcDir, `${componentName}.tsx`), + componentTemplate + ), + fs.writeFile(resolve(srcDir, `${styleName}.scss`), styleTemplate) + ); } - const componentTemplate = createComponentTemplate(_params) - const styleTemplate = createStyleTemplate(_params) - const typesTemplate = createTypesTemplate(_params) - const directiveTemplate = createDirectiveTemplate() - const serviceTemplate = createServiceTemplate(_params) - const indexTemplate = createIndexTemplate(_params) - // 增加测试模板 - const testsTemplate = createTestsTemplate(_params) - // 增加文档模板 - const docTemplate = createDocumentTemplate(_params) - - const componentDir = resolve(DEVUI_DIR, componentName) - const srcDir = resolve(componentDir, 'src') - const testsDir = resolve(DEVUI_DIR, componentName, TESTS_DIR_NAME) - const docsDir = resolve(SITES_COMPONENTS_DIR, componentName) - - if (fs.pathExistsSync(componentDir)) { - logger.error(`${bigCamelCase(componentName)} 组件目录已存在!`) - process.exit(1) + if (hasDirective) { + writeFiles.push( + fs.writeFile(resolve(srcDir, `${directiveName}.ts`), directiveTemplate) + ); } - let spinner = ora(`创建组件 ${bigCamelCase(componentName)} 开始...`).start() - - try { - await Promise.all([fs.mkdirs(componentDir), fs.mkdirs(srcDir), fs.mkdirs(testsDir)]) - - const writeFiles = [ - fs.writeFile(resolve(componentDir, INDEX_FILE_NAME), indexTemplate), - fs.writeFile(resolve(testsDir, `${testName}.ts`), testsTemplate), - ] - - if (!fs.existsSync(docsDir)) { - fs.mkdirSync(docsDir) - writeFiles.push(fs.writeFile(resolve(docsDir, DOCS_FILE_NAME), docTemplate)) - } else { - logger.warning(`\n${bigCamelCase(componentName)} 组件文档已存在:${resolve(docsDir, DOCS_FILE_NAME)}`) - } - - if (hasComponent || hasService) { - writeFiles.push(fs.writeFile(resolve(srcDir, `${typesName}.ts`), typesTemplate)) - } - - if (hasComponent) { - writeFiles.push( - fs.writeFile(resolve(srcDir, `${componentName}.tsx`), componentTemplate), - fs.writeFile(resolve(srcDir, `${styleName}.scss`), styleTemplate) - ) - } - - if (hasDirective) { - writeFiles.push(fs.writeFile(resolve(srcDir, `${directiveName}.ts`), directiveTemplate)) - } - - if (hasService) { - writeFiles.push(fs.writeFile(resolve(srcDir, `${serviceName}.ts`), serviceTemplate)) - } - - await Promise.all(writeFiles) - - spinner.succeed(`创建组件 ${bigCamelCase(componentName)} 成功!`) - logger.info(`组件目录:${componentDir}`) - } catch (e) { - spinner.fail(e.toString()) - process.exit(1) + if (hasService) { + writeFiles.push( + fs.writeFile(resolve(srcDir, `${serviceName}.ts`), serviceTemplate) + ); } + + await Promise.all(writeFiles); + + spinner.succeed(`创建组件 ${bigCamelCase(componentName)} 成功!`); + logger.info(`组件目录:${componentDir}`); + } catch (e) { + spinner.fail(e.toString()); + process.exit(1); + } } -async function createVueDevui(params, {ignoreParseError}) { - const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) - const exportModules = [] +async function createVueDevui(params, { ignoreParseError }) { + const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS); + const exportModules = []; - fileInfo.forEach((f) => { - const em = parseExportByFileInfo(f, ignoreParseError) + fileInfo.forEach((f) => { + const em = parseExportByFileInfo(f, ignoreParseError); - if (isEmpty(em)) return + if (isEmpty(em)) return; - exportModules.push(em) - }) + exportModules.push(em); + }); - const template = createVueDevuiTemplate(exportModules) + const template = createVueDevuiTemplate(exportModules); - let spinner = ora(`创建 ${VUE_DEVUI_FILE_NAME} 文件开始...`).start() + let spinner = ora(`创建 ${VUE_DEVUI_FILE_NAME} 文件开始...`).start(); - try { - await fs.writeFile(VUE_DEVUI_FILE, template, {encoding: 'utf-8'}) + try { + await fs.writeFile(VUE_DEVUI_FILE, template, { encoding: "utf-8" }); - spinner.succeed(`创建 ${VUE_DEVUI_FILE_NAME} 文件成功!`) - logger.info(`文件地址:${VUE_DEVUI_FILE}`) - } catch (e) { - spinner.fail(e.toString()) - process.exit(1) - } + spinner.succeed(`创建 ${VUE_DEVUI_FILE_NAME} 文件成功!`); + logger.info(`文件地址:${VUE_DEVUI_FILE}`); + } catch (e) { + spinner.fail(e.toString()); + process.exit(1); + } } async function createVitepressSidebar() { - const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) - const componentsInfo = [] + const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS); + const componentsInfo = []; - fileInfo.forEach((f) => { - const info = parseComponentInfo(f.dirname) + fileInfo.forEach((f) => { + const info = parseComponentInfo(f.dirname); - if (isEmpty(info)) return + if (isEmpty(info)) return; - componentsInfo.push(info) - }) + componentsInfo.push(info); + }); - const template = createVitepressSidebarTemplate(componentsInfo) + const template = createVitepressSidebarTemplate(componentsInfo); - let spinner = ora(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件开始...`).start() + let spinner = ora(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件开始...`).start(); - try { - await fs.writeFile(VITEPRESS_SIDEBAR_FILE, template, {encoding: 'utf-8'}) + try { + await fs.writeFile(VITEPRESS_SIDEBAR_FILE, template, { encoding: "utf-8" }); - spinner.succeed(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件成功!`) - logger.info(`文件地址:${VITEPRESS_SIDEBAR_FILE}`) - } catch (e) { - spinner.fail(e.toString()) - process.exit(1) - } + spinner.succeed(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件成功!`); + logger.info(`文件地址:${VITEPRESS_SIDEBAR_FILE}`); + } catch (e) { + spinner.fail(e.toString()); + process.exit(1); + } } diff --git a/packages/devui-cli/src/commands/dev.ts b/packages/devui-cli/src/commands/dev.ts deleted file mode 100644 index 932a50a7..00000000 --- a/packages/devui-cli/src/commands/dev.ts +++ /dev/null @@ -1,90 +0,0 @@ -import path from 'path'; -import fs from 'fs'; -import fsExtra from 'fs-extra'; -import {build, defineConfig} from 'vite'; - -import type {UserConfig, InlineConfig} from 'vite' -import {CWD, DEVUI_DIR} from '../shared/constant'; - -const vue = require('@vitejs/plugin-vue') -const vueJsx = require('@vitejs/plugin-vue-jsx') - -const entryDir = path.join(CWD, '/devui') -const outputDir = path.join(CWD, 'build') - -const baseConfig: InlineConfig = { - configFile: false, - publicDir: false, - plugins: [vue(), vueJsx()] -} - -const rollupOptions = { - external: ['vue', 'vue-router'], - output: { - globals: { - vue: 'Vue' - } - } -} - -const buildSingle = async (name) => { - await build(({ - ...baseConfig, - build: { - rollupOptions, - lib: { - entry: path.join(entryDir, name), - name: 'index', - fileName: 'index', - formats: ['es', 'umd'] - }, - outDir: path.join(outputDir, name) - } - })) -} - -const buildAll = async () => { - await build(({ - ...baseConfig, - build: { - rollupOptions, - lib: { - entry: path.join(entryDir, 'vue-devui.ts'), - name: 'vue-devui', - fileName: 'vue-devui', - formats: ['es', 'umd'] - }, - outDir: outputDir - } - })) -} - -const createPackageJson = (name) => { - const fileStr = `{ - "name": "${name}", - "version": "0.0.0", - "main": "index.umd.js", - "module": "index.es.js", - "style": "style.css" -}` - - fsExtra.outputFile( - path.join(outputDir, `${name}/package.json`), - fileStr, - 'utf-8' - ) -} - -export async function dev() { - await buildAll() - const components = fs.readdirSync(entryDir).filter(name => { - const componentDir = path.join(entryDir, name) - const isDir = fs.lstatSync(componentDir).isDirectory() - return isDir && fs.readdirSync(componentDir).includes('index.ts') - }) - - for (const name of components) { - await buildSingle(name) - createPackageJson(name) - } -} diff --git a/packages/devui-cli/src/commands/generate-theme.ts b/packages/devui-cli/src/commands/generate-theme.ts index e1012e66..c8aea78a 100644 --- a/packages/devui-cli/src/commands/generate-theme.ts +++ b/packages/devui-cli/src/commands/generate-theme.ts @@ -1,18 +1,14 @@ -import path from 'path'; -import fs from 'fs-extra'; -import {CWD} from '../shared/constant'; +import fs from "fs-extra"; +import path from "path"; +import { CWD } from "../shared/constant"; -require('esbuild-register') -const theme = require(path.join(CWD, '/devui/theme/themes/light')) -const themeFilePath = path.join(CWD, '/devui/theme/theme.scss') +require("esbuild-register"); +const theme = require(path.join(CWD, "/devui/theme/themes/light")); +const themeFilePath = path.join(CWD, "/devui/theme/theme.scss"); const fileStr = Object.entries(theme) - .map(([key, value]) => `$${key}: var(--${key}, ${value})`) - .join(';\n') + .map(([key, value]) => `$${key}: var(--${key}, ${value})`) + .join(";\n"); export const generateTheme = async () => { - await fs.outputFile( - themeFilePath, - fileStr, - 'utf-8' - ) -} + await fs.outputFile(themeFilePath, fileStr, "utf-8"); +}; diff --git a/packages/devui-cli/src/index.ts b/packages/devui-cli/src/index.ts index 4e81f2ca..6e80f56a 100644 --- a/packages/devui-cli/src/index.ts +++ b/packages/devui-cli/src/index.ts @@ -1,14 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import packageJson from '../package.json'; -import {dev} from './commands/dev'; -import {create} from './commands/create'; +import { create } from "./commands/create"; +import { build } from './commands/build'; -export const cliVersion: string = packageJson.version; - -process.env.DEVUI_CLI_VERSION = cliVersion; - -export { - dev, - create -}; +export { build, create }; diff --git a/packages/devui-cli/src/inquirers/component.ts b/packages/devui-cli/src/inquirers/component.ts index 9dd0335f..99485f0c 100644 --- a/packages/devui-cli/src/inquirers/component.ts +++ b/packages/devui-cli/src/inquirers/component.ts @@ -1,53 +1,56 @@ -import {COMPONENT_PARTS_MAP, VITEPRESS_SIDEBAR_CATEGORY} from '../shared/constant'; +import { + COMPONENT_PARTS_MAP, + VITEPRESS_SIDEBAR_CATEGORY, +} from "../shared/constant"; export const typeName = () => ({ - name: 'name', - type: 'input', - message: '(必填)请输入组件 name ,将用作目录及文件名:', + name: "name", + type: "input", + message: "(必填)请输入组件 name ,将用作目录及文件名:", validate: (value) => { - if (value.trim() === '') { - return '组件 name 是必填项!' + if (value.trim() === "") { + return "组件 name 是必填项!"; } - return true - } -}) + return true; + }, +}); export const typeTitle = () => ({ - name: 'title', - type: 'input', - message: '(必填)请输入组件中文名称,将用作文档列表显示:', + name: "title", + type: "input", + message: "(必填)请输入组件中文名称,将用作文档列表显示:", validate: (value) => { - if (value.trim() === '') { - return '组件名称是必填项!' + if (value.trim() === "") { + return "组件名称是必填项!"; } - return true - } -}) + return true; + }, +}); export const selectCategory = () => ({ - name: 'category', - type: 'list', - message: '(必填)请选择组件分类,将用作文档列表分类:', + name: "category", + type: "list", + message: "(必填)请选择组件分类,将用作文档列表分类:", choices: VITEPRESS_SIDEBAR_CATEGORY, - default: 0 -}) + default: 0, +}); export const typeAliasName = () => ({ - name: 'alias', - type: 'input', - message: '(选填)请输入组件 name 别名,将用作组件别名被导出:' -}) + name: "alias", + type: "input", + message: "(选填)请输入组件 name 别名,将用作组件别名被导出:", +}); export const selectParts = () => ({ - name: 'parts', - type: 'checkbox', - message: '(必填)请选择包含部件,将自动生成部件文件:', + name: "parts", + type: "checkbox", + message: "(必填)请选择包含部件,将自动生成部件文件:", choices: COMPONENT_PARTS_MAP, default: [], validate: (value) => { if (value.length === 0) { - return '部件必须包含至少一项' + return "部件必须包含至少一项"; } - return true - } -}) + return true; + }, +}); diff --git a/packages/devui-cli/src/inquirers/create.ts b/packages/devui-cli/src/inquirers/create.ts index 55cd752e..01b9a462 100644 --- a/packages/devui-cli/src/inquirers/create.ts +++ b/packages/devui-cli/src/inquirers/create.ts @@ -1,10 +1,9 @@ -import {CREATE_SUPPORT_TYPES} from '../shared/constant'; - +import { CREATE_SUPPORT_TYPES } from "../shared/constant"; export const selectCreateType = () => ({ - name: 'type', - type: 'list', - message: '(必填)请选择创建类型:', + name: "type", + type: "list", + message: "(必填)请选择创建类型:", choices: CREATE_SUPPORT_TYPES, - default: 0 -}) + default: 0, +}); diff --git a/packages/devui-cli/src/shared/constant.ts b/packages/devui-cli/src/shared/constant.ts index c3bb2908..853b4dcb 100644 --- a/packages/devui-cli/src/shared/constant.ts +++ b/packages/devui-cli/src/shared/constant.ts @@ -1,46 +1,58 @@ -const { resolve } = require('path') -const { version } = require('../../package.json') +import { join } from "path"; +// @ts-ignore +import packageJson from "../../package.json"; -export const VERSION = version -export const CWD = process.cwd() -export const DEVUI_DIR = resolve(CWD, 'devui') -export const DEVUI_NAMESPACE = 'd' -export const CSS_CLASS_PREFIX = 'devui' -export const TESTS_DIR_NAME = '__tests__' -export const INDEX_FILE_NAME = 'index.ts' -export const DOCS_FILE_NAME = 'index.md' -export const VUE_DEVUI_IGNORE_DIRS = ['shared', 'style'] -export const VUE_DEVUI_FILE_NAME = 'vue-devui.ts' -export const VUE_DEVUI_FILE = resolve(DEVUI_DIR, VUE_DEVUI_FILE_NAME) -export const SITES_DIR = resolve(CWD, 'docs') -export const SITES_COMPONENTS_DIR_NAME = 'components' -export const SITES_COMPONENTS_DIR = resolve(SITES_DIR, SITES_COMPONENTS_DIR_NAME) -export const VITEPRESS_DIR = resolve(SITES_DIR, '.vitepress') -export const VITEPRESS_SIDEBAR_FILE_NAME = 'sidebar.ts' -export const VITEPRESS_SIDEBAR_FILE = resolve(VITEPRESS_DIR, `config/${VITEPRESS_SIDEBAR_FILE_NAME}`) +const { version } = packageJson; +export const VERSION = version; +export const CWD = process.cwd(); +export const DEVUI_DIR = join(CWD, "devui"); +export const DEVUI_NAMESPACE = "d"; +export const CSS_CLASS_PREFIX = "devui"; +export const TESTS_DIR_NAME = "__tests__"; +export const INDEX_FILE_NAME = "index.ts"; +export const DOCS_FILE_NAME = "index.md"; +export const VUE_DEVUI_IGNORE_DIRS = ["shared", "style"]; +export const VUE_DEVUI_FILE_NAME = "vue-devui.ts"; +export const VUE_DEVUI_FILE = join(DEVUI_DIR, VUE_DEVUI_FILE_NAME); +export const SITES_DIR = join(CWD, "docs"); +export const SITES_COMPONENTS_DIR_NAME = "components"; +export const SITES_COMPONENTS_DIR = join(SITES_DIR, SITES_COMPONENTS_DIR_NAME); +export const VITEPRESS_DIR = join(SITES_DIR, ".vitepress"); +export const VITEPRESS_SIDEBAR_FILE_NAME = "sidebar.ts"; +export const VITEPRESS_SIDEBAR_FILE = join( + VITEPRESS_DIR, + `config/${VITEPRESS_SIDEBAR_FILE_NAME}` +); // 这里的分类顺序将会影响最终生成的页面侧边栏顺序 -export const VITEPRESS_SIDEBAR_CATEGORY = ['通用', '导航', '反馈', '数据录入', '数据展示', '布局'] +export const VITEPRESS_SIDEBAR_CATEGORY = [ + "通用", + "导航", + "反馈", + "数据录入", + "数据展示", + "布局", +]; export const COMPONENT_PARTS_MAP = [ { - name: 'component(组件)', - value: 'component' + name: "component(组件)", + value: "component", }, { - name: 'directive(指令)', - value: 'directive' + name: "directive(指令)", + value: "directive", }, { - name: 'service(服务)', - value: 'service' - } -] + name: "service(服务)", + value: "service", + }, +]; export const CREATE_SUPPORT_TYPE_MAP = Object.freeze({ - component: 'component', - 'vue-devui': 'vue-devui', - 'theme-variable': 'theme-variable', -}) -export const CREATE_SUPPORT_TYPES = Object.keys(CREATE_SUPPORT_TYPE_MAP) -export const CREATE_UNFINISHED_TYPES = [] + component: "component", + "vue-devui": "vue-devui", + "theme-variable": "theme-variable", +}); +export const CREATE_SUPPORT_TYPES = Object.keys(CREATE_SUPPORT_TYPE_MAP); +export const CREATE_UNFINISHED_TYPES = []; diff --git a/packages/devui-cli/src/shared/logger.ts b/packages/devui-cli/src/shared/logger.ts index 7c12ee2a..680d5583 100644 --- a/packages/devui-cli/src/shared/logger.ts +++ b/packages/devui-cli/src/shared/logger.ts @@ -1,16 +1,16 @@ -import chalk from 'chalk'; +import chalk from "chalk"; export const logger = { - info(text) { - console.log(chalk.hex('#00afef')(text)) - }, - success(text) { - console.log(chalk.hex('#00c48f')(text)) - }, - warning(text) { - console.log(chalk.hex('#ff9800')(text)) - }, - error(text) { - console.log(chalk.hex('#f44336')(text)) - } -} + info(text: string) { + console.log(chalk.hex("#00afef")(text)); + }, + success(text: string) { + console.log(chalk.hex("#00c48f")(text)); + }, + warning(text: string) { + console.log(chalk.hex("#ff9800")(text)); + }, + error(text: string) { + console.log(chalk.hex("#f44336")(text)); + }, +}; diff --git a/packages/devui-cli/src/shared/utils.ts b/packages/devui-cli/src/shared/utils.ts index 4e269278..436848cb 100644 --- a/packages/devui-cli/src/shared/utils.ts +++ b/packages/devui-cli/src/shared/utils.ts @@ -1,133 +1,132 @@ -const {camelCase, upperFirst} = require('lodash') -const {INDEX_FILE_NAME, DEVUI_DIR} = require('./constant') -import {resolve} from 'path' - -import fs from 'fs-extra'; - -import {logger} from './logger'; - -const traverse = require('@babel/traverse').default -const babelParser = require('@babel/parser') - -export const bigCamelCase = (str) => { - return upperFirst(camelCase(str)) -} - -export const resolveDirFilesInfo = (targetDir, ignoreDirs = []) => { - return fs - .readdirSync(targetDir) - .filter( - (dir) => - // 过滤:必须是目录,且不存在与忽略目录内,拥有 INDEX_FILE_NAME - fs.statSync(resolve(targetDir, dir)).isDirectory() && - !ignoreDirs.includes(dir) && - fs.existsSync(resolve(targetDir, dir, INDEX_FILE_NAME)) - ) - .map((dir) => ({ - name: bigCamelCase(dir), - dirname: dir, - path: resolve(targetDir, dir, INDEX_FILE_NAME) - })) -} +import fs from "fs-extra"; +import { camelCase, upperFirst } from "lodash"; +import { resolve } from "path"; +import { DEVUI_DIR, INDEX_FILE_NAME } from "./constant"; +import { logger } from "./logger"; + +const traverse = require("@babel/traverse").default; +const babelParser = require("@babel/parser"); + +export const bigCamelCase = (str: string) => { + return upperFirst(camelCase(str)); +}; + +export const resolveDirFilesInfo = (targetDir: string, ignoreDirs = []) => { + return fs + .readdirSync(targetDir) + .filter( + (dir) => + // 过滤:必须是目录,且不存在与忽略目录内,拥有 INDEX_FILE_NAME + fs.statSync(resolve(targetDir, dir)).isDirectory() && + !ignoreDirs.includes(dir) && + fs.existsSync(resolve(targetDir, dir, INDEX_FILE_NAME)) + ) + .map((dir) => ({ + name: bigCamelCase(dir), + dirname: dir, + path: resolve(targetDir, dir, INDEX_FILE_NAME), + })); +}; interface exportModuleI { - default: string; - parts: Array; - fileInfo: string + default: string; + parts: Array; + fileInfo: string; } export const parseExportByFileInfo = (fileInfo, ignoreParseError) => { - const exportModule: exportModuleI = {default: '', fileInfo: '', parts: []} - const indexContent = fs.readFileSync(fileInfo.path, {encoding: 'utf-8'}) - - const ast = babelParser.parse(indexContent, { - sourceType: 'module', - plugins: [ - 'typescript' - ] - }) - - const exportName = [] - let exportDefault = null - - traverse(ast, { - ExportNamedDeclaration({node}) { - if (node.specifiers.length) { - node.specifiers.forEach(specifier => { - exportName.push(specifier.local.name) - }) - } else if (node.declaration) { - if (node.declaration.declarations) { - node.declaration.declarations.forEach(dec => { - exportName.push(dec.id.name) - }) - } else if (node.declaration.id) { - exportName.push(node.declaration.id.name) - } - } - }, - ExportDefaultDeclaration() { - exportDefault = fileInfo.name + 'Install' - } - }) - - if (!exportDefault) { - logger.error(`${fileInfo.path} must have "export default".`) - - if (ignoreParseError) { - return exportModule - } else { - process.exit(1) + const exportModule: exportModuleI = { default: "", fileInfo: "", parts: [] }; + const indexContent = fs.readFileSync(fileInfo.path, { encoding: "utf-8" }); + + const ast = babelParser.parse(indexContent, { + sourceType: "module", + plugins: ["typescript"], + }); + + const exportName = []; + let exportDefault = null; + + traverse(ast, { + ExportNamedDeclaration({ node }) { + if (node.specifiers.length) { + node.specifiers.forEach((specifier) => { + exportName.push(specifier.local.name); + }); + } else if (node.declaration) { + if (node.declaration.declarations) { + node.declaration.declarations.forEach((dec) => { + exportName.push(dec.id.name); + }); + } else if (node.declaration.id) { + exportName.push(node.declaration.id.name); } + } + }, + ExportDefaultDeclaration() { + exportDefault = fileInfo.name + "Install"; + }, + }); + + if (!exportDefault) { + logger.error(`${fileInfo.path} must have "export default".`); + + if (ignoreParseError) { + return exportModule; + } else { + process.exit(1); } + } - if (!exportName.length) { - logger.error(`${fileInfo.path} must have "export xxx".`) + if (!exportName.length) { + logger.error(`${fileInfo.path} must have "export xxx".`); - if (ignoreParseError) { - return exportModule - } else { - process.exit(1) - } + if (ignoreParseError) { + return exportModule; + } else { + process.exit(1); } - - exportModule.default = exportDefault - exportModule.parts = exportName - exportModule.fileInfo = fileInfo - - return exportModule -} - -export const parseComponentInfo = (name) => { - const componentInfo = { - name: bigCamelCase(name) - } - let hasExportDefault = false - const indexContent = fs.readFileSync(resolve(DEVUI_DIR, name, INDEX_FILE_NAME), {encoding: 'utf-8'}) - - const ast = babelParser.parse(indexContent, { - sourceType: 'module', - plugins: [ - 'typescript' - ] - }) - traverse(ast, { - ExportDefaultDeclaration({node}) { - hasExportDefault = true - if (node.declaration && node.declaration.properties) { - const properties = node.declaration.properties - properties.forEach(pro => { - if (pro.type === 'ObjectProperty') { - componentInfo[pro.key.name] = pro.value.value - } - }) - } - } - }) - - if (!hasExportDefault) { - logger.warning(`${componentInfo.name} must have "export default" and component info.`) - } - - return componentInfo -} + } + + exportModule.default = exportDefault; + exportModule.parts = exportName; + exportModule.fileInfo = fileInfo; + + return exportModule; +}; + +export const parseComponentInfo = (name: string) => { + const componentInfo = { + name: bigCamelCase(name), + }; + let hasExportDefault = false; + const indexContent = fs.readFileSync( + resolve(DEVUI_DIR, name, INDEX_FILE_NAME), + { encoding: "utf-8" } + ); + + const ast = babelParser.parse(indexContent, { + sourceType: "module", + plugins: ["typescript"], + }); + traverse(ast, { + ExportDefaultDeclaration({ node }) { + hasExportDefault = true; + if (node.declaration && node.declaration.properties) { + const properties = node.declaration.properties; + properties.forEach((pro) => { + if (pro.type === "ObjectProperty") { + componentInfo[pro.key.name] = pro.value.value; + } + }); + } + }, + }); + + if (!hasExportDefault) { + logger.warning( + `${componentInfo.name} must have "export default" and component info.` + ); + } + + return componentInfo; +}; diff --git a/packages/devui-cli/src/templates/component.ts b/packages/devui-cli/src/templates/component.ts index 52edb6ba..d70617bb 100644 --- a/packages/devui-cli/src/templates/component.ts +++ b/packages/devui-cli/src/templates/component.ts @@ -1,15 +1,17 @@ -import {CSS_CLASS_PREFIX, DEVUI_NAMESPACE} from '../shared/constant'; - - -import {camelCase} from 'lodash'; - -import {bigCamelCase} from '../shared/utils'; - +import { camelCase } from "lodash"; +import { CSS_CLASS_PREFIX, DEVUI_NAMESPACE } from "../shared/constant"; +import { bigCamelCase } from "../shared/utils"; // 创建组件模板 -export const createComponentTemplate = ({ styleName, componentName, typesName }) => `\ +export const createComponentTemplate = ({ + styleName, + componentName, + typesName, +}) => `\ import { defineComponent } from 'vue' -import { ${camelCase(componentName)}Props, ${bigCamelCase(componentName)}Props } from './${typesName}' +import { ${camelCase(componentName)}Props, ${bigCamelCase( + componentName +)}Props } from './${typesName}' import './${styleName}.scss' export default defineComponent({ @@ -22,7 +24,7 @@ export default defineComponent({ } } }) -` +`; // 创建类型声明模板 export const createTypesTemplate = ({ componentName }) => `\ @@ -34,8 +36,10 @@ export const ${camelCase(componentName)}Props = { } \*\/ } as const -export type ${bigCamelCase(componentName)}Props = ExtractPropTypes -` +export type ${bigCamelCase( + componentName +)}Props = ExtractPropTypes +`; // 创建指令模板 export const createDirectiveTemplate = () => `\ @@ -49,9 +53,13 @@ export default { beforeUnmount() { }, unmounted() { } } -` +`; // 创建server模板 -export const createServiceTemplate = ({ componentName, typesName, serviceName }) => `\ +export const createServiceTemplate = ({ + componentName, + typesName, + serviceName, +}) => `\ import { ${bigCamelCase(componentName)}Props } from './${typesName}' const ${bigCamelCase(serviceName)} = { @@ -59,14 +67,14 @@ const ${bigCamelCase(serviceName)} = { } export default ${bigCamelCase(serviceName)} -` +`; // 创建scss模板 export const createStyleTemplate = ({ componentName }) => `\ .${CSS_CLASS_PREFIX}-${componentName} { // } -` +`; // 创建index模板 export const createIndexTemplate = ({ @@ -77,27 +85,39 @@ export const createIndexTemplate = ({ hasService, componentName, directiveName, - serviceName + serviceName, }) => { - const importComponentStr = `\nimport ${bigCamelCase(componentName)} from './src/${componentName}'` - const importDirectiveStr = `\nimport ${bigCamelCase(directiveName)} from './src/${directiveName}'` - const importServiceStr = `\nimport ${bigCamelCase(serviceName)} from './src/${serviceName}'` - - const installComponentStr = ` app.use(${bigCamelCase(componentName)} as any)` - const installDirectiveStr = `\n app.directive('${bigCamelCase(componentName)}', ${bigCamelCase(directiveName)})` - const installServiceStr = `\n app.config.globalProperties.$${camelCase(serviceName)} = ${bigCamelCase( + const importComponentStr = `\nimport ${bigCamelCase( + componentName + )} from './src/${componentName}'`; + const importDirectiveStr = `\nimport ${bigCamelCase( + directiveName + )} from './src/${directiveName}'`; + const importServiceStr = `\nimport ${bigCamelCase( + serviceName + )} from './src/${serviceName}'`; + + const installComponentStr = ` app.use(${bigCamelCase( + componentName + )} as any)`; + const installDirectiveStr = `\n app.directive('${bigCamelCase( + componentName + )}', ${bigCamelCase(directiveName)})`; + const installServiceStr = `\n app.config.globalProperties.$${camelCase( serviceName - )}` + )} = ${bigCamelCase(serviceName)}`; - const getPartStr = (state, str) => (state ? str : '') + const getPartStr = (state, str) => (state ? str : ""); - const importStr = getPartStr(hasComponent, importComponentStr) + - getPartStr(hasDirective, importDirectiveStr) + - getPartStr(hasService, importServiceStr) + const importStr = + getPartStr(hasComponent, importComponentStr) + + getPartStr(hasDirective, importDirectiveStr) + + getPartStr(hasService, importServiceStr); - const installStr = getPartStr(hasComponent, installComponentStr) + - getPartStr(hasDirective, installDirectiveStr) + - getPartStr(hasService, installServiceStr) + const installStr = + getPartStr(hasComponent, installComponentStr) + + getPartStr(hasDirective, installDirectiveStr) + + getPartStr(hasService, installServiceStr); return `\ import type { App } from 'vue'\ @@ -105,17 +125,19 @@ ${importStr} ${ hasComponent ? `\n${bigCamelCase(componentName)}.install = function(app: App): void { - app.component(${bigCamelCase(componentName)}.name, ${bigCamelCase(componentName)}) + app.component(${bigCamelCase(componentName)}.name, ${bigCamelCase( + componentName + )}) }\n` - : '' + : "" } export { ${[ hasComponent ? bigCamelCase(componentName) : null, hasDirective ? bigCamelCase(directiveName) : null, - hasService ? bigCamelCase(serviceName) : null + hasService ? bigCamelCase(serviceName) : null, ] .filter((p) => p !== null) - .join(', ')} } + .join(", ")} } export default { title: '${bigCamelCase(componentName)} ${title}', @@ -125,8 +147,8 @@ export default { ${installStr} } } -` -} +`; +}; // 创建测试模板 export const createTestsTemplate = ({ @@ -135,29 +157,26 @@ export const createTestsTemplate = ({ serviceName, hasComponent, hasDirective, - hasService + hasService, }) => `\ import { mount } from '@vue/test-utils'; import { ${[ - hasComponent ? bigCamelCase(componentName) : null, - hasDirective ? bigCamelCase(directiveName) : null, - hasService ? bigCamelCase(serviceName) : null - ] - .filter((p) => p !== null) - .join(', ')} } from '../index'; + hasComponent ? bigCamelCase(componentName) : null, + hasDirective ? bigCamelCase(directiveName) : null, + hasService ? bigCamelCase(serviceName) : null, +] + .filter((p) => p !== null) + .join(", ")} } from '../index'; describe('${componentName} test', () => { it('${componentName} init render', async () => { // todo }) }) -` +`; // 创建文档模板 -export const createDocumentTemplate = ({ - componentName, - title -}) => `\ +export const createDocumentTemplate = ({ componentName, title }) => `\ # ${bigCamelCase(componentName)} ${title} // todo 组件描述 @@ -213,4 +232,4 @@ d-${componentName} 事件 | | | | | | | | | | -` +`; diff --git a/packages/devui-cli/src/templates/vitepress-sidebar.ts b/packages/devui-cli/src/templates/vitepress-sidebar.ts index 87df74b3..452a4846 100644 --- a/packages/devui-cli/src/templates/vitepress-sidebar.ts +++ b/packages/devui-cli/src/templates/vitepress-sidebar.ts @@ -1,35 +1,47 @@ -import {kebabCase} from 'lodash'; -import {SITES_COMPONENTS_DIR_NAME, VITEPRESS_SIDEBAR_CATEGORY} from '../shared/constant'; -import {logger} from '../shared/logger'; +import { kebabCase } from "lodash"; +import { + SITES_COMPONENTS_DIR_NAME, + VITEPRESS_SIDEBAR_CATEGORY, +} from "../shared/constant"; +import { logger } from "../shared/logger"; function buildComponentOptions(text, name, status) { - return {text, link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/`, status} + return { + text, + link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/`, + status, + }; } function buildCategoryOptions(text, children = []) { - return {text, children} + return { text, children }; } export const createVitepressSidebarTemplate = (componentsInfo = []) => { - const rootNav = {text: '快速开始', link: '/'} - const categoryMap = VITEPRESS_SIDEBAR_CATEGORY.reduce((map, cate) => map.set(cate, []), new Map()) + const rootNav = { text: "快速开始", link: "/" }; + const categoryMap = VITEPRESS_SIDEBAR_CATEGORY.reduce( + (map, cate) => map.set(cate, []), + new Map() + ); - componentsInfo.forEach((info) => { - if (categoryMap.has(info.category)) { - categoryMap.get(info.category).push(buildComponentOptions(info.title, info.name, info.status)) - } else { - logger.warning(`组件 ${info.name} 的分类 ${info.category} 不存在!`) - } - }) + componentsInfo.forEach((info) => { + if (categoryMap.has(info.category)) { + categoryMap + .get(info.category) + .push(buildComponentOptions(info.title, info.name, info.status)); + } else { + logger.warning(`组件 ${info.name} 的分类 ${info.category} 不存在!`); + } + }); - const sidebar = [].concat( - rootNav, - Array.from(categoryMap).map(([k, v]) => buildCategoryOptions(k, v)) - ) + const sidebar = [].concat( + rootNav, + Array.from(categoryMap).map(([k, v]) => buildCategoryOptions(k, v)) + ); - return `\ + return `\ export default { - '/': ${JSON.stringify(sidebar, null, 2).replace(/\n/g, '\n\t')} -} -` + '/': ${JSON.stringify(sidebar, null, 2).replace(/\n/g, "\n\t")} } +`; +}; diff --git a/packages/devui-cli/src/templates/vue-devui.ts b/packages/devui-cli/src/templates/vue-devui.ts index 12a629c1..14be28ea 100644 --- a/packages/devui-cli/src/templates/vue-devui.ts +++ b/packages/devui-cli/src/templates/vue-devui.ts @@ -1,36 +1,38 @@ -import {relative} from 'path'; -import {INDEX_FILE_NAME, VERSION, VUE_DEVUI_FILE} from '../shared/constant'; +import { relative } from "path"; +import { INDEX_FILE_NAME, VERSION, VUE_DEVUI_FILE } from "../shared/constant"; export const createVueDevuiTemplate = (exportModules = []) => { - const packages = [] - const imports = [] - const installs = [] - - exportModules.forEach((m) => { - const {fileInfo} = m - const relativePath = relative(VUE_DEVUI_FILE, fileInfo.path) - .replace(/\\/g, '/') - .replace('..', '.') - .replace('/' + INDEX_FILE_NAME, '') - - const importStr = `import ${m.default}, { ${m.parts.join(', ')} } from '${relativePath}'` - - packages.push(...m.parts) - imports.push(importStr) - installs.push(m.default) - }) - - const template = `\ + const packages = []; + const imports = []; + const installs = []; + + exportModules.forEach((m) => { + const { fileInfo } = m; + const relativePath = relative(VUE_DEVUI_FILE, fileInfo.path) + .replace(/\\/g, "/") + .replace("..", ".") + .replace("/" + INDEX_FILE_NAME, ""); + + const importStr = `import ${m.default}, { ${m.parts.join( + ", " + )} } from '${relativePath}'`; + + packages.push(...m.parts); + imports.push(importStr); + installs.push(m.default); + }); + + const template = `\ import type { App } from 'vue' -${imports.join('\n')} +${imports.join("\n")} const installs = [ - ${installs.join(',\n\t')} + ${installs.join(",\n\t")} ] export { - ${packages.join(',\n\t')} + ${packages.join(",\n\t")} } export default { @@ -39,7 +41,7 @@ export default { installs.forEach((p) => app.use(p as any)) } } -` +`; - return template -} + return template; +}; diff --git a/packages/devui-cli/tsconfig.json b/packages/devui-cli/tsconfig.json index 2d6eda1d..9cf5d52d 100644 --- a/packages/devui-cli/tsconfig.json +++ b/packages/devui-cli/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "skipLibCheck": true, "esModuleInterop": true, + "emitDeclarationOnly": true, "lib": ["esnext", "dom"] }, "include": ["src/**/*", "site"], -- Gitee From f5527e55fd985c7e0943349b2cce6fdb3c62910c Mon Sep 17 00:00:00 2001 From: TinsFox <1414849373@qq.com> Date: Thu, 28 Oct 2021 16:48:33 +0800 Subject: [PATCH 7/7] chore: update tsconfig.json --- packages/devui-cli/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/devui-cli/tsconfig.json b/packages/devui-cli/tsconfig.json index 9cf5d52d..2d6eda1d 100644 --- a/packages/devui-cli/tsconfig.json +++ b/packages/devui-cli/tsconfig.json @@ -6,7 +6,6 @@ "declaration": true, "skipLibCheck": true, "esModuleInterop": true, - "emitDeclarationOnly": true, "lib": ["esnext", "dom"] }, "include": ["src/**/*", "site"], -- Gitee