一个项目脚手架开发过程全记录

公司内部不同类型的项目越来越多,有基于 jQuery 的,也有 Vue 的,还有小程序的,虽然已经有各种类型的项目模板,但每次有新项目,都要去手动拉取对应的项目模板,然后修改里面的配置,还得删掉一些演示代码,实在有些烦人,于是开发一个内部项目脚手架的想法孕育而生。

因为之前有了解过 vue-cli 的实现思路,它将项目模板和脚手架分离,模板存放在 git 上,然后在与用户交互的过程中下载不同的模板,这样模板可以单独维护,即便有更新,用户也不需要更新脚手架。

思路清晰了,下面就该动动手了。

准备依赖库

核心依赖库:

  • commander 解析命令行的命令和参数,用于处理用户输入的指令
  • inquirer 通用的命令行用户界面集合,用于和用户进行交互
  • download-git-repo 下载 git 仓库,用于下载项目模板
  • handlebars 模板引擎,用于将用户提交的信息填充到文件中
  • shelljs shell 命令执行,用于执行部分特殊指令

辅助依赖库:

  • ora 终端旋转器,用于展示 loading 提示
  • chalk 给终端字体加上颜色
  • log-symbols 给终端加上彩色符号

初始化

首先准备一个空目录,然后依次运行:

1
2
yarn init
yarn add commander inquirer download-git-repo handlebars shelljs ora chalk log-symbols

此时目录下就会生成好 package.json 文件,并且相关依赖也都安装好了。

然后打开 package.json 增加 bin 字段,并在里面定义我们的命令名和执行文件:

1
2
3
4
5
6
7
8
{
"name": "1one-project",
...
"bin": {
"create-1one-project": "index.js"
},
...
}

最后创建 index.js 文件,并在文件开头写上 #!/usr/bin/env node ,代表这个文件是 node 环境下的脚本文件。

处理命令行

准备工作做好后,先构思一下最终效果,我们的命令行不需要太多功能,只需要一个 init 即可,即 create-1one-project init 的指令,实现如下:

1
2
3
4
5
6
7
const program = require('commander');

program.command('init [name]')
.action((name) => {
console.log(name);
});
program.parse(process.argv);

这里我增加了一个可选的 name 参数,下面会用到它。

命令行交互

这部分就靠 inquirer 实现了,主要就是构建一套问答系统,收集交互过程中用户提交选择的信息。

因为在 gulp-automation 里有使用过,所以并不陌生,直接看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const inquirer = require('inquirer');

inquirer.prompt([
{
type: 'input',
message: '请输入项目名',
name: 'name',
default: name,
validate: function(val) {
if (val == '') {
return '项目必须要有名称噢';
}
if (fs.existsSync(val)) {
return '项目名已存在';
}
return true;
}
},
{
type: 'list',
message: '请选择项目类型',
name: 'type',
choices: ['jQuery', 'vue']
}
]).then(answers => {
console.log(answers);
});

下载项目模板

因为在上一步我做了项目类型的选择,也就代表需要根据用户选择下载不用的项目模板。

1
2
3
4
5
6
7
8
9
const download = require('download-git-repo');

download(answers.type == 'jQuery' ? 'hooray/jquery-project-template' ? 'hooray/vue-project-template', answers.name, err => {
if (err) {
console.log('项目创建失败');
} else {
console.log('项目创建成功');
}
});

download-git-repo 支持从 Github 、Gitlab 和 Bitbucket 去下载,具体用法请查看官方文档。

渲染项目模板

这里需要使用 handlebars 语法对模板中的 package.json 文件进行修改,当然不限于这个文件,这里只是抛砖引玉。

1
2
3
4
5
6
{
"name": "{{name}}",
"version": "1.0.0",
"description": "",
...
}

随后就是在模板下载成功后,将需要的信息渲染到 package.json 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
const handlebars = require('handlebars');
const fs = require('fs');

const packageFile = `${answers.name}/package.json`;
if (fs.existsSync(packageFile)) {
const content = fs
.readFileSync(packageFile)
.toString();
const result = handlebars.compile(content)({
name: answers.name
});
fs.writeFileSync(packageFile, result);
}

安装模板依赖包

项目模板下载好后,通常我们会手动进入项目目录,然后执行 yarn 或者 npm i 进行依赖包安装,既然一步操作少不了,那就把它也自动化了吧。

1
2
3
4
5
6
7
8
9
10
const shell = require('shelljs');

shell.cd(answers.name)
.exec('yarn', function(err) {
if (err) {
console.log(err)
} else {
console.log('依赖包安装成功')
}
})

测试

到这步为止,整个脚手架的功能已经齐全并且可以正常使用了,本地做测试的话,可以通过 node index.js init 的方式执行脚本命令。

确认脚手架使用没问题后,我们最后来优化一下界面。

视觉美化

Loading 美化

比如在下载、安装依赖都是属于比较耗时,并且时间无法预估的操作,所以增加一个 loading 提示是比较必要的。

1
2
3
4
5
6
7
8
9
const ora = require('ora');

// 开始下载
const spinner = ora('正在下载模板...');
spinner.start();
// 下载失败调用
spinner.fail();
// 下载成功调用
spinner.succeed();

文字颜色美化

成功绿色,失败红色。

1
2
3
4
const chalk = require('chalk');

console.log(chalk.green('项目创建成功'));
console.log(chalk.red('项目创建失败'));

符号美化

在文字颜色的基础上,还可以增加 × 的符号,会让用户更加具象的清楚提示信息。

1
2
3
4
5
const chalk = require('chalk');
const symbols = require('log-symbols');

console.log(symbols.success, chalk.green('项目创建成功'));
console.log(symbols.error, chalk.red('项目创建失败'));

发布

通过 yarn publish 把脚手架发布到 NPM 上吧。

完整代码

我就知道你们没什么耐心,想直接看完整代码,那就顺便介绍一下我司的脚手架工具。

目前提供了 gulp-automationvue-automation 两套模板,也是介绍过很多遍的东西了。

参考