grunt源码解析:整体运行机制&grunt-cli源码解析
前端的童鞋对grunt
应该不陌生,前面也陆陆续续的写了几篇grunt入门的文章。本篇文章会更进一步,对grunt的源码进行分析。文章大体内容内容如下:
- grunt整体设计概览
- grunt-cli源码分析
- grunt-cli模块概览
- grunt-cli源码分析
- 写在后面
grunt整体设计概览
grunt
主要由三部分组成。其中,grunt-cli
是本文的讲解重点
grunt-cli
:命令行工具,调用本地安装的grunt
来运行任务,全局安装。grunt
:本地grunt,一般安装在项目根目录下。主要作用是读取插件配置,调用指定的grunt插件。- grunt插件:完成具体的构建任务。
grunt运行机制
抛开代码细节,grunt运行的整体流程如下。接下来,我们就会对红框部分,grunt-cli
的源码进行进一步的探讨。
grunt-cli源码分析
从grunt-cli/packge.json
里可以看到,主入口文件是 bin/grunt
。接下来,我们就分析下bin/grunt.js
的源码。
"bin": {
"grunt": "bin/grunt"
},
grunt-cli源码概览
同样,我们先通过一个简单的流程图看下这个文件究竟做了什么事情。从下图可以看到,主要包含三个逻辑分支。
- 运行任务:主要的分支,用绿色标出,比如运行
grunt copy
就会进入这个分支 - 信息查看:右侧的一堆
if/else
分支,如运行grunt --version
就会进入这个分支(还有其他如help
等没列出来) - 异常处理:非上面两种情况,异常退出。
查看信息
如果命令行中包含--completion
或 --version
- 如果有
--completion
,输出自动完成信息,程序退出 - 如果有
--version
,输出版本信息,程序退出
运行任务
- 命令行中是否有
--gruntfile
、--base
- 都有:优先将
basedir
设置为gruntfile
指定的路径 - 只有
gruntfile
或base
:将basedir
设置为base
或gruntfile
指定的路径
- 都有:优先将
- 根据
basedir
找到当前项目的grunt
,并运行指定任务
异常处理
如果没有包含--completion
、--version
,当前项目下也没有安装grunt
,异常退出。
grunt-cli
模块概览
下图为grunt-cli
的整体目录机构,相对比较简单。这里做下介绍
- bin/grunt:主入口文件,调用其他模块来完成工作。
- lib/*:被
bin/grunt
调用,主要有如下几个模块cli.js
:分析、处理命令行参数。completion.js
:处理自动完成信息。info
:打印版本、帮助信息等。
grunt-cli
源码分析
首先,引入组件的依赖模块
// Especially badass external libs.
var findup = require('findup-sync'); // 文件查找
var resolve = require('resolve').sync; // 路径解析
// Internal libs.
var options = require('../lib/cli').options; // 解析后的命令行参数
var completion = require('../lib/completion'); // 自动完成
var info = require('../lib/info'); // 版本信息等
var path = require('path');
var basedir = process.cwd();
var gruntpath;
然后,根据命令行参数,决定是否要打印版本信息、自动完成信息,还是设置当前的工作路径(basedir)
// Do stuff based on CLI options.
if ('completion' in options) {
completion.print(options.completion); // 打印自动完成信息,程序退出
} else if (options.version) {
info.version(); // 打印版本信息(在本地grunt里会退出)
} else if (options.base && !options.gruntfile) {
basedir = path.resolve(options.base); // 根据 base 设置 basedir
} else if (options.gruntfile) { // 根据 gruntfile 设置 basedir
basedir = path.resolve(path.dirname(options.gruntfile));
}
最后的考验,如果能找到本地grunt,就进入最后一步;如果找不到,很有可能就异常退出了。
try {
// 得到当前目录的本地grunt路径(grunt/lib/grunt.js)
gruntpath = resolve('grunt', {basedir: basedir}); // 得到的是类似这样的路径 /Users/usename/Documents/code/edu_proj/trunk/htdocs/ke.qq.com/node_modules/grunt/lib/grunt.js
} catch (ex) {
gruntpath = findup('lib/grunt.js'); // 有可能当前在项目的子目录里,逐级往上查找,直到找到 grunt
// No grunt install found!
if (!gruntpath) { // 找不到grunt,悲剧了
if (options.version) { process.exit(); } // 打印版本信息,退出
if (options.help) { info.help(); } // 打印帮助信息
info.fatal('Unable to find local grunt.', 99); // 打印错误日志,并异常退出
}
}
恭喜,来到这一步,可以愉快地运行构建任务了。
// Everything looks good. Require local grunt and run it.
require(gruntpath).cli(); // 找到 本地grunt了,开始运行 .cli 是local grunt的方法
写在后面
本文对构建工具grunt
的整体设计、运行机制,以及grunt-cli的源码进行了较为深入的分析。后续会对本地grunt
的源码进行剖析,敬请期待。如有错漏,请指出。有任何疑问也可以在下面留言 :)
github博客:https://github.com/chyingp/blog
新浪微博:http://weibo.com/chyingp
站酷主页:http://www.zcool.com.cn/u/346408/