Vue-cli 的 create 指令是如何创建项目文件的
Vue-cli 是创建 Vue 项目的一个好方法,之前只是使用,没有关注过内部结构是咋样的。最近在做一个组内项目的 cli 工具,参考了一下 Vue-cli 的实现方法。
Vue-cli 项目结构
Vue-cli,是一个多 package 项目,使用 lerna 进行管理。package 内的 @vue 文件夹下有很多包:
其中与创建项目有关的包,我们主要需要关注 cli 和 cli-service。
@vue/cli
@vue/cli 包,就是我们平时执行的 bin 文件,入口是 @vue/cli/bin/vue.js。该文件会先检查用户当前使用的 node.js 版本是否符合要求。
之后使用 commander.js来提供命令行指令接口。
1 | program |
commander 会帮助收集选项参数等内容,之后调用了
1 | require('../lib/create')(name, options) |
可以看到最终项目创建逻辑是在 ../lib/create
中执行的。
../lib/create
create.js 文件主要做了以下几件事:
- 检查项目初始化名称是否合法。
- 检查项目初始化目录是否存在,若存在提示用户选择覆盖、合并、取消进行操作。
- 初始化 Creator 类,并执行实例的
.create
方法。
Creator 类
我们直接来看一下 Creator 类的 create 方法,该方法主要做了以下事情:
初始化 preset
preset 用于指定初始化项目的具体配置,例如是否使用 babel、typescript 等。preset 可以通过执行命令行工具时指定,未指定时会在命令行中提供选项让使用者选择。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19if (!preset) {
if (cliOptions.preset) {
// vue create foo --preset bar
preset = await this.resolvePreset(cliOptions.preset, cliOptions.clone)
} else if (cliOptions.default) {
// vue create foo --default
preset = defaults.presets.default
} else if (cliOptions.inlinePreset) {
// vue create foo --inlinePreset {...}
try {
preset = JSON.parse(cliOptions.inlinePreset)
} catch (e) {
error(`CLI inline preset is not valid JSON: ${cliOptions.inlinePreset}`)
exit(1)
}
} else {
preset = await this.promptAndResolvePreset()
}
}之后会在 preset 中添加 @vue/cli-service 插件,该插件用于创建初始化项目的基础内容。
1
2
3
4
5
6// inject core service
preset.plugins['@vue/cli-service'] = Object.assign({
projectName: name
}, preset, {
bare: cliOptions.bare
})根据命令行选项,初始化git。
安装所有插件。
preset 初始化之后,就会生成 package.json 文件,所有插件作为依赖放在 package.json 中,使用 npm 或 yarn 来进行安装。
初始化 Generator 类,并执行实例的 generate 方法。
Generator 类用于提供插件的加载机制。加载所有插件的内容,放入初始化的项目中。我们之后会说。
插件内容加载完毕后,会生成新的 package.json 依赖,此时需要再次进行安装。
生成 README.md 文件。
进行第一次 git 提交。
命令行中打印初始化完成提示信息。
Generator 类
Generator 类是 Vue-cli 提供的与插件之间交流机制,编写插件时可利用这个机制,进行文件、package.json 依赖的修改,具体可见官方文档:https://cli.vuejs.org/dev-guide/plugin-dev.html#generator
它是怎么工作的呢?Generator 类初始化时,会给每个插件初始化一个 GeneratorAPI 类,作为插件与 cli 之间的接口。
1 | // apply generators from plugins |
之后在 .generate 方法调用时,调用了自身的 .resolveFiles 方法,来获取所有的文件内容。
1 | // wait for file resolve |
文件内容获取后,进行 package.json 的写入,以及模板文件的写入:
1 | this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n' |