經過本篇你能夠了解到:node
grunt-cli
其實也是Node
模塊,它能夠幫助咱們在控制檯中直接運行grunt
命令。所以當你使用grunt
的時候,每每都是先安裝grunt-cli
,再安裝grunt
。linux
若是你使用的是npm install -g grunt-cli
命令,那麼安裝地址以下:npm
windows: C:\\Users\\neusoft\\AppData\\Roaming\\npm\\node_modules\\grunt-cli linux: /nodejs/node_modules/grunt-cli
在這裏能夠直接看到編譯後的代碼。json
當執行grunt
命令時,會默認先去全局的grunt-cli
下找grunt-cli
模塊,而不會先走當前目錄下的node_modules
的grunt-cli
。
加載相應的代碼後,grunt-cli作了下面的工做:windows
completion
或者version
或者help
命令grunt.cli()
,繼續分析參數,執行相應的任務首先Node的模塊都會有一個特色,就是先去讀取package.json,經過裏面的main或者bin來肯定主程序的位置,好比grunt-cli在package.json中能夠看到主程序位於:數組
"bin": { "grunt": "bin/grunt" }
找到主程序,下面就看一下它都作了什麼:app
首先加載必備的模塊:dom
// 與查找和路徑解析有關 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則是相關的信息。async
而後纔是真正的核心代碼!
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();
這部份內容,能夠普遍的理解到其餘的模塊加載機制。
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);
至此,會獲得兩個變量:
而後根據文件名稱判斷加載的方式。加載的方式,主要包括兩類:
//匹配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中模塊的加載機制。
若是對你有幫助,就點個贊吧!若有異議,還請及時指點!