vue-cli源码分析
—— 一步步实现自己的脚手架工具
基于版本2.9.3, vue-cli 3.x更改为与create-react-app类似的集成cli了,所以待有时间再研究
工作流程
根据自己的理解绘制流程图:
第三方依赖
- download-git-repo
从git仓库下载项目模板 - commander
组织和处理命令行工具,简化命令流程 - metalsmith
静态站点生成工具,插件化文件处理流程,插件化类似于express的中间件 - inquirer
实现交互式命令行 - handlebars
模板渲染引擎 - async
异步执行管理函数 - ora
在终端优雅地显示进度工具 - user-home
Node4.x之前没有获取用户目录的api,所以这个是用来兼容之前的,其实也可以不需要,因为node版本现在都已经很高了,用require("os").homedir()
代替 - tildify
将绝对路径转换为波浪路径,e.g./Users/sindresorhus/dev
->~/dev
- chalk
在终端打印出带有颜色的文字 - rimraf
实现unix commandrm -rf
- semver
npm语义版本,可用于比较、验证版本号 - request
简单的http请求,用于vue list 获取所有版本 - multimatch
扩展自minimatch.match()
,可匹配多个模式
如何让发布的npm包全局可执行
让你的npm包实现像vue-cli
一样,全局执行vue
命令,其实非常简单;下面简单实现一个小demo:
第一步 新建项目,初始化项目,编写的执行程序文件,存放为 项目根目录/bin/test
,注意文件名没有后缀。1
2
console.log('test');
第二步 添加npm bin配置1
2
3
4
5{
"bin": {
"test": "./bin/test"
}
}
第三步 link当前模块为全局模块
项目根目录执行以下命令1
$ npm link
实际开发时,将项目publish到npm包时,不需要该命令,这个只是为测试,将当前模块link为全局模块后,可执行全局test命令,现在在终端执行test会打印出test,证明成功了。
现在再去看源码时,就可以直接看vue-cli bin目录下的vue文件了。
——————–分割线—————————-
补充知识
npm包实现可执行命令的探究
windows上如果要让一个文件可执行,那么这个文件应该是.exe或者.cmd后缀,或者通过程序运行,比如:node test.js
;
linux上不根据扩展名来判断文件是否可执行,大家一定注意到上面的test.js文件第一行#!/usr/bin/env node
,其实这个就是用来表明该文件可以在linux下执行,并且指明用node程序执行。
那么为什么在项目package.json文件加上bin属性就可以变为可执行?
如何查看可执行文件存在位置
在命令行执行npm bin -g
即可查找到,比如我执行后的结果:
windows
因为在全局安装包时,npm会判断package.json是否有bin配置,如果存在,则会在npm包的环境变量设置目录下新建xxx.cmd文件和一个xxx文件(shell可执行文件,第一行为:#!/bin/sh
),xxx就是在bin属性下配置的属性值,比如上面例子中写的是test文件,可执行文件名就会是test,test.cmd文件内容如下:
1 | @IF EXIST "%~dp0\node.exe" ( |
%~dp0
在windows中代表该bat脚本文件所在目录路径,该程序意思就是查看当前目录下是否有node程序;如果有,则直接用当前目录下的node程序执行test文件;如果不存在,直接全局调用。
linux
在linux上因为test文件本身就是可执行的,所以与window上不同的是,npm会在linux的.nvm/versions/node/v9.11.1/bin
目录下新建一个软连接,指向安装目录下的bin目录下的可执行文件:
图片以pm2的软连接为例子。
介绍几个重要的第三方依赖库
1. commander
简化命令行操作,具体使用方法这里不做介绍,官网的解释已经很全面了。commander有两种使用方式:
第一种,action形式,如果匹配到对应的command就会执行对应的action
修改test文件为下面的内容:1
2
3
4
5
6
7
8
9
10
11
12
13
const program = require('commander');
program
.command('init <dir>')
.option('-c --clone', 'download template through git-clone')
.action((dir, cmd) => {
console.log(dir);
console.log(cmd.clone);
});
program.parse(process.argv);
执行test init /temp -c
,打印结果:
第二种,git-style 模式,可以将子命令分成模块
vue-cli采取的就是这种模式。现在修改test:1
2
3
4
5
6program
.version(require('../package.json').version)
.usage('<command> [options]')
.command('init', '生成一个新项目');
program.parse(process.argv);
增加test-init文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const program = require('commander');
program
.usage('<template-name> [project-name]')
.option('-c --clone', 'use git-clone');
/**
* 确保在命令行运行 test init 无参数时显示该命令的help提示
* help指令是commander自动生成的,如果想自定义,可以查看vue-cli源码35-44行
*/
function help () {
program.parse(process.argv);
if (program.args.length < 1) return program.help();
}
help();
执行test init
:
可以看到执行了test-init文件,这就是commander的sub-commander形式,test文件定义了都有什么命令(注意不可以有action),当匹配到相应的命令,但是没有匹配到action时就会去找当前文件下的test-命令
文件。
2. download-git-repo
支持的远程仓库:github、gitlab、bitbucket、自定义仓库
该模块整合了git-clone和download,同时支持执行git clone
命令和http协议下载两种方式。
有了这个模块我们就可以从远程下载我们提前做好的项目模板了。
使用方法:require('download-git-repo')(url, dest, option, callback)
- url:下载路径
格式:owner/name#my-branch
e.g.vuejs-templates/webpack
默认github仓库,如果要下在其他的,格式:gitlab:owner/name#my-branch
,自定义仓库:https://mygitlab.com:owner/name#my-branch
- dest:下载至dest目录
- option: clone 为true时,使用
git clone
下载,否则使用http download
- callback: 回调
现在,修改我们的test-init 文件:
1 |
|
这里,因为还没又自己的模板,我们用vue-cli的模板进行测试,执行test init vuejs-templates/webpack test-vue
,等待一会回发现控制台打印出下载完成。
根据以上几个步骤,如果没有模板渲染的需求,基本上就可以创作出最简版的cli工具了,但是工作中会遇到每个项目不同使用的编译环境或者依赖都不同,所以需要进一步通过命令行交互实现同一个模板用于不同项目。
补充
在看vue-cli的源码的时候,会发现vue-cli也提供了–clone的选项,但是如果我们在用vue的模板时加上–clone选项,会发现出现了报错:
主要原因:vuejs-templates/webpack的默认分支并不是master而是develop,这里需要查看download-git-repo的源码1
2
3
4
5
6
7
8
9
10// line 32-39
// repo.checkout默认为master,所以shallow默认为true
gitclone(url, dest, { checkout: repo.checkout, shallow: repo.checkout === 'master' }, function (err) {
if (err === undefined) {
rm(dest + '/.git')
fn()
} else {
fn(err)
}
})
进一步需要看下git-clone的源码,会发现当checkout有值时,会进行git checkout操作,并且shallow为true时,git clone 会添加–depth 1。
现在明白了,由于shallow为true,导致git clone 时只下载了最近一次commit,并且仅包含默认分支,而vuejs-templates/webpack的默认分支不是master,所以git-clone模块进行git checkout master报错。
这里,如果我们在下载自己的模板并且添加了–clone参数时需确保模板默认分支为master,当然,也可以自己写一个模块
inquirer
具体api请自行查阅官方文档,这里简单应用到我们自建的项目中
metalsmith