avatar

仿vue-cli搭建属于自己的脚手架

什么是脚手架

脚手架就是个工具,方便我们新建项目用的工具,有了这个项目我们就能直接通过几行命令, 再通过配置就可以开发了

想想我们平时开发一个新项目, 基本上我们可以用 git clone url 来新建(复制)项目, 或者这个复制粘贴整个文件夹

首先要基本了解vue-cli(@vue/cli)是怎么工作的

脚手架的本质也是从远程下载一个模板来进行新的项目, 所以, 有什么不同呢? 就高大上啊😳, 当然不止于此, 脚手架是高级版的克隆, 它提供了交互式的命令让我们可以动态的更改模板/其他配置项, 然后用命令就可以搞定了

前置知识

commander

这是用来编写指令和处理命令行的,具体用法如下

1
2
3
4
5
6
7
8
9
10
const program = require("commander");
// 定义指令
program
.version('0.0.1')
.command('init', 'Generate a new project from a template')
.action(() => {
// 回调函数
})
// 解析命令行参数
program.parse(process.argv);

回忆一下,我们曾用过的 vue init/create 命令就是这样声明的。

inquirer

强大的交互式命令行工具,具体用法如下

1
2
3
4
5
6
7
8
const inquirer = require('inquirer');
inquirer
.prompt([
// 一些交互式的问题
])
.then(answers => {
// 回调函数,answers 就是用户输入的内容,是个对象
});

想象一下我们用 vue init webpack project-name 之后是不是会有几个交互问题,问你文件名啊、作者啊、描述啊、要不要用 eslint 啊等等之类的,就是用这个来写的。

chalk

这是用来修改控制台输出内容样式的,比如颜色啊,具体用法如下:

1
2
3
const chalk = require('chalk');
console.log(chalk.green('success'));
console.log(chalk.red('error'));

ora

这是一个好看的加载,就是你下载的时候会有个转圈圈的那种效果,用法如下:

1
2
3
const ora = require('ora')
let spinner = ora('downloading template ...')
spinner.start()

download-git-repo

看名字很明显了,这是用来下载远程模板的,支持 GitHub、 GitLab 和 Bitbucket 等,用法如下

1
2
const download = require('download-git-repo')
download(repository, destination, options, callback)

其中 repository 是远程仓库地址;destination 是存放下载的文件路径,也可以直接写文件名,默认就是当前目录;options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载。

项目搭建

有了上面的知识储备后, 我们就正式开始撸代码了

  1. 首先我们创建一个文件夹, 取名为 demo-cli
  2. 在改目录下执行 npm init -y 命令, 在生成的 package.json 文件中写入以下依赖并执行 npm install 安装, 如下
1
2
3
4
5
6
7
"dependencies": {
"chalk": "^2.4.2",
"commander": "^2.19.0",
"download-git-repo": "^1.1.0",
"inquirer": "^6.2.2",
"ora": "^3.2.0"
}
  1. 新建一个 bin 文件夹, 在 bin 文件夹下新建一个无后缀名的 demo 文件, 并写上:
1
2
#!/usr/bin/env node
console.log('hello');

这个文件就是我们整个脚手架的入口文件, 我们用 node ./bin/demo 运行一下, 就可以看到在控制台打印出 hello

这里要注意开头的 #!/usr/bin/env node 这个语句必须加上,主要是为了让系统看到这一行的时候,会沿着该路径去查找 node 并执行,主要是为了兼容 Mac ,确保可执行。

bin 目录初始化

当前,bin 目录下就只有一个文件,就是入口文件 demo。所以现在我们先来编写这个文件,由于内容较少,我们直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env node
const program = require('commander');

program
.version(require('../package').version, '-V, --version') // 定义当前版本
.usage('<command> [options]') // 定义使用方法
.command('add', 'add a new template') // 定义四个指令
.command('delete', 'delete a template')
.command('list', 'list all the template')
.command('init', 'generate a new project from a template')

// 解析命令行参数
program.parse(process.argv)

这个文件的主要作用就是定义指令,现在我们用 node ./bin/demo 运行一下,就能看到如下结果:

commander

当然,你可能会觉得每次输入 node ./bin/demo 这个命令有点麻烦,没关系,我们可以在 package.json 里面写入已下内容:

1
2
3
4
// bin 用来指定每个命令所对应的可执行文件的位置
"bin": {
"demo": "bin/demo"
}

然后在根目录下执行 npm link(就是把命令挂载到全局的意思),这样我们每次只要输入 demo,就可以直接运行了,so cool

是不是好像有点样子了呢😁😁😁,那就让我们继续完善下 bin 目录吧!ok,让我们在 bin 目录下再新建四个文件,分别对应上面的四个指令,然后分别处理四个指令要做的事情

同样的,我们修改一下 package.json 里面的 bin 内容,如下:

1
2
3
4
5
6
7
"bin": {
"demo": "bin/demo",
"demo-add": "bin/demo-add",
"demo-delete": "bin/demo-delete",
"demo-list": "bin/demo-list",
"demo-init": "bin/demo-init"
}

然后执行 npm unlink 解绑全局命令,再执行 npm link 重新把命令绑定到全局

最后顺便在根目录下新建一个 template.json 文件,里面的内容就是一个 {}。

编写具体指令

好了,一切准备就绪,接下来就让我们来写下具体的四个指令吧。

demo-add

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env node

// 交互式命令行
const inquirer = require('inquirer')
// 修改控制台字符串的样式
const chalk = require('chalk')
// node 内置文件模块
const fs = require('fs')
// 读取根目录下的 template.json
const tplObj = require(`${__dirname}/../template`)

// 自定义交互式命令行的问题及简单的校验
let question = [
{
name: "name",
type: 'input',
message: "请输入模板名称",
validate (val) {
if (val === '') {
return 'Name is required!'
} else if (tplObj[val]) {
return 'Template has already existed!'
} else {
return true
}
}
},
{
name: "url",
type: 'input',
message: "请输入模板地址",
validate (val) {
if (val === '') return 'The url is required!'
return true
}
}
]

inquirer
.prompt(question).then(answers => {
// answers 就是用户输入的内容,是个对象
let { name, url } = answers;
// 过滤 unicode 字符
tplObj[name] = url.replace(/[\u0000-\u0019]/g, '')
// 把模板信息写入 template.json 文件中
fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
if (err) console.log(err)
console.log('\n')
console.log(chalk.green('Added successfully!\n'))
console.log(chalk.grey('The latest template list is: \n'))
console.log(tplObj)
console.log('\n')
})
})
文章作者: Kwin
文章链接: http://kw1n.cn/2020/03/25/20191006-仿vue-cli搭建属于自己的脚手架/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Kwin 's Blog
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论