小强赵的个人站点

精进自己,服务他人

Vue-cli 源码解读

Vue-cli 是创建 Vue 项目的脚手架,但是不像 Angular 和 React 脚手架那样提供大而全的功能,而是让使用者通过学习其他开源工具(如 Express,webpack 等)来根据自己的项目定制脚手架。两种思路各有优劣,这里不做评判,关于这一点可以在知乎看 大漠穷秋 和 尤雨溪 的论战。由于 Vue-cli 脚手架的特点,我们需要深入了解 Vue-cli 的源码,才能更好的配置自己的脚手架,所以这篇文章来了。cli -> Command Line Interface for batch scripting.

用到了那些库?

如果你对前端的开源项目保持关注,那么通过阅读 package.json 中的依赖你就能大体猜到这个脚手架有什么功能,采用那个方案实现的。Vue-cli 一共用到 53 个库,我们挑几个说一下。

一眼瞥过去能看懂这些,还有什么能一眼看出来的欢迎补充。

目录

了解项目下的各个文件夹是干什么的,是一种读源码不错的开始:

${object-root}
    ├── bin  命令执行的入口文件,对应 package.json 的 bin 配置
    ├── docs 文档
    ├── lib  功能代码,提供 bin 所需的一切
    └── test 测试相关

其实最重要的就是 bin 和 lib 这两个文件夹了,之所以没有将代码全部写进 bin 我觉得有以下几点考虑(其实很多项目都是这么做的):

一、bin 中的每个文件会比较大;

二、bin 中的内容提供一个二级目录的功能,具体实现交给 lib 中的模块,你可以从 bin 中找到你关心的内容然后进入到 lib 中查看细节,而不用把全部代码都看一遍。

三、一些公用的配置和功能模块可以抽离出来,A 和 B 公用的部分放在 A 和 B 任何一方都是不合理的。

整理流程和关键点

读一个库要把它能干什么时刻放在心里,这样不会“因为走的太久,而忘记为什么出发”。

vue list

先从最简单的 vue-list 说起,调用 github api 提供的接口,然后拼字符串打印出来。显然这货在断网环境下是玩不转的。这这段并不是很长的源码里,我们可以 get 两个新技能:

官方提供了 6 套模板,帮助我们初始化不同类型(从技术栈的角度划分)的项目。

vue init

用法:

$ vue init <template-name> <project-name>

template-name 首先可以从官方提供的 6 套模板中选一套,也可以自定义一套模板,放在你的 github 上:

$ vue init <username/repo> <project-name>

如果你不想开源你的模板,还可可以放在本地:

$ vue init <~/fs/path/to-custom-template> <project-name>

上面这些就是 vue init 命令实现的功能,下面我们通过源码分析一下这些功能是怎么实现的。

首先是一段参数的定义 Usage,然后是一段帮助文档 Help,这些都不是重点我们直接跳过。从 Settings 开始进入重点,首先是处理模板和项目路径这两个输入值,然后判断项目路径是否存在,如果存在给出提示,并询问是否继续,代码就是下面这一段:

if (exists(to)) {
    inquirer.prompt([{
        type: 'confirm',
        message: inPlace
                ? 'Generate project in current directory?'
                : 'Target directory exists. Continue?',
        name: 'ok'
    }], function (answers) {
        if (answers.ok) {
            run()
        }
    })
} else {
    run()
}

然后判断模板的类型,如果是本地模板那么直接复制,这部分逻辑在上面用到的 run 函数中:

function run () {
    // check if template is local
    if (isLocalPath(template)) {
        ...
    }
    else {
        ...
    }
}

而 isLocalPath 函数就来自 lib 文件夹下的一个自定义模块中。如果不是本地模板那就是远程模板,进入到 else 分支中。进入到 else 后做的第一件事就是检验 Node 版本:

checkVersion(function () {
    // ...
})

因为如果 Node 版本不够高,官方的模板根本跑不起来,看到这里还是佩服作者的,如果只把文件给你下载到本地其实他的本质工作已经做完了,但是你跑不起来会报错,如果是小白用户卡在这里可能就过不去了。回调函数中放的是版本检测没问后才执行的代码。

经过上面一个检测 Node 版本的小插曲后我们回到正题,本地模板的对立面就是远程模板,远程模板又分两种,上面提到过,一种官方模板,一种自定义模板,区分他们的方式也很简单,就是看输入值是否有斜杠:

if (!hasSlash) {
    // 官方模板逻辑
}
else {
    // 自定义模板逻辑
}

下载官方模板前,对是否包含 “#” 和 “-2.0” 两个字符串做了判断,来识别是 vue 1.0 还是 vue 2.0。直接从远程下载内容到本地,这里面有个东西挺好玩 -- ora,命令行中的 loading。还有一个 download-git-repo,用来下载 github repository。这两个库在写一些工具的时候很有用。

到目前为止,远程的模板已经下载到了本地,但是并没有写入本地文件夹,而是在本地内存里悬着,具体为什么,我们在下一趴揭晓。

自定义模板

自定义模板就是把官方模板用到的那套模板引擎流程做了规范,主要的有这么几点:

上面这些不需要深入理解,只需要把官方模板复制一份出来,做简单的修改就可以。

说一下实现,vue cli 用了一个叫 metalsmith 的任务管理器,和 gulp 比较类似。然后依次执行下面任务:

这些操作在 lib/generate.js 中,关键代码摘录如下:

metalsmith.use(askQuestions(opts.prompts))
    .use(filterFiles(opts.filters))
    .use(renderTemplateFiles(opts.skipInterpolation))

渲染模板用的是 consolidate.handlebars,consolidate 是 TJ 大神开发的集成模板引擎,支持很多模板引擎:

也就是在一些复杂的项目中,不同类型的文件可以使用不同的模板,而 handlebars 是一个比较简单的模板引擎,用法大概是这样:

var render = require('consolidate').handlebars.render
render(fileStr, data, function (err, res) {
    // ...
})

整理用到的库

consolidate,集成模板引擎,。

chalk,命令行高亮。

commander,命令行辅助,TJ 大神的库。还有更简单的库 -- yargs。

inquirer,收集用户输入,支持单选,多选,文本输入,密码输入,更具前面输入判断是否展示当前项,校验,加工输入

download-git-repo,下载远程仓库。容易被忽略的一点就是带了 ssh 的功能,可以用来从私有库下载,回调函数执行的时候文件没有被保存到本地硬盘,可以在回调函数中加工下载文件。

metalsmith,构建静态网站的工具,每一个加工的工序就是一个插件。

参考

https://github.com/vuejs/vue-cli

https://github.com/vuejs-templates/webpack

http://blog.fens.me/nodejs-commander/

https://github.com/segmentio/metalsmith

https://github.com/tj/consolidate.js

https://github.com/wycats/handlebars.js/

相关文章

https://zhuanlan.zhihu.com/p/25000026

https://segmentfault.com/a/1190000009803941

https://segmentfault.com/q/1010000007948863

https://github.com/dwqs/blog/issues/56

遗留的问题

如何调试 nodejs commander? debug nodejs commander in vscode