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依賴的模塊:
其中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 加載模塊的名稱
然后根據文件名稱判斷加載的方式。加載的方式,主要包括兩類:
- 只傳入模塊的名稱,則從當前路徑逐級向上查找
- 傳入標準的路徑,直接在該路徑下查找
如果正常的使用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
那么會得到如下的數組:
執行的代碼如下:
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加载原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos(7) 使用yum进行安装l
- 下一篇: molo 安装记录