vue-cli源码学习1.x

前言

说起vue-cli源码的阅读,主要源于公司实习时候,对公司的前端脚手架比较感兴趣,然后就开始看了看,但是看的不是很明白。就准备先看看比较熟悉的vue-cli,而且vue-cli使用人数比较多,也有一些源码分析的文章,有助于代码的理解。

但其实看一遍别人的文章,感觉只是稍微明白了一点。于是,本着学习cli工具的开发思路,因此,从master分支的第一个提交开始看起,准备梳理一下整个cli工具的开发流程,以及作者的一些思考。

这篇文章将主要分析vue-cli 1.x版本的一些原理。

整体思路

  • bin/ 目录下面放命令文件,比如vuevue-initvue-list,注意这里没有加js后缀
  • lib/ 目录下面放一些公共的方法

其实vue-cli最初只提供了两种命令,分别是vue init初始化项目,以及vue list查看模板列表。命令行的控制则主要是引入commander包来控制。这里选项交互暂时没用到inquirer包,后面版本会用到。

bin目录写到package.json文件中作用是可直接命令行启动,即npm i -g后可直接使用vue init <package-name> <project-name>形式。

同时,为了方便调试,可以直接在目录下面npm link到全局,这样改动后可以直接看到效果。原理即创建软连接,使用npm unlink即可取消。

脚本文件调用

主文件vue如何根据用户输入选择执行不同脚本文件?

最开始使用child_process.spawn(脚本路径,参数)直接执行,如下

1
2
3
4
5
// 这里执行子进程,根据命令行输入,执行不同bin下文件
// 注意这里有一个执行权限问题,chmod 755 file
// 但是不太清楚如果发布以后,npm下载之后,是否可执行,如果权限是不可执行,那其它用户下载后又该如何解决?已解决,用commander特性
spawn(bin, args, { stdio: 'inherit' })
.on('close', process.exit.bind(process))

后来的提交直接使用了commander特性,如下所示,command加入第二个参数,就可以在目录下面寻找vue-init与vue-list文件执行,省了一大段调用nodeAPI获取输入参数,选择脚本路径与参数的代码。

1
2
3
4
5
6
require('commander')
.version(require('../package').version)
.usage('<command> [options]')
.command('init', 'generate a new project from a template')
.command('list', 'list available official templates')
.parse(process.argv)

vue init

vue init的思路很简单,就是预先写好一个模板,放到远程仓库中。然后当使用vue init选择不同模板构建时,则下载不同的模板。先将模板下载到/tmp下,然后再将其generate到项目位置,而不是直接将其下载到目标位置。

一些预设选项是通过下载下来的模板中meta.json中的选项来交互。版本目前暂时还不能init成功,据我推测是代码比较老,和template不太匹配的缘故。比较有意思的是作者最初使用的Khaos包来generate到目标位置,可能作者觉得这个太麻烦了,重新封装了一下这个包叫khaos-patched,一行代码就能实现功能。也有些借鉴意义。

这里有一个用法比较独特(之前没见过)

1
2
3
4
5
// 举例:如果返回-1,则 ~-1 为 0, 也就是说没有找到,即为false,加上!,即为 true,既没有找到就进入
// 如果返回其他值,则 ~number为 非0,即找到true,加上!即为false
if (!~template.indexOf('/')) {
template = 'vuejs-templates/' + template
}

算的上奇淫巧技吧,但是有意思的事,作者在后面的版本中修改了,变成了如下,可能是觉得可读性不太好?

1
2
3
4
var hasSlash = template.indexOf('/') > -1
if (!hasSlash) {
template = 'vuejs-templates/' + template
}

vue list

vue list实现思路很简单,直接发请求获取一些模板列表,显示出来。这里就不多做解释了。

更新情况

2.0.x

依赖

完成功能

  • 使用 inquirer 和 metalsmith 代替 Khaos 和 prompt-for
  • 添加lint和test,以及重构项目,将Metalsmith所用插件方法单独抽离,放进lib
  • 使用ora代替lib/spinner.js
  • 添加vue-cli版本检查

1.4.x

完成功能

  • 添加lib/spinner.js下载动画

1.3.x

依赖

  • prompt-for-patched: 命令行问题及选择

完成功能

  • 添加-c参数,可直接下载任意git仓库
  • 添加没有指定project-name时,可将当前目录初始化功能

1.2.x

依赖

  • download-git-repo: 下载github仓库

完成功能

  • 使用download-git-repo替换download-github-repo

1.1.x

依赖

  • khaos-patched: yyx自己基于khaos写的

完成功能

  • 将khaos替换为khaos-patched
  • 添加lib/git-user.js获取作者

1.0.x

依赖

  • commander: 命令行交互
  • chalk: 命令行高亮
  • cross-spawn: Node子进程
  • download-github-repo: 下载github上仓库
  • khaos: 生成项目
  • rimraf: rm -rf
  • uid: 生成一串随机数

完成功能

  • 构建项目主框架,完成vue init功能和vue list功能,使用子进程切换执行脚本
  • 抽离lig/logger.js信息输出
  • 使用commander的方法,删除大量vue脚本代码
  • 增加目录下是否已有模板判断

总结

当然,目前只是看了最初的几次提交,1.x版本还有好多次提交,代码结构比较简单,后面会愈发复杂。同时上述原理只是1.x的前期版本,不包括2.x以及3.x版本。特别是3.x有了较大的改进。

———————-2019.8.3—————————-

1.x版本已经看完了,2.x版本也已经看完了。看完后觉得还是非常清晰地,当然和3.x版本相比,之前的版本还是有比较大的去别的。1.x和2.x版本总体上来讲,还是从远程仓库下载模板,可配置性虽然有但还是不够灵活,而且扩展性非常不好。3.x版本的最大特点就是插件式引入,而且开发者也可以直接开发自己的插件,更加灵活。