天天看點

Node.js 中 source map 使用問題總結

node 應用功能越來越複雜,很多業務都開始嘗試使用 typescript 來開發。現在前端寫的 js 大部分是經過編譯過程的,浏覽器中通過 source map 的使用,可以很好的解決源碼和編譯運作時代碼差異的問題。

那麼,在 node 伺服器環境應該如何使用 source map 呢?最近在重新搭建一個完全基于 ts 的 node 應用,所有的相關流程看起來都挺好的,唯一的缺陷是報錯資訊錯誤資訊指向的是 js 檔案。我覺得應該探索下如何讓 node 支援 source map 。

對于 node 而言,伺服器 source map 最大的價值在于錯誤資訊有正确的錯誤堆棧,是以隻要我們能夠實作自定義錯誤堆棧資訊就可以了。

<code>error.preparestacktrace</code> 方法可以拿到兩個參數,錯誤基本資訊和結構化錯誤堆棧,第二個參數是一個數組,通過這個數組可以拿到錯誤檔案以及位置資訊。最後基于這些資訊重新傳回一個字元串,這樣就可以覆寫 error 對象的 stack 屬性了。

基本代碼結構如下:

在 <code>wrapcallsite</code> 方法裡面可以通過分析源碼,找到 sourcemap 然後傳回正确的位置資訊。

這看起來已經很完美了。source map 讀取隻在出現錯誤的時候才執行,是以這個功能不會有性能問題,在生成環境也可以開啟。

這個問題排查了很久,最終定位到在 <code>wrapcallsite</code> 方法中拿到的 frame 對象傳回的行号就是錯誤的,而這個擷取行号的方法是 native code ,這個幾乎沒法調試了。我想,難道是 node 的問題?要調試到 node 源碼麼?

問題定位到後,解決就容易了。但解決這個問題得先講講 power-assert 是如何實作的。

power-assert 作為一個斷言庫,最大的特色在于錯誤資訊的報告是非常友好的,一張圖可以很清晰看到差別

Node.js 中 source map 使用問題總結

實作這樣炫酷的報告是需要做一些特殊的處理,把測試用例的代碼進行一次轉換,舉個例子

注:上面的代碼不是真實運作的代碼,經過一些删減

對于 <code>assert(a === b);</code> 這樣一個表達式,會通過 <code>capture</code> 捕獲每一個運算過程的位置和值,最終通過 <code>expr</code> 運算。這樣經過轉換後,代碼運作邏輯不變,但是異常發生的時候可以傳回 assert 表達式中每一步的傳回值。

我所遇到的問題也就是因為 power-assert 對代碼進行了轉換,最終異常抛出時,真實 js 異常位置資訊是轉換後的位置,這個位置自然是無法正确定位到源碼位置了。

運作簡單流程圖如下

Node.js 中 source map 使用問題總結

回到最初的問題,跑用例的時候行号不對了。power-assert 的影響在于兩點

測試檔案源碼會被 power-assert 修改,增加一些資訊收集代碼

既然 power-assert 有引入 sourcemap ,那麼是不是我關閉自己引入的 sourcemap 就可以了呢?理論上是應該如此的,但是因為 power-assert 對 sourcemap 檔案不支援(inline sourcemap 是支援的),是以隻能定位到 js 源碼,無法定位到 ts 源碼。

這兩個問題解決後,在自己的業務用引入 source-map-support 也沒有問題了,power-assert 傳回的錯誤堆棧也可以正确的指向 sourcemap 位置了。

有時候可能會遇到一些奇怪的錯誤行号的問題,這可能是某個依賴包對 js 進行了轉換,畢竟這在前端太常見了,動不動就重新編譯 js 源碼。

繼續閱讀