學(xué)習(xí)源碼的一個(gè)好辦法就是,跟進(jìn)源碼的邏輯中,看看流程是怎么樣流轉(zhuǎn)的,
這需要我們有直接debug代碼的能力,
有時(shí)候還需要我們在某些關(guān)鍵位置寫入log。
下面我們從npm run build 命令行工具開始,想辦法debug進(jìn)webpack中,
然后在關(guān)鍵位置寫入log。
1. npm scripts
上一篇中,我們在命令行中調(diào)用npm run build,
源碼就被自動(dòng)的編譯打包,然后結(jié)果輸出到了 ./dist/index.js 文件中了。
$ npm run build
> debug-webpack@1.0.0 build ~/Test/debug-webpack
> webpack
Hash: 2e91628041d9a877f709
Version: webpack 4.20.2
Time: 639ms
Built at: 2018-10-09 09:25:24
Asset Size Chunks Chunk Names
index.js 937 bytes 0 [emitted] index
Entrypoint index = index.js
[0] ./src/index.js 8 bytes {0} [built]
可是,這到底發(fā)生了什么呢?
1.1 npm run build

上一篇中我們在npm scripts配置了npm run build命令,
{
...,
"scripts:": {
...,
"build": "webpack"
}
...,
}
通過查看npm-run-script文檔,我們知道,
npm run會(huì)自動(dòng)添加node_module/.bin 到當(dāng)前命令所用的PATH變量中,
因此,npm run build 實(shí)際會(huì)調(diào)用 node_modules/.bin/webpack。
$ node_modules/.bin/webpack
Hash: 2070b107dceedfc63c72
Version: webpack 4.20.2
Time: 334ms
Built at: 2018-10-09 10:13:05
Asset Size Chunks Chunk Names
index.js 930 bytes 0 [emitted] index
Entrypoint index = index.js
[0] ./src/index.js 10 bytes {0} [built]
與執(zhí)行npm run build 效果一樣。
1.2 顯示原身

我在Finder中打開這個(gè)文件看了下,發(fā)現(xiàn)它是一個(gè)軟鏈接(symbolic link),
于是,我們還要看看它的原身在哪里。
$ l ~/.nvm/versions/node/v8.12.0/bin/webpack
lrwxr-xr-x 1 用戶名 staff 41B 10 24 09:50 node_modules/.bin/webpack -> ../_webpack@4.20.2@webpack/bin/webpack.js
我們看到它的原身在這里,
../_webpack@4.20.2@webpack/bin/webpack.js
完整路徑如下,
~/Test/debug-webpack/node_modules/_webpack@4.20.2@webpack/bin/webpack.js
這就是我們在node_modules中安裝的webpack模塊的文件地址。
我們來看看代碼,
https://github.com/webpack/webpack/blob/v4.20.2/bin/webpack.js
#!/usr/bin/env node
process.exitCode = 0;
/**
* @param {string} command process to run
* @param {string[]} args commandline arguments
* @returns {Promise<void>} promise
*/
const runCommand =
...
以上鏈接是webpack github倉庫的地址,我已經(jīng)找到了tag為4.20.2的版本位置,
它展示了4.20.2版本的webpack,./bin/webpack.js的源代碼。
后文中我們可以使用這樣的方式展示源代碼了。
1.3 Shebang
我們注意到了,./bin/webpack.js 文件頭有一行這樣的代碼,
#!/usr/bin/env node
它被稱為 Shebang。
在類Unix系統(tǒng)中,包含Shebang的文本,如果作為可執(zhí)行文件調(diào)用,
#!后面指定的解釋器將會(huì)被調(diào)用,用來執(zhí)行后面的代碼。
Shebang 語法如下,
#!interpreter [optional-arg]
注:
/usr/bin/env 不是一個(gè)路徑,而是一個(gè)命令,
后面跟node 參數(shù),就會(huì)找到node并調(diào)用它,
我們來試試,
$ /usr/bin/env node --version
v8.12.0
2. 寫入日志
上文中,我們了解到,
npm run build最終導(dǎo)致node解釋執(zhí)行了 ./bin/webpack.js 文件。
由于Node.js是解釋型語言,所以,我們可以直接修改源碼,來查看更改效果。
一般而言,最常見的寫日志的方法是直接使用console.log ,
但是我們發(fā)現(xiàn),控制臺(tái)還輸出了其他的文本,
我們很難找到自己輸出的信息。
為了展示方便,我決定使用 debug 模塊來輸出信息,
它是一個(gè)日志庫,可以用顏色來區(qū)分不同的日志,
看看github倉庫中的官方截圖,

