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_modules
的grunt-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中模块的加载机制。
如果对你有帮助,就点个赞吧!如有异议,还请及时指点!
Grunt-cli的执行过程以及Grunt加载原理的更多相关文章
- jvm(1)类加载(一)(加载过程,双亲加载)
JVM类加载器机制与类加载过程 jvm虚拟机的种类: Hotspot(Oracle)(基本上都是在说这个) J9, JikesRVM(IBM) Zulu, Zing (Azul) Launcher是一 ...
- vue第七单元(vue的单文件组件形式-单文件组件的加载原理-vue-cli构建的开发环境以及生命周期)
第七单元(vue的单文件组件形式-单文件组件的加载原理-vue-cli构建的开发环境以及生命周期) #课程目标 掌握安装 vue-cli 命令行工具的方法,掌握使用命令行在本地搭建开发环境,使用命令行 ...
- 老调重弹:JDBC系列之<驱动加载原理全面解析) ----转
最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读者 ...
- 深入解析 composer 的自动加载原理 (转)
深入解析 composer 的自动加载原理 转自:https://segmentfault.com/a/1190000014948542 前言 PHP 自5.3的版本之后,已经重焕新生,命名空间.性状 ...
- Spring Boot源码分析-配置文件加载原理
在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...
- 从SpringBoot源码分析 配置文件的加载原理和优先级
本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级 跟入源码之前,先提一个问题: SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...
- 【Docker】7. 镜像-加载原理、分层原理、commit镜像
一.什么是镜像 镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件. 它包含运行某个软件所需的所有内容,包括代码.运行时环境.库.环境变量和配置文件. 所有的应用,直接 ...
- iOS 下拉刷新-上拉加载原理
前言 讲下拉刷新及上拉加载之前先给大家解释UIScrollView的几个属性 contentSize是UIScrollView可以滚动的区域. contentOfinset 苹果官方文档的解释是&qu ...
- Vue(基础七)_webpack(webpack异步加载原理)
---恢复内容开始--- 一.前言 1.webpack异步加载原理’ 2.webpack.ensure原理 ...
- Node.js require 模块加载原理 All In One
Node.js require 模块加载原理 All In One require 加载模块,搜索路径 "use strict"; /** * * @author xgqfrms ...
随机推荐
- c#发送http请求
直接代码,自己备用 /** * @method:生成验证码 */ [JSONMethod] [Description ( "生成验证码" )] [DomTemplate ( )] ...
- 【转】LINUX 5 常用ftp telnet配置
LINUX 5 常用ftp telnet配置 一.解决远程登陆乱码问题 目标:在xwindow和其console中使用中文界面,在纯console中使用英文 在/etc/profile最后加上一行 e ...
- 广度优先搜索 cdoevs 1226 倒水问题
cdoevs 1226 倒水问题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 有两个无刻度标志的水壶,分别可装 x 升 ...
- hdu-5496 Beauty of Sequence(递推)
题目链接: Beauty of Sequence Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Java ...
- 深入.NET框架 项目《魔兽登录系统》
创建魔兽系统相关窗体: 登录窗体(frmLogin) 注册窗体(frmRegister) 主窗体 (frmMain) 实现魔兽登录系统: 登录的界面如下 实现思路: 1.创建一个对象数组,长度为1 ...
- Inspector a ProgressBar(定制属性面板)
一.定制进度条 这篇文章主要学习如何在Unity的Inspector中使用ProgressBar 普通属性面板预览 通常我们的属性面板如下 定制属性面板预览 而通过扩展成ProcessBar后 二.内 ...
- DOTween文档
前言 DOTween现在还处于 alpha,所以还有一些缺失的功能(如路径插件,附加回调和其它的tween选项),这个文档在不久的将来可能会更新. DoTween:0.8.2.00 官方文档:http ...
- Linux安装Memcached服务
环境: CentOS 6.4 libevent-1.4.14b-stable memcached-1.4.21 查看是否安装libevent[root@localhost ~]# rpm -qa |g ...
- JAVA NIO概述(一):I/O模型
NIO是jdk1.4加入的新功能,我们一般成为非阻塞IO,在1.4之前,JAVA中的都是BIO(堵塞IO),BIO有以下几个缺点: 没有数据缓冲区,I/O性能存在问题 没有C/C++中channel( ...
- Corotational 模型代码
今天看了Corotational模型的代码. 在Vega中,获得模型内力的方法是先构造一个ForceModel对象,再调用其对应方法. 对于Corotational模型,构造的流程为: 构造Corot ...