createComponentMode.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. // 创建模板
  2. const inquirer = require('inquirer');
  3. const ora = require('ora');
  4. const fs = require('fs');
  5. const path = require('path');
  6. const esprima = require('esprima');
  7. const estraverse = require('estraverse');
  8. const escodegen = require('escodegen');
  9. const demoModel = require('./demo');
  10. const config = require('../src/config.json');
  11. const nav = config.nav;
  12. let spinner;
  13. let componentConfig = {
  14. version: '3.0.0', //版本
  15. name: '', //组件名称
  16. type: '', //组件属于哪种类型
  17. cName: '', //组件中文名称
  18. desc: '', //组件描述
  19. show: '', //组件是否显示在demo/文档中
  20. tarodoc: false, //是否显示taro文档
  21. // taro: true, //是否生成.taro.vue文件,因为目前默认组件都会生成,所以,此项目前用不到
  22. // exportEmpty: true, //表示是否要在生成运行时文件时导出组件模块,目前用不到
  23. // exportEmptyTaro: true, //表示是否要在生成taro运行文件时导出组件模块,目前用不到
  24. author: ''
  25. };
  26. const questions = [
  27. {
  28. type: 'input',
  29. name: 'name',
  30. message: '组件英文名(每个单词的首字母都大写,例如InputNumber):',
  31. validate(value) {
  32. value = value.trim();
  33. if (!value) return '组件名称不能为空';
  34. if (!/^[A-Z][a-zA-Z]*$/.test(value)) return '组件名称采用驼峰式命名,且首字母大写,如InputNumber';
  35. for (let i = 0; i < nav.length; i++) {
  36. const item = nav[i];
  37. const cItem = item.packages.find((values) => values.name === value);
  38. if (cItem) return `${value}已存在!`;
  39. }
  40. return true;
  41. }
  42. },
  43. {
  44. type: 'input',
  45. name: 'cName',
  46. message: '组件中文名(10字以内):',
  47. validate(value) {
  48. value = value.trim();
  49. if (value && value.length <= 10) return true;
  50. return `组件名称不能为空,并且在10字以内`;
  51. }
  52. },
  53. {
  54. type: 'input',
  55. name: 'desc',
  56. message: '组件描述(50字以内):',
  57. validate(value) {
  58. value = value.trim();
  59. if (value && value.length <= 50) return true;
  60. return `组件描述不能为空,并且在50字以内`;
  61. }
  62. },
  63. {
  64. type: 'rawlist',
  65. name: 'type',
  66. message: '请选择组件的分类',
  67. choices: nav.map((item) => `${item.name}`),
  68. validate(value) {
  69. value = +value.trim();
  70. if (value && /\d+$/.test(value) && value <= nav.length) return true;
  71. return `您的输入有误,请输入编号`;
  72. }
  73. },
  74. {
  75. type: 'confrim',
  76. name: 'show',
  77. message: '组件是否显示在文档和demo中(y/n),如:SwiperItem则不需要',
  78. default: 'y'
  79. },
  80. {
  81. type: 'input',
  82. name: 'author',
  83. message: '请输入组件作者(可署化名)'
  84. }
  85. ];
  86. const traverseAst = (ast, componentName, componentType) => {
  87. estraverse.traverse(ast, {
  88. enter: (node) => {
  89. if (node.type === 'VariableDeclarator' && node.id.name === 'subpackages') {
  90. node.init.elements.forEach((item) => {
  91. const itemKey = item.properties.find((value) => value.key.name === 'root').value.value;
  92. const itemValue = item.properties.find((value) => value.key.name === 'pages').value.elements;
  93. const path = `pages/${componentName}/index`;
  94. if (itemKey === componentType && !itemValue.find((subItem) => subItem.value === path)) {
  95. itemValue.push({
  96. type: 'Literal',
  97. value: path,
  98. raw: "'pages/'" + componentName + "'/index'"
  99. });
  100. }
  101. });
  102. }
  103. }
  104. });
  105. };
  106. const generateToFile = (ast, taroConfigPath) => {
  107. const code = escodegen.generate(ast);
  108. fs.writeFileSync(taroConfigPath, code, 'utf8');
  109. };
  110. const createSource = async (paths) => {
  111. /**生成 vue .taro.vue 文件 */
  112. const sourcePath = paths.sourcePath;
  113. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  114. const content = demoModel(name).source;
  115. const filePath = path.join(sourcePath, 'index.vue');
  116. const taroFilePath = path.join(sourcePath, 'index.taro.vue');
  117. if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, content);
  118. if (!fs.existsSync(taroFilePath)) fs.writeFileSync(taroFilePath, content);
  119. };
  120. const createDemo = (paths) => {
  121. /**生成 demo tarodemo taro配置文件 */
  122. const sourcePath = paths.sourcePath;
  123. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  124. const taroPath = `${paths.taroPath}/pages/${name}`;
  125. const demo = demoModel(name).demo;
  126. const taroDemo = demoModel(name).taroDemo;
  127. const filePath = path.join(sourcePath, 'demo.vue');
  128. const taroFilePath = path.join(taroPath, 'index.vue');
  129. const taroConfigPath = path.join(taroPath, 'index.config.ts');
  130. if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, demo);
  131. if (!fs.existsSync(taroFilePath)) fs.writeFileSync(taroFilePath, taroDemo);
  132. if (!fs.existsSync(taroConfigPath))
  133. fs.writeFileSync(taroConfigPath, `export default { navigationBarTitleText: '${componentConfig.name}' }`);
  134. };
  135. const createDoc = (paths) => {
  136. /**生成doc文档 */
  137. const sourcePath = paths.sourcePath;
  138. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  139. const doc = demoModel(name).doc;
  140. const filePath = path.join(sourcePath, 'doc.md');
  141. if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, doc);
  142. };
  143. const createScss = (paths) => {
  144. /**生成scss文件 */
  145. const sourcePath = paths.sourcePath;
  146. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  147. const scss = `.nut-${name} {}`;
  148. const filePath = path.join(sourcePath, 'index.scss');
  149. if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, scss);
  150. };
  151. const createTest = (paths) => {
  152. /**生成测试文件 */
  153. const sourcePath = paths.sourcePath;
  154. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  155. const testPath = path.join(`src/packages/__VUE/${name}/__tests__`);
  156. if (!fs.existsSync(testPath)) fs.mkdirSync(testPath);
  157. const testFilePath = path.join(testPath, `${name}.spec.ts`);
  158. if (!fs.existsSync(testFilePath)) fs.writeFileSync(testFilePath, `import { mount } from '@vue/test-utils';`);
  159. };
  160. const updateConfig = () => {
  161. /**更新 config 文件 */
  162. const componentTypeItem = nav.find((navitem) => navitem.name === componentConfig.type);
  163. if (!componentTypeItem.packages.find((item) => item.name === componentConfig.name)) {
  164. componentTypeItem.packages.push(componentConfig);
  165. }
  166. const filePath = path.join(`src/config.json`);
  167. const tempfile = JSON.stringify(config, null, 2);
  168. fs.writeFileSync(filePath, tempfile);
  169. };
  170. const createDir = () => {
  171. const componentName = componentConfig.name.toLowerCase();
  172. const componentType = nav.find((navitem) => navitem.name === componentConfig.type).enName;
  173. const sourcePath = path.join(`src/packages/__VUE/${componentName}`);
  174. const taroPath = path.join(`src/sites/mobile-taro/vue/src/${componentType}`);
  175. if (!fs.existsSync(sourcePath)) fs.mkdirSync(sourcePath);
  176. if (!fs.existsSync(taroPath)) fs.mkdirSync(`${taroPath}/pages`);
  177. if (!fs.existsSync(`${taroPath}/pages/${componentName}`)) fs.mkdirSync(`${taroPath}/pages/${componentName}`);
  178. const taroConfigPath = path.join(`src/sites/mobile-taro/vue/src/app.config.ts`);
  179. try {
  180. const taroConfigData = fs.readFileSync(taroConfigPath, 'utf8');
  181. const ast = esprima.parseModule(taroConfigData);
  182. traverseAst(ast, componentName, componentType);
  183. generateToFile(ast, taroConfigPath);
  184. } catch (err) {
  185. console.log(err);
  186. }
  187. return {
  188. sourcePath,
  189. taroPath
  190. };
  191. };
  192. const createFile = (filePath) => {
  193. createSource(filePath);
  194. createDemo(filePath);
  195. createDoc(filePath);
  196. createScss(filePath);
  197. createTest(filePath);
  198. updateConfig();
  199. spinner.succeed('组件模板生成完毕,请开始你的表演~');
  200. process.exit();
  201. };
  202. const create = () => {
  203. const filePath = createDir();
  204. createFile(filePath);
  205. };
  206. const init = () => {
  207. inquirer.prompt(questions).then((answers) => {
  208. answers.show = answers.show === 'y' ? true : false;
  209. componentConfig = Object.assign(componentConfig, answers);
  210. spinner = ora('正在生成组件模版,请稍后...').start();
  211. create();
  212. });
  213. };
  214. const createComponent = () => {
  215. init();
  216. };
  217. createComponent();