Grunt-cli的执行过程以及Grunt加载原理

通过本篇你可以了解到:

  • 1 grunt-cli的执行原理
  • 2 nodeJS中模块的加载过程

Grunt-cli原理

grunt-cli其实也是Node模块,它可以帮助我们在控制台中直接运行grunt命令。因此当你使用grunt的时候,往往都是先安装grunt-cli,再安装grunt

如果你使用的是npm install -g grunt-cli命令,那么安装地址如下:

windows:
C:\\Users\\neusoft\\AppData\\Roaming\\npm\\node_modules\\grunt-cli
linux:
/nodejs/node_modules/grunt-cli

在这里可以直接看到编译后的代码。

当执行grunt命令时,会默认先去全局的grunt-cli下找grunt-cli模块,而不会先走当前目录下的node_modulesgrunt-cli
加载相应的代码后,grunt-cli做了下面的工作:

  • 1 设置控制台的名称
  • 2 获取打开控制台的目录
  • 3 执行completion或者version或者help命令
  • 4 查找grunt,执行相应的命令
  • 5 调用grunt.cli(),继续分析参数,执行相应的任务

源码初探

首先Node的模块都会有一个特点,就是先去读取package.json,通过里面的main或者bin来确定主程序的位置,比如grunt-cli在package.json中可以看到主程序位于:

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

找到主程序,下面就看一下它都做了什么:

首先加载必备的模块:

// 与查找和路径解析有关
var findup = require('findup-sync');
var resolve = require('resolve').sync;

//供grunt-cli使用
var options = require('../lib/cli').options;
var completion = require('../lib/completion');
var info = require('../lib/info');

//操作路径
var path = require('path');

然后就是判断下当前的参数,比如如果输入grunt --version,则会同时输出grunt-cli和grunt的版本:

//根据参数的不同,操作不同
if ('completion' in options) {
  completion.print(options.completion);
} else if (options.version) {
  //如果是grunt --version,则进入到这个模块。
  //调用info的version方法
  info.version();
} else if (options.base && !options.gruntfile) {
  basedir = path.resolve(options.base);
} else if (options.gruntfile) {
  basedir = path.resolve(path.dirname(options.gruntfile));
}

其中,cli定义了当前指令参数的别名,没什么关键作用。
info则是相关的信息。

然后才是真正的核心代码!

查找grunt

var basedir = process.cwd();
var gruntpath;
...
try {
  console.log("寻找grunt");
  gruntpath = resolve('grunt', {basedir: basedir});
  console.log("找到grunt,位置在:"+gruntpath);
} catch (ex) {
  gruntpath = findup('lib/grunt.js');
  // No grunt install found!
  if (!gruntpath) {
    if (options.version) { process.exit(); }
    if (options.help) { info.help(); }
    info.fatal('Unable to find local grunt.', 99);
  }
}

可以看到它传入控制台开启的目录,即process.cwd();
然后通过resolve方法解析grunt的路径。

最后调用grunt.cli()方法

require(gruntpath).cli();

查找grunt

这部分内容,可以广泛的理解到其他的模块加载机制。
resolve是grunt-cli依赖的模块:

var core = require('./lib/core');
exports = module.exports = require('./lib/async');
exports.core = core;
exports.isCore = function (x) { return core[x] };
exports.sync = require('./lib/sync');

其中async为异步的加载方案,sync为同步的加载方案。看grunt-cli程序的最上面,可以发现grunt-cli是通过同步的方式查找grunt的。

sync就是标准的node模块了:

var core = require('./core');
var fs = require('fs');
var path = require('path');

module.exports = function (x, opts) {};

主要看看内部的加载机制吧!

首先判断加载的模块是否是核心模块:

if (core[x]) return x;

core其实是个判断方法:

module.exports = require('./core.json').reduce(function (acc, x) {
    acc[x] = true;//如果是核心模块,则返回该json。
    return acc;
}, {});

核心模块有下面这些:

[
    "assert",
    "buffer_ieee754",
    "buffer",
    "child_process",
    "cluster",
    "console",
    "constants",
    "crypto",
    "_debugger",
    "dgram",
    "dns",
    "domain",
    "events",
    "freelist",
    "fs",
    "http",
    "https",
    "_linklist",
    "module",
    "net",
    "os",
    "path",
    "punycode",
    "querystring",
    "readline",
    "repl",
    "stream",
    "string_decoder",
    "sys",
    "timers",
    "tls",
    "tty",
    "url",
    "util",
    "vm",
    "zlib"
]

回到sync.js中,继续定义了两个方法:

//判断是否为文件
var isFile = opts.isFile || function (file) {
        console.log("查询文件:"+file);
        try {
            var stat = fs.statSync(file)
        }catch (err) {
            if (err && err.code === 'ENOENT')
                return false
        }
        console.log("stat.isFile:"+stat.isFile());
        console.log("stat.isFIFO:"+stat.isFIFO());
        return stat.isFile() || stat.isFIFO();
    };

