Grunt-cli的执行过程以及Grunt加载原理
2016-01-27 18:42
399 查看
通过本篇你可以了解到:
1 grunt-cli的执行原理
2 nodeJS中模块的加载过程
如果你使用的是
在这里可以直接看到编译后的代码。
当执行
加载相应的代码后,grunt-cli做了下面的工作:
1 设置控制台的名称
2 获取打开控制台的目录
3 执行
4 查找grunt,执行相应的命令
5 调用
找到主程序,下面就看一下它都做了什么:
首先加载必备的模块:
然后就是判断下当前的参数,比如如果输入grunt --version,则会同时输出grunt-cli和grunt的版本:
其中,cli定义了当前指令参数的别名,没什么关键作用。
info则是相关的信息。
然后才是真正的核心代码!
可以看到它传入控制台开启的目录,即process.cwd();
然后通过resolve方法解析grunt的路径。
最后调用grunt.cli()方法
resolve是grunt-cli依赖的模块:
其中async为异步的加载方案,sync为同步的加载方案。看grunt-cli程序的最上面,可以发现grunt-cli是通过同步的方式查找grunt的。
sync就是标准的node模块了:
主要看看内部的加载机制吧!
首先判断加载的模块是否是核心模块:
core其实是个判断方法:
核心模块有下面这些:
回到sync.js中,继续定义了两个方法:
至此,会得到两个变量:
y 代表控制台开启的路径,查找会从这个路径开始
x 加载模块的名称
然后根据文件名称判断加载的方式。加载的方式,主要包括两类:
只传入模块的名称,则从当前路径逐级向上查找
传入标准的路径,直接在该路径下查找
如果正常的使用grunt xxx的时候,就会进入loadNodeMudelsSync()方法中。
这个方法中使用了另一个关键的方法来获取加载的路径:
nodeModulesPathsSync方法可以分解目录,并返回加载模块的路径。
举个例子,如果我的路径是
那么会得到如下的数组:
执行的代码如下:
获取到了加载的路径后,就一次执行加载方法。
如果是文件,则使用下面的方法加载,其实就是遍历一遍后缀数组,看看能不能找到:
如果是目录,则尝试读取package.json,查找它的main参数,看看能不能直接找到主程序;如果找不到,则自动对 当前路径/index下进行查找。
这样,就完成了模块的加载了。
如果对你有帮助,就点个赞吧!如有异议,还请及时指点!
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中模块的加载机制。如果对你有帮助,就点个赞吧!如有异议,还请及时指点!
相关文章推荐
- 关于JS变量提升的一些坑
- I am a student. student.a am I
- C语言学习笔记 数据类型
- tomcat7+jdk的keytool生成证书 配置https
- osgearth使用arcgis地图服务的问题
- LAMP架构之编译安装httpd+(php-fpm)+mariadb
- 最新回归算法理解、实现及应用(2)logistic回归
- Gson和Fastjson的使用
- XUtils3.0 解决不缓存问题
- java下实现压缩数据存取
- 题 新建文档(有点乱)
- The Java™ Tutorials — Generics :Type Inference 类型推断
- C++关键字-const与volatile限定说明符
- 直接拿来用!10款实用Android UI工具
- android学习视频
- Splunk 模式的中国践行者——日志易让日志分析更容易
- 荔枝FM架构师刘耀华:异地多活IDC机房架构 - 极客头条 - CSDN.NET
- 细说python类3——类的创建过程
- REST API 基于ACCESS TOKEN
- 0--PHP初识之路