我们团队的前端项目是基于一套内部的后台框架进行开发的,这套框架是基于vue和ElementUI进行了一些定制化包装,并加入了一些自己团队设计的模块,可以进一步简化后台页面的开发工作。
这套框架拆分为基础组件模块,用户权限模块,数据图表模块三个模块,后台业务层的开发至少要基于基础组件模块,可以根据具体需要加入用户权限模块或者数据图表模块。尽管vue提供了一些脚手架工具vue-cli,但由于我们的项目是基于多页面的配置进行开发和打包,与vue-cli生成的项目结构和配置有些不一样,所以创建项目的时候,仍然需要人工去修改很多地方,甚至为了方便,直接从之前的项目copy过来然后进行魔改。表面上看问题不大,但其实存在很多问题:
重复性工作,繁琐而且浪费时间
copy过来的模板容易存在无关的代码
项目中有很多需要配置的地方,容易忽略一些配置点,进而埋坑
人工操作永远都有可能犯错,建新项目时,总要花时间去排错
内部框架也在不停的迭代,人工建项目往往不知道框架最新的版本号是多少,使用旧版本的框架可能会重新引入一些bug
针对以上问题,我开发了一个脚手架工具,可以根据交互动态生成项目结构,自动添加依赖和配置,并移除不需要的文件。
接下来整理一下我的整个开发经历。
基本思路
开始撸代码之前,先捋一捋思路。其实,在实现自己的脚手架之前,我反复整理分析了vue-cli的实现,发现很多有意思的模块,并从中借鉴了它的一些好的思想。
vue-cli是将项目模板作为资源独立发布在git上,然后在运行的时候将模板下载下来,经过模板引擎渲染,最后生成工程。这样将项目模板与工具分离的目的主要是,项目模板负责项目的结构和依赖配置,脚手架负责项目构建的流程,这两部分并没有太大的关联,通过分离,可以确保这两部分独立维护。假如项目的结构、依赖项或者配置有变动,只需要更新项目模板即可。
参照vue-cli的思路,我也将项目模板独立发布到git上,然后通过脚手架工具下载下来,经过与脚手架的交互获取新项目的信息,并将交互的输入作为元信息渲染项目模板,最终得到项目的基础结构。
工程结构
工程基于 nodejs 8.4 以及 ES6 进行开发,目录结构如下
/bin # ------ 命令执行文件 /lib # ------ 工具模块 package.json
下面的部分代码需要你先对 Promise 有一定的了解才更好的理解。
使用commander.js开发命令行工具
nodejs内置了对命令行操作的支持,node工程下 package.json 中的 bin 字段可以定义命令名和关联的执行文件。
{ "name": "macaw-cli", "version": "1.0.0", "description": "我的cli", "bin": { "macaw": "./bin/macaw.js" } }
经过这样配置的nodejs项目,在使用 -g 选项进行全局安装的时候,会自动在系统的 [prefix]/bin 目录下创建相应的符号链接(symlink)关联到执行文件。如果是本地安装,这个符号链接会生成在 ./node_modules/.bin 目录下。这样做的好处是可以直接在终端中像执行命令一样执行nodejs文件。关于 prefix ,可以通过 npm config get prefix 获取。
hello, commander.js
在bin目录下创建一个macaw.js文件,用于处理命令行的逻辑。
touch ./bin/macaw.js
接下来就要用到github上一位神级人物——tj ——开发的模块commander.js 。commander.js可以自动的解析命令和参数,合并多选项,处理短参,等等,功能强大,上手简单。具体的使用方法可以参见项目的README。
在 macaw.js 中编写命令行的入口逻辑
#!/usr/bin/env node const program = require('commander') // npm i commander -D program.version('1.0.0') .usage('<command> [项目名称]') .command('hello', 'hello') .parse(process.argv)
接着,在 bin 目录下创建 macaw-hello.js ,放一个打印语句
touch ./bin/macaw-hello.js echo "console.log('hello, commander')" > ./bin/macaw-hello.js
这样,通过node命令测试一下
node ./bin/macaw.js hello
不出意外,可以在终端上看到一句话:hello, commander。
commander支持 ,可以根据子命令自动引导到以特定格式命名的命令执行文件,文件名的格式是 [command]-[subcommand] ,例如:
macaw hello => macaw-hello
macaw init => macaw-init
定义init子命令
我们需要通过一个命令来新建项目,按照常用的一些名词,我们可以定义一个名为 init 的子命令。
对 bin/macaw.js 做一些改动。
const program = require('commander') program.version('1.0.0') .usage('<command> [项目名称]') .command('init', '创建新项目') .parse(process.argv)
在bin目录下创建一个 init 命令关联的执行文件
touch ./bin/macaw-init.js
添加如下代码