沐光

记录在前端之路的点点滴滴

Node 简单脚手架构建

前言

近期组内有脚手架相关项目的开发搭建,因为先前用过 vue-clicreate-react-app 等相关脚手架工具,对其实现的方式还是挺感兴趣的,因此趁此机会学习了解一番组内的脚手架工具的搭建,写篇文章小记一下~。

搭建前的准备

脚手架运作原理

我们时常会在 ~/.bashrc 内注册自己的 alias 命令,如:

1
alias goCode="cd ~/Code"

然后 source ~/.bashrc 生效后,直接在控制台输入 goCode 就能够跳转至对应文件夹内了。这个指令就相当于是一个软链,告诉系统我调用它时,触发的就是其记录的代码。

Node 脚手架的运行其实也很类似,首先需要了解的一点就是 package.jsonbin 字段,在安装对应的 npm 包时,它会告诉系统 bin 内注册的指令就是调用我这个包的指令(相当于软链),在命令行使用对应字段时就能够调用此包内的命令了。

开发需要的包

交互包
  • commander:可以自动的解析命令和参数,用于处理用户输入的命令;
  • inquirer:通用的命令行用户界面集合,用于和用户进行交互;
样式包
  • chalk:可以给终端的字体加上颜色;
  • cfonts:可以添加炫酷的标题;
  • ora:下载过程久的话,可以用于显示下载中的动画效果;
模板包
  • download-git-repo:下载并提取 git 仓库,用于下载项目模板;
  • ejs:模板引擎,将用户提交的信息动态填充到文件中;

项目搭建

初始化仓库结构

在准备步骤已经说过,此需要配合使用到 package.jsonbin 字段,参考 @vue/cli 的项目结构,初始化项目的结构如下:

init

然后在该项目内执行 npm link 挂载在系统全局的 npm 包列表内,就能够调用 myInit 指令了(控制台会打印出 “init” 内容)。

取消挂在命令为: npm unlink(仍然是项目内使用)

添加交互

现在能够调用自己创建的指令了,接下来就需要添加一些交互信息,比如最为基础的 myInit --help 指令,获取全量指令列表,那么我们先需要安装 commander 包来处理用户提示。

此包的使用方式很简单,按照一下模板开发即可:

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
28
29
30
31
32
/** 初始化实例 */
const { Command } = require('commander');
const program = new Command();

/** 修改通用配置 */
// Head Line Tips
program.name('myInit').usage('<command> [options]');

// Version Options
const currentVersion = require('../package.json').version;
program.version(currentVersion, '-v, --version', 'output the current version');

// Help Options
program.helpOption('-h, --help', 'output usage information');

/** 添加自定义 Command 指令 */
program
.command('create <projectName> [opts]')
.description('Create a program use remote git model')
.option('-i, --inhert', 'create an existed project')
.action((name, opts) => {
console.log(`The project name is "${name}", and the option is ${opts}`);
});

/** 触发调用和默认信息提示 */
// Default value is "process.argv"
program.parse();

// None Match
if (!program.args.length) {
program.help();
}

将通用配置部分按照自己所需配置好后,后面只需要扩充 Command 指令就能够实现控制台交互的效果了。

添加选项交互

如果需要像 @vue/cli 创建项目的交互那样,添加可选择的内容,仅仅依靠 commander 是实现不了的,这时候需要配合使用 inquirer 来做到页面交互。

inquirer 的示例也很清晰明了,可以直接看 github 文档,各种类型均有所介绍,常用的类型包括:listcheckboxinputlist 等。写法如下:

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
const inquirer = require('inquirer');

inquirer
.prompt([
{
type: 'input',
name: 'first_name',
message: "What's your first name",
},
{
type: 'input',
name: 'last_name',
message: "And what's your last name",
},
{
type: 'list',
name: 'sex',
message: 'Are you a girl or boy',
choices: ['boy', 'girl'],
},
])
.then(answers => {
// 结果: { first_name: '', last_name: '', sex: '' }
console.log(answers);
});

inqurer 部分的内容一般放在 Commanderaction 的结果会调部分,用于更精确的处理。

添加模板下载方法

在交互完成后,可以像 @vue/cli 那样直接从远程将对应模板给下载下来,当然,下载下来的模板如果能配合先前的 inquirer 做自定义配置则会更好,简单弄的话直接拷贝一个模板仓库就差不多完事了,最多再调用一下 bash 指令做一下包的安装,简化一下用户操作,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// download-vue.js
const download = require('download-git-repo');

module.exports = () => {
return new Promise((res, rej) => {
download(
'kazehaiya/vue-typescript-components',
'repo/demo',
function (err) {
err ? rej('下载失败') : res('下载成功');
},
);
});
};

之后在 commanderactions 操作中调用该文件即可。

优化样式

基本的控制台交互按照上述两步走基本就 OK 了,剩下的如:控制台颜色配置、标题配置、加载中状态处理 等样式部分可以慢慢优化,代码内用的比较全的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
program
.command('download')
.description('Download Vue Template')
.action(async () => {
const spinner = ora();

try {
spinner.start(chalk.blue('模板下载中……'));
await require('../lib/download-vue')();
} catch {
spinner.fail(chalk.red('下载失败'));
} finally {
spinner.succeed(chalk.green('下载完成'));
}
});

适当的时候使用 chalk 调一下控制台颜色,让输出的内容更加鲜明,然后增加一下加载效果,整体的用户体验就一下子上来了。最后如果需要的话,可以用 cfonts 弄一个炫酷的标题,可能会有不一样的感受:

image-20210628210744365

仓库地址

参考文章