寫在前面:
本來一開始想沿用之前vue源碼的標題:webpack源碼之***,但是這個工具比較巨大,所以為防止有人覺得我裝逼跑來噴我(或者隨時鴿),加上淺析二字,以示慫。
既然是淺析,那么案例就不必太復雜,所以繼續按照之前vue源碼,以最簡單形式進行源碼分析,如下:
配置文件config.js:
module.exports={ entry:'./entry.js', output:{ filename:'output' } }
入口文件entry.js:
console.log('entry');
執行命令為:
node webpack --config config.js
不摸魚了,開始正式進入源碼,慣例上圖:
之前簡單講解了webpack.cmd的內容,可以發現在正常進行webpack打包之前,會調用bin文件夾的webpack.js文件,這一節就看一下這個文件做了什么,經過簡單整理,源碼如下:
var path = require("path"); // 優先使用當前路徑版本的webpack.js try { var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js")); if(__filename !== localWebpack) { return require(localWebpack); } } catch(e) {} // 引入yargs框架 var yargs = require("yargs") .usage("字符串..."); // 配置 require("./config-yargs")(yargs); var DISPLAY_GROUP = "Stats options:"; var BASIC_GROUP = "Basic options:"; yargs.options({ "json": { type: "boolean", alias: "j", describe: "Prints the result as JSON." }, // ... // 各種配置... }); // 解析命令行參數並執行回調函數 // 正常情況下err為null output為空字符 yargs.parse(process.argv.slice(2), (err, argv, output) => { // 報錯信息 if(err && output) { // ... } // 輸出幫助或版本信息 if(output) { // ... } // 這里對配置文件進行轉換與合法性檢測 var options = require("./convert-argv")(yargs, argv); // argv的參數二次處理 // 暫時無視 function ifArg(name, fn, init) { // ... } // 解析配置文件 function processOptions(options) { // 當前配置文件是一個Promise時 if(typeof options.then === "function") { options.then(processOptions).catch(function(err) { console.error(err.stack || err); process.exit(1); // eslint-disable-line }); return; } // 大量的ifArg(...) // 獲取webpack主函數 var webpack = require("../lib/webpack.js"); Error.stackTraceLimit = 30; var lastHash = null; var compiler; try { // 編譯 compiler = webpack(options); } catch(err) { // error } // ...編譯后回調 暫時不管 } // 執行上面的函數 processOptions(options); });
源碼說長也不長,可以分為五大塊:
1、引入yargs框架並進行配置
2、使用yargs解析命令並調用回調函數
3、進行配置文件參數轉換與合法性檢測
4、引入webpack主函數進行編譯
5、編譯完成后調用對應的回調函數
其中yargs框架是一個命令行框架,具體內容請自行查閱,這里不會做更多介紹,因此源碼內容會按順序跳着講。
首先是一個小try/catch語句:
// Local version replace global one try { var localWebpack = require.resolve(path.join(process.cwd(), "node_modules", "webpack", "bin", "webpack.js")); if(__filename !== localWebpack) { return require(localWebpack); } } catch(e) {}
其實注釋已經講明了這個代碼塊的作用,這里稍微詳細再說幾點。
process是node中的一個全局對象,而process.cwd()方法可以獲取到當前進程的絕對路徑,測試如圖:
所以第一行賦值語句會嘗試獲取./node_modules/webpack/bin/webpack,js文件並進行調用,將結果賦給localWebpack。
而__filename是當前指令文件的詳細絕對路徑,在默認的node webpack *指令中,一般情況我們都會配置環境變量,所以這個webpack指向全局的webpack,而__filename也是全局的webpack.js路徑。
因此,if判斷就是優先使用當前路徑的webpack.js,如果不存在,就使用全局的webpack來進行編譯解析。
接下來的幾塊內容全是yargs的配置,這里就不細看了,直接進入yargs.parse函數:
yargs.parse(process.argv.slice(2), (err, argv, output) => { // ... })
前面提到過process是node的全局對象,argv是它的一個屬性,主要處理命令參數,可以簡單打印看一下內容:
在什么也不做的時候,會返回一個數組,其中第一個元素默認為node.exe路徑。
這里創建一個JS專門打印process.argv:
// entry.js console.log(process.argv);
然后執行下列命令:
node entry 1 2 3
結果如圖:
可以看到,傳入命令的參數是從第三個開始依次展開,所以yargs.parse的第一個參數排除了argv數組的前兩個,只傳命令參數。
yargs的parse方法會負責收集並管理參數,在執行成功后會調用后面的函數,回調函數有三個參數,分別為:
1、err => 解析過程中的錯誤信息
2、argv => 參數管理對象
3、output => 輸出到終端的文本
如果正常解析,err為null,而output為空字符。
這里可以觀察一下argv的內容,在源碼中打印結果如圖:
這只是一部分屬性的截圖,對象中的第一個_保存了傳進來的參數,由於直接運行的node webpack --config *,后面沒有帶參數,所以這里是個空數組。
而其余大量的屬性都是在之前配置中添加的,比如說前面的4個:help、h、version、v,來自於config-yarg.js文件,源碼如下:
module.exports = function(yargs) { yargs .help("help") .alias("help", "h") .version() .alias("version", "v") .options( // 更多配置... ) }
這個配置不是關心的重點,暫時沒必要去看。
接下來是convert-argv函數,負責對配置對象轉換並返回:
var options = require("./convert-argv")(yargs, argv);
這個函數有點長,下一節再來講解。