//定义加载的方法
var readFileSync = opts.readFileSync || fs.readFileSync;

//定义扩展策略,默认是添加.js,因此如果模块的名称为grunt.js,可以直接写成grunt
var extensions = opts.extensions || [ '.js' ];

//定义控制台开启的路径
var y = opts.basedir || path.dirname(require.cache[__filename].parent.filename);

至此,会得到两个变量:

  • y 代表控制台开启的路径,查找会从这个路径开始
  • x 加载模块的名称

然后根据文件名称判断加载的方式。加载的方式,主要包括两类:

  • 只传入模块的名称,则从当前路径逐级向上查找
  • 传入标准的路径,直接在该路径下查找
//匹配D:\\workspace\\searcher\\ui-dev\\node_modules\\grunt这种名称
if (x.match(/^(?:\.\.?\/|\/|([A-Za-z]:)?\\)/)) {
        var m = loadAsFileSync(path.resolve(y, x))
            || loadAsDirectorySync(path.resolve(y, x));
        if (m) return m;
    } else {
        var n = loadNodeModulesSync(x, y);
        if (n) return n;
    }
//还没找到就报错
throw new Error("Cannot find module '" + x + "'");

如果正常的使用grunt xxx的时候,就会进入loadNodeMudelsSync()方法中。

这个方法中使用了另一个关键的方法来获取加载的路径:

    function loadNodeModulesSync (x, start) {
        //从模块加载,start是当前目录
        var dirs = nodeModulesPathsSync(start);

        console.log("dirs:"+dirs);

        for (var i = 0; i < dirs.length; i++) {
            var dir = dirs[i];
            var m = loadAsFileSync(path.join( dir, '/', x));
            if (m) return m;
            var n = loadAsDirectorySync(path.join( dir, '/', x ));
            if (n) return n;
        }
    }

nodeModulesPathsSync方法可以分解目录,并返回加载模块的路径。
举个例子,如果我的路径是D:/a/b/c
那么会得到如下的数组:

D:/a/b/c/node_modules
D:/a/b/node_modules
D:/a/node_modules
D:/node_modules

执行的代码如下:

function nodeModulesPathsSync (start) {
        var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\/+/;//根据操作系统的类型,判断文件的分隔方法
        var parts = start.split(splitRe);//分解各个目录层次

        var dirs = [];
        for (var i = parts.length - 1; i >= 0; i--) {//从后往前,在每个路径上,添加node_modules目录,当做查找路径
            if (parts[i] === 'node_modules') continue;//如果该目录已经是node_modules,则跳过。
            var dir = path.join(
                path.join.apply(path, parts.slice(0, i + 1)),
                'node_modules'
            );

            if (!parts[0].match(/([A-Za-z]:)/)) {//如果是Linux系统,则开头加上/
                dir = '/' + dir;
            }
            dirs.push(dir);
        }
        return dirs.concat(opts.paths);
    }

获取到了加载的路径后,就一次执行加载方法。

如果是文件,则使用下面的方法加载,其实就是遍历一遍后缀数组,看看能不能找到:

function loadAsFileSync (x) {
        if (isFile(x)) {
            return x;
        }

        for (var i = 0; i < extensions.length; i++) {
            var file = x + extensions[i];
            if (isFile(file)) {
                return file;
            }
        }
    }

如果是目录,则尝试读取package.json,查找它的main参数,看看能不能直接找到主程序;如果找不到,则自动对 当前路径/index下进行查找。

//如果是目录
    function loadAsDirectorySync (x) {
        var pkgfile = path.join(x, '/package.json');//如果是目录,首先读取package.json
        if (isFile(pkgfile)) {
            var body = readFileSync(pkgfile, 'utf8');//读取成utf-8的格式
            try {
                var pkg = JSON.parse(body);//解析成json
                if (opts.packageFilter) {//暂时不知道这个参数时干嘛的!
                    pkg = opts.packageFilter(pkg, x);
                }
                //主要在这里,读取main参数,main参数指定了主程序的位置
                if (pkg.main) {
                    var m = loadAsFileSync(path.resolve(x, pkg.main));//如果main中指定的是文件,则直接加载
                    if (m) return m;
                    var n = loadAsDirectorySync(path.resolve(x, pkg.main));//如果main中指定的是目录,则继续循环
                    if (n) return n;
                }
            }
            catch (err) {}
        }
        //再找不到,则直接从当前目录下查找index文件
        return loadAsFileSync(path.join( x, '/index'));
    }

这样,就完成了模块的加载了。

结论

因此,如果你同时安装了本地的grunt-cli、grunt和全局的grunt-cli、grunt,就不会纳闷为什么grunt-cli执行的是全局的、而grunt执行的是当前目录下的node_modules中的。另外,也有助于你了解Node中模块的加载机制。

