createComponentMode.js 8.2 KB

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