通过本篇你可以了解到:

  • 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.jsonif (isFile(pkgfile)) {var body = readFileSync(pkgfile, 'utf8');//读取成utf-8的格式try {var pkg = JSON.parse(body);//解析成jsonif (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中模块的加载机制。

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

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

  1. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  2. 【Python】Pandas在数据库中执行SQL语句并加载结果

    提示 建议采用 try-except-finally- 或者 try-finally- ,保证与数据库的连接被关闭. SQL语句入门很简单 (精深很难) ,不会的自己去简单学学. 建库建表 注意con ...

  3. 编写一个USB接口程序,模拟计算机启动过程和关闭过程启动过程中要加载鼠标、键盘、麦克风等USB设备,具体要求如下: (1)定义一个接口USB,包含两个抽象方法turnOn()he turnOff(),

    一.好物推荐 给大家推荐三款蓝牙耳机,下面的链接可以直接购买: 1.https://item.taobao.com/item.htm?ft=t&id=643733003968 2.https: ...

  4. js 和jQuery(自动执行函数)立即执行函数和页面加载完后执行函数写法

    js 立即执行函数的写法. js 立即执行函数只能用于匿名函数,如果声明了函数名是不可以用立即执行的,通常在函数表达式后加一对小括号()用于立即执行 如果想让函数不被调用的情况下,立即自动执行,需要在 ...

  5. 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理

    上一篇 唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理 在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化.hook系统ClassLoader.插件的 ...

  6. webpack 热加载原理探索

    前言 在使用 dora 作为本地 server 开发一个 React 组件的时候,默认使用了 hmr 插件.每次修改代码后页面直接更新,不需要手动 F5 ,感觉非常惊艳,这体验一旦用上后再也回不去了. ...

  7. 老调重弹:JDBC系列之驱动加载原理全面解析)

    前言 最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读 ...

  8. 老调重弹:JDBC系列 之 驱动加载原理全面解析

    前言 最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读 ...

  9. 一文了解 Java 中 so 文件的加载原理

    前言 无论是 Android 开发者还是 Java 工程师应该都有使用过 JNI 开发,但对于 JVM 如何加载 so.Android 系统如何加载 so,可能鲜有时间了解. 本文通过代码.流程解释, ...

  10. spring.factories加载原理以及自定义EnvironmentPostProcessor

    目录 spring.factories加载原理 1. SpringApplication的构造方法 1.1 SpringApplication#getSpringFactoriesInstances ...

最新文章

  1. url传递html字符串,将Selenium HTML字符串传递给Scrapy以将url添加到Scrapy的url列表中...
  2. 304不锈钢蒸玉米后一层黑色
  3. pyqt5必须和python对应_python 使用PyQt5
  4. 玩物得志Java笔试题_代码规范利器-CheckStyle
  5. 《Python Cookbook 3rd》笔记(5.20):与串行端口的数据通信
  6. App-V轻量级应用程序虚拟化之三客户端测试
  7. 什么是 SAP BAPI
  8. oracle 取awr报告,Oracle生成awr报告
  9. c语言教材1-8章参考答案,C语言课后习题参考答案(第1-8章)
  10. 基于STM32-F401的平衡小车
  11. JAVA 16位ID生成工具类含16位不重复的随机数数字+大小写
  12. linux菜刀使用教程,中国菜刀的使用教程
  13. Luogu_P3258 松鼠的新家
  14. pythonascii怎么转换字符串_Python 十六进制整数与ASCii编码字符串相互转换方法
  15. firefox火狐能打开http网址,针对所有https网址无法打开
  16. C语言学生综合测评系统
  17. mybatis plus 条件筛选数据
  18. mysql账号密码忘_MySQL账号密码忘记解决方法
  19. 笔记—力学导论(上)
  20. 学计算机大学生买什么牌子电脑,大学生买手提电脑什么牌子好

热门文章

  1. Python:Matplotlib 画曲线和柱状图(Code)
  2. C++的Android接口---配置NDK
  3. 僧多粥少?还原 OpenStack 的真实“钱景”
  4. 软件开发的MVC构架
  5. 扬尘监测系统_工地扬尘监测_工地扬尘监测解决方案
  6. SolrCloud7.4(Jetty容器)+mysql oracle 部署与应用
  7. Android 调用系统邮件,发送邮件到指定邮箱
  8. java流行的测试框架调研+市面上书籍调研
  9. wine应用运行后字体都是问号
  10. matlab数字滤波器设计函数汇总(转载)