createComponentMode.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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.value === 'root').value.value;
  99. const itemValue = item.properties.find((value) => value.key.value === '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 docEN = demoModel(name).docEN;
  148. const filePath = path.join(sourcePath, 'doc.md');
  149. const filePathEN = path.join(sourcePath, 'doc.en-US.md');
  150. if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, doc);
  151. if (!fs.existsSync(filePathEN)) fs.writeFileSync(filePathEN, docEN);
  152. };
  153. const createScss = (paths) => {
  154. /**生成scss文件 */
  155. const sourcePath = paths.sourcePath;
  156. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  157. const scss = `.nut-${name} {}`;
  158. const filePath = path.join(sourcePath, 'index.scss');
  159. if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, scss);
  160. };
  161. const createTest = (paths) => {
  162. /**生成测试文件 */
  163. const sourcePath = paths.sourcePath;
  164. const name = sourcePath.substring(sourcePath.lastIndexOf('/') + 1);
  165. const testPath = path.join(`src/packages/__VUE/${name}/__tests__`);
  166. if (!fs.existsSync(testPath)) fs.mkdirSync(testPath);
  167. const testFilePath = path.join(testPath, `${name}.spec.ts`);
  168. if (!fs.existsSync(testFilePath)) fs.writeFileSync(testFilePath, `import { mount } from '@vue/test-utils';`);
  169. };
  170. const updateConfig = () => {
  171. /**更新 config 文件 */
  172. const componentTypeItem = nav.find((navitem) => navitem.name === componentConfig.cType);
  173. if (!componentTypeItem.packages.find((item) => item.name === componentConfig.name)) {
  174. componentTypeItem.packages.push(componentConfig);
  175. }
  176. const filePath = path.join(`src/config.json`);
  177. const tempfile = JSON.stringify(config, null, 2);
  178. fs.writeFileSync(filePath, tempfile);
  179. };
  180. const createDir = () => {
  181. const componentName = componentConfig.name.toLowerCase();
  182. const componentType = nav.find((navitem) => navitem.name === componentConfig.cType).enName;
  183. const sourcePath = path.join(`src/packages/__VUE/${componentName}`);
  184. const taroPath = path.join(`src/sites/mobile-taro/vue/src/${componentType}`);
  185. if (!fs.existsSync(sourcePath)) fs.mkdirSync(sourcePath);
  186. if (!fs.existsSync(taroPath)) fs.mkdirSync(`${taroPath}/pages`);
  187. if (!fs.existsSync(`${taroPath}/pages/${componentName}`)) fs.mkdirSync(`${taroPath}/pages/${componentName}`);
  188. const taroConfigPath = path.join(`src/sites/mobile-taro/vue/src/app.config.ts`);
  189. try {
  190. const taroConfigData = fs.readFileSync(taroConfigPath, 'utf8');
  191. const ast = esprima.parseModule(taroConfigData);
  192. traverseAst(ast, componentName, componentType);
  193. generateToFile(ast, taroConfigPath);
  194. } catch (err) {
  195. console.log(err);
  196. }
  197. return {
  198. sourcePath,
  199. taroPath
  200. };
  201. };
  202. const createFile = (filePath) => {
  203. createSource(filePath);
  204. createDemo(filePath);
  205. createDoc(filePath);
  206. createScss(filePath);
  207. createTest(filePath);
  208. updateConfig();
  209. spinner.succeed('组件模板生成完毕,请开始你的表演~');
  210. process.exit();
  211. };
  212. const create = () => {
  213. const filePath = createDir();
  214. createFile(filePath);
  215. };
  216. const init = () => {
  217. inquirer.prompt(questions).then((answers) => {
  218. answers.show = answers.show === 'y' ? true : false;
  219. answers.type = answers.type === 'y' ? 'methods' : 'component';
  220. componentConfig = Object.assign(componentConfig, answers);
  221. spinner = ora('正在生成组件模版,请稍后...').start();
  222. create();
  223. });
  224. };
  225. const createComponent = () => {
  226. init();
  227. };
  228. createComponent();