2.1 安裝debug為devDependencies
./bin/webpack.js 位于 ~/Test/debug-webpack/node_modules/_webpack@4.20.2@webpack 文件夾中,
我們進(jìn)入這個(gè)文件夾,然后安裝debug,
$ cd ~/Test/debug-webpack/node_modules/_webpack@4.20.2@webpack
$ npm i -D debug
2.2 使用debug
在 ./bin/webpack.js 文件頭部調(diào)用debug,這里我們創(chuàng)建了一個(gè)log變量。
#!/usr/bin/env node
const log = require('debug')('debug-webpack webpack webpack.js');
...
記得要放到 #!/usr/bin/env node 后面,
其中第二個(gè)參數(shù)debug-webpack webpack webpack.js 稱為namespace ,可用于區(qū)分日志的顏色,
這里我們?yōu)檎麄€(gè)文件使用了相同的namespace。
2.3 bin/webpack.js 代碼邏輯

通過閱讀 ./bin/webpack.js 源碼,我們發(fā)現(xiàn),
它首先會(huì)對(duì)已安裝的CLI進(jìn)行檢查,然后會(huì)載入安裝的CLI工具。
webpack要求我們必須安裝webpack-cli 或 webpack-command 之一,否則就會(huì)報(bào)錯(cuò)。
if (installedClis.length === 0) {
// 報(bào)錯(cuò)
}
源碼位置如下:https://github.com/webpack/webpack/blob/v4.20.2/bin/webpack.js#L84
如果我們已經(jīng)安裝了某一個(gè)CLI的話,就會(huì)加載這個(gè)CLI,源碼第149-159行,
else if (installedClis.length === 1) {
const path = require("path");
const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
// eslint-disable-next-line node/no-missing-require
const pkg = require(pkgPath);
// eslint-disable-next-line node/no-missing-require
require(path.resolve(
path.dirname(pkgPath),
pkg.bin[installedClis[0].binName]
));
}
注意以上代碼第7行,webpack動(dòng)態(tài) require了一個(gè)地址,
這時(shí)候我們的log 工具就有用武之地了。
const cliPath = path.resolve(path.dirname(pkgPath), pkg.bin[installedClis[0].binName]);
log('cliPath: %s', cliPath);
require(cliPath);
2.3 查看日志
直接按原樣調(diào)用npm run build是看不到剛才寫入的日志信息的,
我們還需要傳入前置參數(shù),
$ DEBUG=debug-webpack* npm run build
The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described > in Shell Parameters. These assignment statements affect only the environment seen by that command.
—— Bash Reference Manual - 3.7.4 Environment
其中名為DEBUG的前置參數(shù),是 debug 模塊所需要的,
debug-webpack* 表示我們要輸出所有以debug-webpack 開頭namespace中的日志。
我們示例中,namespace是debug-webpack webpack webpack.js。
運(yùn)行結(jié)果如下,
debug-webpack webpack webpack.js cliPath: ~/Test/debug-webpack/node_modules/_webpack-cli@3.1.2@webpack-cli/bin/cli.js +0ms

3. webpack-cli/bin/cli.js

上文中我們得到了webpack require的CLI地址,
~/Test/debug-webpack/node_modules/_webpack-cli@3.1.2@webpack-cli/bin/cli.js
源碼位于,https://github.com/webpack/webpack-cli/blob/v3.1.2/bin/cli.js
webpack-cli版本為 v3.1.2。
分析源碼我們發(fā)現(xiàn),代碼中第436行require了webpack模塊,
https://github.com/webpack/webpack-cli/blob/v3.1.2/bin/cli.js#L436
const webpack = require("webpack");
隨后在第441行,調(diào)用webpack,返回了一個(gè)compiler,
https://github.com/webpack/webpack-cli/blob/v3.1.2/bin/cli.js#L441
compiler = webpack(options);
最后,在第533行,調(diào)用了compiler.run,
https://github.com/webpack/webpack-cli/blob/v3.1.2/bin/cli.js#L533
} else compiler.run(compilerCallback);

4. 開始debug
知道了webpack-cli的代碼邏輯之后,我們就可以創(chuàng)建一個(gè)debug.js腳本來模擬webpack-cli調(diào)用了,
在我們上一篇debug-webpack示例項(xiàng)目中,添加一個(gè)./debug.js 文件,
const webpack = require('webpack');
const options = require('./webpack.config');
const compiler = webpack(options);
compiler.run((...args) => {
console.log(...args);
});
保持這個(gè)文件打開狀態(tài),在以上代碼第6行位置打個(gè)斷點(diǎn),
然后在vscode中按 F5(或者點(diǎn)擊左側(cè)調(diào)試面板,再點(diǎn)擊調(diào)試)。

代碼就停在我們的斷點(diǎn)位置上了。
然后我們可以點(diǎn)擊左數(shù)第3個(gè)按鈕,進(jìn)行單步調(diào)試,就可以進(jìn)入compiler.run方法中了。

參考
github: debug
github: webpack v4.20.2 ./bin/webpack.js
github: webpack-cli v3.1.2 ./bin/cli.js
Debugging in Visual Studio Code