|
|
@@ -1,265 +1,236 @@
|
|
|
// 创建模板
|
|
|
-
|
|
|
const inquirer = require('inquirer');
|
|
|
-// import { ROOT_PACKAGE_PATH } from '../util/dic';
|
|
|
-
|
|
|
-const path = require('path');
|
|
|
+const ora = require('ora');
|
|
|
const fs = require('fs');
|
|
|
-const config = require('../src/config.json');
|
|
|
+const path = require('path');
|
|
|
+const esprima = require('esprima');
|
|
|
+const estraverse = require('estraverse');
|
|
|
+const escodegen = require('escodegen');
|
|
|
const demoModel = require('./demo');
|
|
|
+const config = require('../src/config.json');
|
|
|
+
|
|
|
const nav = config.nav;
|
|
|
|
|
|
-var newCpt = {
|
|
|
- version: '3.0.0',
|
|
|
- name: '',
|
|
|
- type: '',
|
|
|
- cName: '',
|
|
|
- desc: '',
|
|
|
- sort: '',
|
|
|
- show: true,
|
|
|
+let spinner;
|
|
|
+
|
|
|
+let componentConfig = {
|
|
|
+ version: '3.0.0', //版本
|
|
|
+ name: '', //组件名称
|
|
|
+ type: '', //组件属于哪种类型
|
|
|
+ cName: '', //组件中文名称
|
|
|
+ desc: '', //组件描述
|
|
|
+ show: '', //组件是否显示在demo/文档中
|
|
|
+ tarodoc: false, //是否显示taro文档
|
|
|
+ // taro: true, //是否生成.taro.vue文件,因为目前默认组件都会生成,所以,此项目前用不到
|
|
|
+ // exportEmpty: true, //表示是否要在生成运行时文件时导出组件模块,目前用不到
|
|
|
+ // exportEmptyTaro: true, //表示是否要在生成taro运行文件时导出组件模块,目前用不到
|
|
|
author: ''
|
|
|
};
|
|
|
-function init() {
|
|
|
- inquirer
|
|
|
- .prompt([
|
|
|
- {
|
|
|
- type: 'input',
|
|
|
- name: 'name',
|
|
|
- message: '组件英文名(每个单词的首字母都大写,如TextBox):',
|
|
|
- validate(value) {
|
|
|
- let repeat = false;
|
|
|
- for (var i = 0; i < nav.length; i++) {
|
|
|
- for (var j = 0; j < nav[i].packages.length; j++) {
|
|
|
- if (nav[i].packages[j].name === value) {
|
|
|
- repeat = true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- if (repeat) {
|
|
|
- return '该组件名已存在!';
|
|
|
- }
|
|
|
- const pass = value && value.match(/^[A-Z]/);
|
|
|
- if (pass) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return '不能为空,且每个单词的首字母都要大写,如TextBox';
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'input',
|
|
|
- name: 'cName',
|
|
|
- message: '组件中文名(十个字以内):',
|
|
|
- validate(value) {
|
|
|
- const pass = value && value.length <= 10;
|
|
|
- if (pass) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return '不能为空,且不能超过十个字符';
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'input',
|
|
|
- name: 'desc',
|
|
|
- message: '组件描述(五十个字以内):'
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'rawlist',
|
|
|
- name: 'type',
|
|
|
- message: '请选择组件类型(输入编号):目前只支持组建模板',
|
|
|
- choices: ['component', 'filter', 'directive', 'method'],
|
|
|
- validate(value) {
|
|
|
- const pass = value && /^[1-4]$/.test(value);
|
|
|
- if (pass) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return '输入有误!请输入选项前编号';
|
|
|
- }
|
|
|
- },
|
|
|
- {
|
|
|
- type: 'input',
|
|
|
- name: 'sort',
|
|
|
- message:
|
|
|
- '请选择组件分类(输入编号):1布局组件,2操作反馈,3基础组件,4导航组件,5数据录入,6特色组件',
|
|
|
- validate(value) {
|
|
|
- const pass = /^[1-6]$/.test(value);
|
|
|
- if (pass) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- return '输入有误!请输入选项前编号';
|
|
|
- }
|
|
|
- },
|
|
|
- // {
|
|
|
- // type: 'confirm',
|
|
|
- // name: 'showDemo',
|
|
|
- // message: '是否需要DEMO页面?',
|
|
|
- // default: true
|
|
|
- // },
|
|
|
- // {
|
|
|
- // type: 'confirm',
|
|
|
- // name: 'showTest',
|
|
|
- // message: '是否需要单元测试页面?',
|
|
|
- // default: true
|
|
|
- // },
|
|
|
- {
|
|
|
- type: 'input',
|
|
|
- name: 'author',
|
|
|
- message: '组件作者(可署化名):'
|
|
|
+const questions = [
|
|
|
+ {
|
|
|
+ type: 'input',
|
|
|
+ name: 'name',
|
|
|
+ message: '组件英文名(每个单词的首字母都大写,例如InputNumber):',
|
|
|
+ validate(value) {
|
|
|
+ value = value.trim();
|
|
|
+ if (!value) return '组件名称不能为空';
|
|
|
+ if (!/^[A-Z][a-zA-Z]*$/.test(value)) return '组件名称采用驼峰式命名,且首字母大写,如InputNumber';
|
|
|
+ for (let i = 0; i < nav.length; i++) {
|
|
|
+ const item = nav[i];
|
|
|
+ const cItem = item.packages.find((values) => values.name === value);
|
|
|
+ if (cItem) return `${value}已存在!`;
|
|
|
}
|
|
|
- ])
|
|
|
- .then(function (answers) {
|
|
|
- // answers.sort = String(sorts.indexOf(answers.sort));
|
|
|
- newCpt = Object.assign(newCpt, answers);
|
|
|
- createNew();
|
|
|
- });
|
|
|
-}
|
|
|
-function createIndexJs() {
|
|
|
- const nameLc = newCpt.name.toLowerCase();
|
|
|
- const destPath = path.join('src/packages/__VUE/' + nameLc);
|
|
|
- if (!fs.existsSync(destPath)) {
|
|
|
- fs.mkdirSync(destPath);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'input',
|
|
|
+ name: 'cName',
|
|
|
+ message: '组件中文名(10字以内):',
|
|
|
+ validate(value) {
|
|
|
+ value = value.trim();
|
|
|
+ if (value && value.length <= 10) return true;
|
|
|
+ return `组件名称不能为空,并且在10字以内`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'input',
|
|
|
+ name: 'desc',
|
|
|
+ message: '组件描述(50字以内):',
|
|
|
+ validate(value) {
|
|
|
+ value = value.trim();
|
|
|
+ if (value && value.length <= 50) return true;
|
|
|
+ return `组件描述不能为空,并且在50字以内`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'rawlist',
|
|
|
+ name: 'type',
|
|
|
+ message: '请选择组件的分类',
|
|
|
+ choices: nav.map((item) => `${item.name}`),
|
|
|
+ validate(value) {
|
|
|
+ value = +value.trim();
|
|
|
+ if (value && /\d+$/.test(value) && value <= nav.length) return true;
|
|
|
+ return `您的输入有误,请输入编号`;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'confrim',
|
|
|
+ name: 'show',
|
|
|
+ message: '组件是否显示在文档和demo中(y/n),如:SwiperItem则不需要',
|
|
|
+ default: 'y'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'input',
|
|
|
+ name: 'author',
|
|
|
+ message: '请输入组件作者(可署化名)'
|
|
|
}
|
|
|
- // copy(path.join(__dirname, './__template__/**.*'), destPath, function (err: any, file: any) {
|
|
|
- // if (err) {
|
|
|
- // console.log('拷贝__template__目录失败!');
|
|
|
- // }
|
|
|
- // createNew();
|
|
|
- // });
|
|
|
+];
|
|
|
|
|
|
- if (newCpt.type == 'method') return;
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- // let content = `import ${newCpt.name} from './src/${nameLc}.vue';
|
|
|
- // ${newCpt.name}.install = function(Vue) {
|
|
|
- // Vue.${newCpt.type}(${newCpt.name}.name, ${newCpt.name});
|
|
|
- // };
|
|
|
- // export default ${newCpt.name}`;
|
|
|
- // let content2 = `${newCpt.name}.install = function(Vue) {
|
|
|
- // Vue.${newCpt.type}(${newCpt.name}.name, ${newCpt.name});
|
|
|
- // };
|
|
|
- // export default ${newCpt.name}`;
|
|
|
+const traverseAst = (ast, componentName, componentType) => {
|
|
|
+ estraverse.traverse(ast, {
|
|
|
+ enter: (node) => {
|
|
|
+ if (node.type === 'VariableDeclarator' && node.id.name === 'subpackages') {
|
|
|
+ node.init.elements.forEach((item) => {
|
|
|
+ const itemKey = item.properties.find((value) => value.key.name === 'root').value.value;
|
|
|
+ const itemValue = item.properties.find((value) => value.key.name === 'pages').value.elements;
|
|
|
+ const path = `pages/${componentName}/index`;
|
|
|
+ if (itemKey === componentType && !itemValue.find((subItem) => subItem.value === path)) {
|
|
|
+ itemValue.push({
|
|
|
+ type: 'Literal',
|
|
|
+ value: path,
|
|
|
+ raw: "'pages/'" + componentName + "'/index'"
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
- // const dirPath = path.join(__dirname, `../src/packages/${nameLc}/`);
|
|
|
+const generateToFile = (ast, taroConfigPath) => {
|
|
|
+ const code = escodegen.generate(ast);
|
|
|
+ fs.writeFileSync(taroConfigPath, code, 'utf8');
|
|
|
+};
|
|
|
|
|
|
- // const filePath = path.join(dirPath, `index.js`);
|
|
|
- // if (!fs.existsSync(dirPath)) {
|
|
|
- // fs.mkdirSync(filePath);
|
|
|
- // }
|
|
|
- // if (newCpt.type == 'filter' || newCpt.type == 'directive'){
|
|
|
- // content = content2;
|
|
|
- // }
|
|
|
- // fs.writeFile(filePath, content, (err) => {
|
|
|
- // if (err) throw err;
|
|
|
- resolve(`生成index.js文件成功`);
|
|
|
- // });
|
|
|
- });
|
|
|
-}
|
|
|
+const createSource = async (paths) => {
|
|
|
+ /**生成 vue .taro.vue 文件 */
|
|
|
+ const sourcePath = paths.sourcePath;
|
|
|
+ const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
|
|
|
+ const content = demoModel(name).source;
|
|
|
+ const filePath = path.join(sourcePath, 'index.vue');
|
|
|
+ const taroFilePath = path.join(sourcePath, 'index.taro.vue');
|
|
|
+ if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, content);
|
|
|
+ if (!fs.existsSync(taroFilePath)) fs.writeFileSync(taroFilePath, content);
|
|
|
+};
|
|
|
|
|
|
-function createVue() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const nameLc = newCpt.name.toLowerCase();
|
|
|
- let content = demoModel(nameLc).vue;
|
|
|
- const dirPath = path.join(__dirname, `../src/packages/__VUE/${nameLc}/`);
|
|
|
- const filePath = path.join(dirPath, `index.vue`);
|
|
|
- if (!fs.existsSync(dirPath)) {
|
|
|
- fs.mkdirSync(filePath);
|
|
|
- }
|
|
|
- fs.writeFile(filePath, content, (err) => {
|
|
|
- if (err) throw err;
|
|
|
- resolve(`生成${newCpt.name}.vue文件成功`);
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
+const createDemo = (paths) => {
|
|
|
+ /**生成 demo tarodemo taro配置文件 */
|
|
|
+ const sourcePath = paths.sourcePath;
|
|
|
+ const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
|
|
|
+ const taroPath = `${paths.taroPath}/pages/${name}`;
|
|
|
+ const demo = demoModel(name).demo;
|
|
|
+ const taroDemo = demoModel(name).taroDemo;
|
|
|
+ const filePath = path.join(sourcePath, 'demo.vue');
|
|
|
+ const taroFilePath = path.join(taroPath, 'index.vue');
|
|
|
+ const taroConfigPath = path.join(taroPath, 'index.config.ts');
|
|
|
+ if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, demo);
|
|
|
+ if (!fs.existsSync(taroFilePath)) fs.writeFileSync(taroFilePath, taroDemo);
|
|
|
+ if (!fs.existsSync(taroConfigPath))
|
|
|
+ fs.writeFileSync(taroConfigPath, `export default { navigationBarTitleText: '${componentConfig.name}' }`);
|
|
|
+};
|
|
|
|
|
|
-function createDemo() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const nameLc = newCpt.name.toLowerCase();
|
|
|
- let content = demoModel(nameLc).demo;
|
|
|
- const dirPath = path.join(__dirname, '../src/packages/__VUE/' + nameLc);
|
|
|
- const filePath = path.join(dirPath, `demo.vue`);
|
|
|
- if (!fs.existsSync(dirPath)) {
|
|
|
- fs.mkdirSync(filePath);
|
|
|
- }
|
|
|
- fs.writeFile(filePath, content, (err) => {
|
|
|
- if (err) throw err;
|
|
|
- resolve(`生成demo.vue文件成功`);
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
+const createDoc = (paths) => {
|
|
|
+ /**生成doc文档 */
|
|
|
+ const sourcePath = paths.sourcePath;
|
|
|
+ const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
|
|
|
+ const doc = demoModel(name).doc;
|
|
|
+ const filePath = path.join(sourcePath, 'doc.md');
|
|
|
+ if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, doc);
|
|
|
+};
|
|
|
|
|
|
-function addToPackageJson() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- let sort = newCpt.sort;
|
|
|
- newCpt.sort = nav[sort - 1].packages.length + 1;
|
|
|
- nav[sort - 1].packages.push(newCpt);
|
|
|
- config.nav = nav;
|
|
|
- // conf.packages.push(newCpt);
|
|
|
- const dirPath = path.join(__dirname, `../`);
|
|
|
- const filePath = path.join(dirPath, `src/config.json`);
|
|
|
+const createScss = (paths) => {
|
|
|
+ /**生成scss文件 */
|
|
|
+ const sourcePath = paths.sourcePath;
|
|
|
+ const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
|
|
|
+ const scss = `.nut-${name} {}`;
|
|
|
+ const filePath = path.join(sourcePath, 'index.scss');
|
|
|
+ if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, scss);
|
|
|
+};
|
|
|
|
|
|
- var tempfile = JSON.stringify(config, null, 2);
|
|
|
- fs.writeFile(filePath, tempfile, (err) => {
|
|
|
- if (err) throw err;
|
|
|
- resolve(`修改config.json文件成功`);
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
-function createScss() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const nameLc = newCpt.name.toLowerCase();
|
|
|
- let content = `.nut-${nameLc} {}`;
|
|
|
- const dirPath = path.join(__dirname, '../src/packages/__VUE/' + nameLc);
|
|
|
- const filePath = path.join(dirPath, `index.scss`);
|
|
|
- if (!fs.existsSync(dirPath)) {
|
|
|
- fs.mkdirSync(filePath);
|
|
|
- }
|
|
|
- fs.writeFile(filePath, content, (err) => {
|
|
|
- if (err) throw err;
|
|
|
- resolve(`index.scss文件成功`);
|
|
|
- });
|
|
|
- });
|
|
|
-}
|
|
|
-function createDoc() {
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- const nameLc = newCpt.name.toLowerCase();
|
|
|
- let content = demoModel(nameLc).doc;
|
|
|
- const dirPath = path.join(__dirname, '../src/packages/__VUE/' + nameLc);
|
|
|
- const filePath = path.join(dirPath, `doc.md`);
|
|
|
- if (!fs.existsSync(dirPath)) {
|
|
|
- fs.mkdirSync(filePath);
|
|
|
- }
|
|
|
- fs.writeFile(filePath, content, (err) => {
|
|
|
- if (err) throw err;
|
|
|
- resolve(`doc.md文件成功`);
|
|
|
- });
|
|
|
+const createTest = (paths) => {
|
|
|
+ /**生成测试文件 */
|
|
|
+ const sourcePath = paths.sourcePath;
|
|
|
+ const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
|
|
|
+ const testPath = path.join(`src/packages/__VUE/${name}/__tests__`);
|
|
|
+ if (!fs.existsSync(testPath)) fs.mkdirSync(testPath);
|
|
|
+ const testFilePath = path.join(testPath, `${name}.spec.ts`);
|
|
|
+ if (!fs.existsSync(testFilePath)) fs.writeFileSync(testFilePath, `import { mount } from '@vue/test-utils';`);
|
|
|
+};
|
|
|
+
|
|
|
+const updateConfig = () => {
|
|
|
+ /**更新 config 文件 */
|
|
|
+ const componentTypeItem = nav.find((navitem) => navitem.name === componentConfig.type);
|
|
|
+ if (!componentTypeItem.packages.find((item) => item.name === componentConfig.name)) {
|
|
|
+ componentTypeItem.packages.push(componentConfig);
|
|
|
+ }
|
|
|
+ const filePath = path.join(`src/config.json`);
|
|
|
+ const tempfile = JSON.stringify(config, null, 2);
|
|
|
+ fs.writeFileSync(filePath, tempfile);
|
|
|
+};
|
|
|
+
|
|
|
+const createDir = () => {
|
|
|
+ const componentName = componentConfig.name.toLowerCase();
|
|
|
+ const componentType = nav.find((navitem) => navitem.name === componentConfig.type).enName;
|
|
|
+ const sourcePath = path.join(`src/packages/__VUE/${componentName}`);
|
|
|
+ const taroPath = path.join(`src/sites/mobile-taro/vue/src/${componentType}`);
|
|
|
+ if (!fs.existsSync(sourcePath)) fs.mkdirSync(sourcePath);
|
|
|
+ if (!fs.existsSync(taroPath)) fs.mkdirSync(`${taroPath}/pages`);
|
|
|
+ if (!fs.existsSync(`${taroPath}/pages/${componentName}`)) fs.mkdirSync(`${taroPath}/pages/${componentName}`);
|
|
|
+ const taroConfigPath = path.join(`src/sites/mobile-taro/vue/src/app.config.ts`);
|
|
|
+ try {
|
|
|
+ const taroConfigData = fs.readFileSync(taroConfigPath, 'utf8');
|
|
|
+ const ast = esprima.parseModule(taroConfigData);
|
|
|
+ traverseAst(ast, componentName, componentType);
|
|
|
+ generateToFile(ast, taroConfigPath);
|
|
|
+ } catch (err) {
|
|
|
+ console.log(err);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ sourcePath,
|
|
|
+ taroPath
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+const createFile = (filePath) => {
|
|
|
+ createSource(filePath);
|
|
|
+ createDemo(filePath);
|
|
|
+ createDoc(filePath);
|
|
|
+ createScss(filePath);
|
|
|
+ createTest(filePath);
|
|
|
+ updateConfig();
|
|
|
+ spinner.succeed('组件模板生成完毕,请开始你的表演~');
|
|
|
+ process.exit();
|
|
|
+};
|
|
|
+
|
|
|
+const create = () => {
|
|
|
+ const filePath = createDir();
|
|
|
+ createFile(filePath);
|
|
|
+};
|
|
|
+
|
|
|
+const init = () => {
|
|
|
+ inquirer.prompt(questions).then((answers) => {
|
|
|
+ answers.show = answers.show === 'y' ? true : false;
|
|
|
+ componentConfig = Object.assign(componentConfig, answers);
|
|
|
+ spinner = ora('正在生成组件模版,请稍后...').start();
|
|
|
+ create();
|
|
|
});
|
|
|
-}
|
|
|
-function createNew() {
|
|
|
- createIndexJs()
|
|
|
- .then(() => {
|
|
|
- if (newCpt.type == 'component' || newCpt.type == 'method') {
|
|
|
- return createVue();
|
|
|
- } else {
|
|
|
- return;
|
|
|
- }
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- return createScss();
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- return createDemo();
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- return createDoc();
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- return addToPackageJson();
|
|
|
- })
|
|
|
- .then(() => {
|
|
|
- console.log('组件模板生成完毕,请开始你的表演~');
|
|
|
- process.exit();
|
|
|
- });
|
|
|
-}
|
|
|
-function createComponent() {
|
|
|
+};
|
|
|
+
|
|
|
+const createComponent = () => {
|
|
|
init();
|
|
|
-}
|
|
|
+};
|
|
|
+
|
|
|
createComponent();
|