grunt源码解析:整体运行机制&grunt-cli源码解析

前端的童鞋对grunt应该不陌生,前面也陆陆续续的写了几篇grunt入门的文章。本篇文章会更进一步,对grunt的源码进行分析。文章大体内容内容如下:

  1. grunt整体设计概览
  2. grunt-cli源码分析
  3. grunt-cli模块概览
  4. grunt-cli源码分析
  5. 写在后面
    Alt text

grunt整体设计概览

grunt主要由三部分组成。其中,grunt-cli是本文的讲解重点

  1. grunt-cli:命令行工具,调用本地安装的grunt来运行任务,全局安装。
  2. grunt:本地grunt,一般安装在项目根目录下。主要作用是读取插件配置,调用指定的grunt插件。
  3. grunt插件:完成具体的构建任务。

grunt运行机制

抛开代码细节,grunt运行的整体流程如下。接下来,我们就会对红框部分,grunt-cli的源码进行进一步的探讨。
Alt text

grunt-cli源码分析

grunt-cli/packge.json里可以看到,主入口文件是 bin/grunt。接下来,我们就分析下bin/grunt.js的源码。

"bin": {
    "grunt": "bin/grunt"
},

grunt-cli源码概览

同样,我们先通过一个简单的流程图看下这个文件究竟做了什么事情。从下图可以看到,主要包含三个逻辑分支。

  1. 运行任务:主要的分支,用绿色标出,比如运行grunt copy就会进入这个分支
  2. 信息查看:右侧的一堆if/else分支,如运行grunt --version就会进入这个分支(还有其他如help等没列出来)
  3. 异常处理:非上面两种情况,异常退出。

Alt text

查看信息

如果命令行中包含--completion--version

  • 如果有--completion,输出自动完成信息,程序退出
  • 如果有--version,输出版本信息,程序退出

运行任务

  1. 命令行中是否有--gruntfile--base
    • 都有:优先将basedir设置为gruntfile指定的路径
    • 只有gruntfilebase:将basedir设置为basegruntfile指定的路径
  2. 根据basedir找到当前项目的grunt,并运行指定任务

异常处理

如果没有包含--completion--version,当前项目下也没有安装grunt,异常退出。

grunt-cli模块概览

下图为grunt-cli的整体目录机构,相对比较简单。这里做下介绍

  1. bin/grunt:主入口文件,调用其他模块来完成工作。
  2. lib/*:被bin/grunt调用,主要有如下几个模块
    • cli.js:分析、处理命令行参数。
    • completion.js:处理自动完成信息。
    • info:打印版本、帮助信息等。

Alt text

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的源码进行剖析,敬请期待。如有错漏,请指出。有任何疑问也可以在下面留言 :)

posted @ 2014-06-28 17:23  程序猿小卡  阅读(1600)  评论(0编辑  收藏