webpack源碼分析 - 調試

最新更新地址node

調試

爲何要調試

webpack源碼寫的仍是比較繞,各類回調,遞歸和異步跳來跳去,剛開始跟代碼時容易迷失方向。console.log在處理複雜流程就稍顯薄弱,調試簡單的代碼還行,可是遇到各類異步方法和遞歸調用時,只經過log打印出來的東西很難看得懂。看代碼時有時候須要咱們深刻到各個子流程中,有時候又要忽略細節只看總體,因此靈活使用調試工具顯得尤其重要。固然,必要的console.log也是須要的,須要咱們根據狀況選擇合適的工具。webpack

這裏咱們使用vscode自帶的調試工具來跟蹤代碼,基本上只需簡單配置開箱即用,很是容易上手,基礎的斷點功能能夠很好地讓代碼執行過程掌控在本身手中,調用堆棧功能能夠幫助咱們保留上下文環境,變量查看功能配合堆棧可讓咱們隨時在各個調用切換,接下來咱們先熟悉這幾個功能的使用。git

跑起來demo.js

在側邊欄一個小蟲子的圖標就是調試面板,底部調試控制檯終端能夠輸出調試信息,運行代碼能夠從調試面板啓動。(圖片.jpg)github

首先建立一個工程文件夾debugger,在根目錄建立demo.js文件,很簡單僅僅將啓動命令參數打印出來web

// demo.js
function printArgv() {
    console.log(process.argv);
}
printArgv()
複製代碼

接下來在根目錄下新建.vscode文件夾,而後建立launch.json文件(這一步可讓vscode自動生成)json

// launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "啓動程序",
            "skipFiles": [ "<node_internals>/**" ],
            "program": "${workspaceFolder}/demo.js",
            "args": ["-o", "bundle.js"]
        }
    ]
}
複製代碼

配置很是簡單,建立完成後就能夠在調試面板點擊啓動程序按鈕,成功則會在調試控制檯中打印相似下面的語句bash

Array(4) ["/usr/local/bin/node", "~/Desktop/debugger/demo.js", "-o", "bundle.js"]異步

基礎操做

如今咱們在demo.js處打上斷點,直接在行號前打上紅點就行。啓動運行(F5),咱們會看到代碼會停在printArgv()這行,同時左側的調試面板內容也豐富起來。async

// demo.js
    function printArgv() {
        console.log(process.argv); // 斷點
    }
    this.count = 9
    printArgv() // 斷點
複製代碼

首先看看 變量 一欄,這裏展現了環境上下文的全部可用變量,是最主要的調試功能之一,很是簡單直觀,在接下來使用過程當中會反覆使用到這裏的內容:編輯器

  • Local 爲當前上下文環境,裏面有常見的this對象及自定義的printArgv函數等,另外將鼠標移動到代碼處能夠快速瀏覽到對應的變量詳情。
  • Global 則是全局上下文,裏面常見的有global對象及eval函數等。

調用堆棧

程序任務運行是由一組函數相互調用來完成的,調用堆棧就記錄了其中的調用關係。一個函數調用另外一個函數產生入棧操做,最頂層的函數就是當前運行的函數。頂層的函數執行結束後回到調用函數產生出棧操做。

點擊 單步調試 按鈕(F11),代碼會繼續向下執行到console.log(process.argv);一行,此時查看 調用堆棧 面板,會看到最頂層顯示的就是printArgv,就是當前運行的函數,另外會顯示demo.js 2:5,即當前運行在了demo文件的第2行,同時在 變量 面板中也顯示這當前棧的上下文。

接着看第二行(anonymous function) demo.js 5:1,由調用棧可知程序是在demo.js的第5行調用了printArgv函數,其中(anonymous function)意思是這個函數是匿名函數,就是說明了咱們寫的demo.js是運行在一個匿名函數中。

咱們點擊調用堆棧面板的第二行,能夠看到編輯器退回到了調用printArgv()的位置並用綠色高亮,同時能夠看到變量面板中的上下文變量也切換到了當前行的上下文。這個提供給咱們回溯運行環境的功能很是強大,在調用棧很是深時候特別有用,能夠很方便回頭看看前面的內容。

skipFiles

在調用堆棧咱們還能夠看到有顯示另外8個:只讀核心模塊(skipped by 'skipFiles')的信息,在launch.json咱們配置了一項"skipFiles": [ "<node_internals>/**" ],這裏咱們告訴調試器將node的核心代碼隱藏起來,咱們不關心node是怎麼啓動的,console.log是調用的哪一個底層代碼來運行的,因此增長了這個配置項忽略掉對咱們幫助不大的調用棧,最重要的是在點擊 單步調試 時,代碼不會走到忽略掉的文件裏,大大方便了咱們的調試。

接下來咱們新建一個文件print_util.js來測試忽略文件,同時在launch.json的skipFiles裏添加多一項${workspaceFolder}/print_util.js

// print_util.js
function printArgv() {
    console.log(process.argv); // 斷點
}
module.exports = printArgv

// demo.js
const printArgv = require('./print_util')
printArgv() // 斷點
複製代碼

運行程序能夠發現代碼不會走到print_util.js文件中,同時斷點面板也不會顯示該文件裏的斷點。

eval 和 已載入的腳本

js運行時除了直接執行咱們寫好了的js文件,另外也能夠經過eval()來執行字符串代碼。

// demo.js
function printArgv() {
    eval('console.log(process.argv);') // 斷點
}
printArgv() // 斷點
複製代碼

調試執行上面的語句,連續執行2次 單步調試 後,咱們會看到控制檯並無打印並正常結束,而是在調用堆棧裏出現了另一個匿名函數,這個匿名函數的位置是在VMxxx 1:1中。

在執行到eval函數時,虛擬機會"建立"出一個js文件,文件內容就是傳入eval的參數,因爲是個js文件就以匿名函數執行。

已載入的腳本面板中,咱們能夠看到有三個內容:demo.js,<eval><node_internals>,其中的eval項裏咱們能夠找到虛擬機給"建立"的文件,打開能夠看到就是傳入的參數console.log(process.argv);

異步調用

首先添加'neo-async'依賴yarn add neo-async

// demo.js
const async = require('neo-async')
var tasks = [
    function(done) {
        console.log('task1 run') // 斷點1
        setTimeout(function() {
            done(null, 'task1'); // 斷點2
        }, 1000);
    },
    function(done) {
        console.log('task2 run') // 斷點3
        setTimeout(function() {
            done(null, 'task2'); // 斷點4
        }, 2000);
    },
];
console.time('task')
async.parallel(tasks, function(err, res) { // 斷點5
  console.timeEnd('task')
  console.log(res); // 斷點6
});
複製代碼

執行後控制檯打印以下:

task1 run
task2 run
[ 'task1', 'task2' ]
task: 2003.954ms
複製代碼

parallel會同時執行兩個任務task1和task2,並在任務裏面開啓了兩個延時任務,最後等2秒全部任務執行完後經過回掉打印出結果。

爲了避免進入neo-async代碼實現裏,將node_modules文件夾添加到skipFiles裏:"${workspaceFolder}/node_modules/**"。接着運行調試,運行順序以下:

  • node核心運行(忽略)
  • demo.js以匿名函數執行
  • async.parallel執行(忽略)
  • task1的console.log()
  • task2的console.log()
  • node核心Timer運行,等待1秒(忽略)
  • task1.done
  • node核心Timer運行,等待1秒(忽略)
  • task2.done
  • async.callback執行(忽略)
  • tasks回調執行

以上操做可讓咱們清晰看到整個異步函數的運行流程。若是去掉上面的skipFiles,調試過程當中就會進到async源碼中,容易干擾咱們的分析。若是沒有在最後的回調中打斷點,那麼點下一步調試器是不會進入到最後的回調去,會直接結束。因此異步函數斷點打在哪裏須要你們根據實際狀況,根據你要關注的源碼重點進行分析。

調試webpack

瞭解瞭如何使用調試,是爲了更好的幫助咱們跟蹤源碼,下面以webpack源碼來看如何運用調試工具幫助咱們理清順序

  1. 從github clone webpack源碼,並切換到 webpack-4 tag下,

  2. yarn install安裝依賴

  3. 添加vscode調試環境 launch.json,修改配置項program"${workspaceFolder}/bin/webpack.js",使用該文件做爲啓動文件。

  4. 修改/node_module/webpack_cli/bin/cli.js第267行。由於webpack使用cli做爲啓動器,cli又是node_modules依賴包,因此cli最後運行的是node_modules下的webpack庫,如今想讓cli運行clone下來的webpack庫,因此強行更改依賴關係,若是有更好方法能夠改進。

- const webpack = require("webpack");
+ const webpack = require("../../../lib/webpack");
複製代碼
  1. 在/bin/webpack.js第一行添加斷點,運行調試

注意事項

  • 調用堆棧:在webpack代碼中函數遞歸回調層層嵌套,有時候跟着跟着就忘了前面是從哪裏進來的,這時候翻一翻調用棧就能夠很方便找到回憶。
  • skipFiles:在webpack裏引用了大量第三方庫,有時候咱們想要跟到這些庫裏看看細節,有時候對細節清楚了又想快速跳過,這時候靈活配置這個參數就能夠很是方便咱們控制調試的節奏。
  • eval:在webpack中的核心類Tapable中大量使用了eval方法,瞭解這一點後面在調用堆棧中看到相關內容就不會在迷惑了。
  • 異步調用:在webpack大量使用了async,因此瞭解異步方法運行流程和調試方法,同時配合skipFiles將不關心的分支流程忽略調,纔不容易被源碼給繞暈。
相關文章
相關標籤/搜索