如果对你有帮助,就点个赞吧!如有异议,还请及时指点!

本文转自博客园xingoo的博客,原文链接:Grunt-cli的执行过程以及Grunt加载原理,如需转载请自行联系原博主。

时间: 2024-10-26 12:03:25

Grunt-cli的执行过程以及Grunt加载原理的相关文章

JDBC数据库连接过程及驱动加载与设计模式详解_java

首先要导入JDBC的jar包: 接下来,代码: Class.forName(xxx.xx.xx)返回的是一个类 Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段. JDBC连接数据库 • 创建一个以JDBC连接数据库的程序,包含7个步骤: 1.加载JDBC驱动程序: 在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的静态方法forName(String

hibernate-Hibernate 在同一个事务里执行插入之后懒加载查询问题

问题描述 Hibernate 在同一个事务里执行插入之后懒加载查询问题 这样一段代码:他们是在同一个事务下的 public class ObjectA{ private String id; private ObjectB objectB; ...get/set方法:}public void functionA(){ ObjectA a = new ObjectA(); dao.add(a); functionB(a.getId); dao.commit();}public void funct

jvm-JVM加载原理@求大神推荐

问题描述 JVM加载原理@求大神推荐 哪位大神可以推荐一下,我需要看一些什么资料可以了解一些jvm的加载原理!最近在学习java有没有什么好点的书呢 解决方案 jvm的话可以看看<深入理解Java虚拟机:JVM高级特性与最佳实践>,其他书就是<java编程思想>和<effective java>了.不过这些书都需要你有了java一定的编程经验后再看,初学的话还是看一些带"入门"词汇的就行了. 解决方案二: <java的编程思想>.<

解析苹果的官方例子LazyTableImages实现图片懒加载原理

解析苹果的官方例子LazyTableImages实现图片懒加载原理 首先在官网下载源码: https://developer.apple.com/library/ios/navigation/#section=Resource%20Types&topic=Sample%20Code 打开运行: 仔细观察你会发现,只有在滑动停止的时候才会加载图片,是在如下位置实现的: 以下是设计先进的地方: 下载图片是可以取消的: 总结: 实现起来很easy:)

WordPress实现HTML5预加载与加载原理

HTML5预加载原理 ,这里简单的写一下原理 同时也把原理放出,这个方法的关键在于link rel="xx"这里,rel属性在html5发生 了变化,所以可以使用它来实现预加载,这里贴出rel在html4.01和html5的值供参考. linkrel html5-rel HTML 4.01 与 HTML 5 之间的差异 已删除的值:appendix, chapter, contents, copyright, glossary, index, section, start, subse

php命令行(cli)模式下报require 加载路径错误的解决方法_php技巧

今天,同事突然告诉我,我写的一个做计划任务的php脚本执行总是不成功. 脚本本身很简单,里面只有包含了几个库文件并执行了一个函数,函数应该没有错误,这个函数在别处也调用过,没有问题.我在本地用浏览器访问页面,执行成功,看来没有问题,我有点怀疑是同事的计划任务的问题.我又打开命令行,用php直接执行脚本,这回报错了,是require 包含文件错误,看来是路径不对. 不明白是什么原因引起的,只是从报错来看是路径不对,我猜测是跟相对路径有关,于是把相对路径的全部改成绝对路径,再执行,问题解决. 在百度

Firmware 加载原理分析【转】

转自:http://blog.csdn.net/dxdxsmy/article/details/8669840   [-] 原理分析 实现机制 总结   前言                     前段时间移植 wifi 驱动到 Android 的内核上,发现 firmware 的加载始终出错,问了几个人,都不是很了解,没办法,只好自己研究一下. 原理分析     从本质上来说, firmware 需要做的事情包括两件: 1,  通知用户态程序,我需要下载 firmware 了: 2,  用户

开源博客QBlog开发者视频教程:模板机制加载原理解说(三)

前言: 感谢大伙的支持,同样对于本次视频的发布,也限量发送20份源码,秋色园10份,博客园10份. 秋色园在 开源博客下载 处留言,博客园在 秋色园QBlog高性能博客开放源码下载 限量下载1000次 处留言. 由于本节视频不好录,重复录制的次数比较多,花时间较长,因此发布也晚了些. 大伙在学习的时候,还是要配合"秋色园技术原理解析"这个系列看,因为视频只是针对重点讲解,不会覆盖系列.   内容简介: 1:秋色园QBlog 的皮肤界面的介绍 2:秋色园QBlog 的皮肤是如何被加载的-

Javascript在网页页面加载时的执行顺序

javascript|加载|网页|页面|执行 一.在HTML中嵌入Javasript的方法 直接在Javascript代码放在标记对<script>和</script>之间 由<script />标记的src属性制定外部的js文件 放在事件处理程序中,比如:<p >点击我</p> 作为URL的主体,这个URL使用特殊的Javascript:协议,比如:<a href="javascript:alert('我是由javascript