关于高级篇

在脚手架入门版和脚手架进阶版中我们已经深入的了解到如何从0到1开发一个脚手架工具,那么在本篇的脚手架高级版中我将会带大家借助前面两篇中学到的知识实现一个可以通过终端指令生成js文件/组件在指定的文件夹中的功能。徜若前面两篇文章你已经很好的掌握,那么这篇是很容易学习和理解的,一起来看下吧~
高级版新增的结构目录

  1. coo-cli3
  2. ├─lib
  3. | ├─utils
  4. | | ├─compiler.js // ejs模板编译
  5. | ├─templates // ejs模板
  6. | | ├─component.vue.ejs
  7. | | ├─router.vue.ejs
  8. | | ├─store.vue.ejs
  9. | | types.vue.ejs
  10. | ├─core
  11. | | ├─create.js
  12. | | ├─actions
  13. | | | ├─addFile.js // 命令的主要逻辑js

命令功能及用法解析

coo addFile <要生成的文件/组件名> -d [指定文件夹] -t [文件类型]命令实现的主要功能(高级版)

  • 关联对应的ejs模板
  • ejs模板编译,直出result字符串
  • 将result字符串写入当前指定的文件
  • 将最后的文件放到用户指定的文件夹中

首先大家可以看到这个命令似乎要比前两篇中学习的命令多了一些字符,如:-d、-t。心里难免会犯嘀咕,这些是啥意思?干啥用的?怎么用?所以这里有必要给大家简单的介绍一下这个命令的基本用法。如下:

  • <要生成的文件/组件名> 尖括号这里是必写,ex: coo addFile HelloWorld
  • -d [指定文件夹] -d代表指定文件夹,destination缩写,方括号内容表示非必写,不写的话,默认值为:src/components,ex: coo addFile HelloWorld -d src/common
  • -t [文件类型] -t代表生成的文件类型,type缩写,文件类型如:.js文件、.vue组件等,方括号内容表示非必写,不写的话,默认值为:component,ex: coo addFile HelloWorld -d src/common -t router

-d、-t统称为命令行参数,可以为多个,具体实现代码如下:./lib/core/create.js

  1. program
  2. .command('addFile <file-name>')
  3. .description('add a new file')
  4. .option('-d, --dest <dest>', 'a destination folder')
  5. .option('-t, --type <type>', 'a file type')
  6. .action(addFileAction)
  7. .on('--help', function() {
  8. console.log('');
  9. console.log('Examples:');
  10. console.log('');
  11. console.log('coo addFile HelloWorld -d [src/components] -t [component]');
  12. });

ok,了解到这个命令的基本用法和实现后,接下来我们就来看下如何生成一个文件并在用户指定的文件夹中。

模板关联

想要通过命令生成一个不管是组件文件还是js文件,不难想到,我们本地应该是有对应的模板提供,因为生成的文件不可能是凭空而来,所以我们这里需要提前定制好一些对应的模板,需要注意的是,这里的模板不应该是一成不变的,他可以设计成更加灵活的,所以这里还是推荐大家使用模板引擎,如:EJS、Pug、Jade来定制模板,我这里是使用EJS模板来做演示,代码如下:./lib/templates/router.vue.ejs

  1. // 普通加载路由
  2. // import <%= data.name %> from './<%= data.name %>.vue'
  3. //懒加载路由
  4. const <%= data.name %> = () => import('./<%= data.name %>.vue')
  5. export default {
  6. path: './<%= data.lowerName %>',
  7. name: '<%= data.name %>',
  8. component: <%= data.name %>,
  9. children: []
  10. }

ok, 现在我们已经有了定制好的模板,并且模板的名字为router.vue.ejs,这里我们可以看到不同的模板文件对应不同的名字

  1. | ├─templates // ejs模板
  2. | | ├─component.vue.ejs
  3. | | ├─router.vue.ejs
  4. | | ├─store.vue.ejs
  5. | | types.vue.ejs

这样我们就可以很方便的通过命令参数-t指定文件类型后,通过字符串拼接的方式关联到对应的模板了,代码如下:./lib/core/actions/addFile.js

  1. const addFile = async (name, { dest = 'src/components', type = 'component' }) => {
  2. // 1. 关联对应的模版
  3. const tempName = `${type}.vue.ejs`
  4. }

模板编译

这部分我们主要是通过ejs的renderFile方法来对模板进行编译,通俗的讲就是,对模板中的数据变量进行替换渲染返回一个字符串,实现很简单,代码如下:./lib/utils/compiler.js

  1. const ejs = require('ejs')
  2. const path = require('path')
  3. const compiler = (tempName, data) => {
  4. const tempPath = path.resolve(__dirname, '..', `templates/${tempName}`)
  5. return new Promise((resolve, reject) => {
  6. ejs.renderFile(tempPath, { data }, {}, (err, res) => {
  7. if (err) {
  8. reject(err)
  9. return
  10. }
  11. resolve(res)
  12. })
  13. })
  14. }
  15. module.exports = compiler

这样我们就拿到了ejs编译后的结果

  1. const addFile = async (name, { dest = 'src/components', type = 'component' }) => {
  2. const data = { name, lowerName: name.toLowerCase() }
  3. // 1. 关联对应的模版
  4. const tempName = `${type}.vue.ejs`
  5. // 2. 编译ejs模板,直出result
  6. const res = await compiler(tempName, data)
  7. }

文件写入

最后就是根据用户指定的文件路径-d src/common进行文件写入了,文件写入功能我们在脚手架进阶版中已经讲过,很简单,这里不再赘述。在./lib/utils/writeFileTree.js中:

  1. const fs = require('fs-extra')
  2. const path = require('path')
  3. module.exports = async function writeFileTree (dir, files) {
  4. Object.keys(files).forEach((name) => {
  5. const filePath = path.join(dir, name)
  6. fs.ensureDirSync(path.dirname(filePath))
  7. fs.writeFileSync(filePath, files[name])
  8. })
  9. }

我们这里依然用writeFileTree这个方法进行文件写入

  1. const compiler = require('../../utils/compiler')
  2. const writeFileTree = require('../../utils/writeFileTree')
  3. const addFile = async (name, { dest = 'src/components', type = 'component' }) => {
  4. const data = { name, lowerName: name.toLowerCase() }
  5. // 1. 关联对应的模版
  6. const tempName = `${type}.vue.ejs`
  7. // 2. 编译ejs模板,直出result
  8. const res = await compiler(tempName, data)
  9. const files = {}
  10. // 3. 将result 写入对应的文件中
  11. files[type === 'component' ? `${name}.vue` : `${type}.js`] = res
  12. // 4. 将最后的文件放到用户指定的文件夹中
  13. writeFileTree(dest, files)
  14. }

可以看到,我们这里在给files增加属性时,简单的使用三元表达对type的值进行了判断,如果是component或不指定-t,那么在生成文件时默认为vue组件,否则都是js文件。

总结 🎉

有了脚手架入门版和脚手架进阶版前面两篇的层层铺垫和学习,脚手架高级版的整体实现思想其实很简单,也很容易理解,这也是实现通过命令生成文件功能的核心。那么到这里,脚手架开发分享系列就告一段落了,希望对大家有所帮助~😁
参考资料: