閱讀322 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

起源

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

那麼,在 Node 服務器環境應該如何使用 source map 呢?最近在重新搭建一個完全基於 ts 的 Node 應用,所有的相關流程看起來都挺好的,唯一的缺陷是報錯信息錯誤信息指向的是 js 文件。我覺得應該探索下如何讓 Node 支持 source map 。

原理

對於 Node 而言,服務器 source map 最大的價值在於錯誤信息有正確的錯誤堆棧,所以隻要我們能夠實現自定義錯誤堆棧信息就可以了。

恰好 v8 引擎有提供一個私有的 (Stack-Trace-API), 這個提供了讓開發者自定義錯誤 stack 信息的能力。具體來說,開發者可以實現 Error 對象的 prepareStackTrace 方法,如果 Error 對象上定義了這個方法,那麼每次錯誤信息都會經過 Error.prepareStackTrace 處理後返回。

Error.prepareStackTrace 方法可以拿到兩個參數,錯誤基本信息和結構化錯誤堆棧,第二個參數是一個數組,通過這個數組可以拿到錯誤文件以及位置信息。最後基於這些信息重新返回一個字符串,這樣就可以覆蓋 Error 對象的 stack 屬性了。

基本代碼結構如下:

function prepareStackTrace(error, stack) {
  return error + stack.map(function(frame) {
    return '\n    at ' + wrapCallSite(frame);;
  }).join('');
}
Error.prepareStackTrace = prepareStackTrace;

wrapCallSite 方法裏麵可以通過分析源碼,找到 sourceMap 然後返回正確的位置信息。

原理很簡單,已經有一個 npm 包 source-map-support 封裝好了相關功能。

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

問題

Stack Trace API 看起來很美好,但現實場景總是更加複雜。我在引入 source-map-support 後,運行起來沒什麼問題,但在跑測試用例的時候,錯誤堆棧的位置信息完全不對。

這個問題排查了很久,最終定位到在 wrapCallSite 方法中拿到的 frame 對象返回的行號就是錯誤的,而這個獲取行號的方法是 native code ,這個幾乎沒法調試了。我想,難道是 Node 的問題?要調試到 Node 源碼麼?

折騰了很久沒有什麼效果,就在我打算放棄的時候,我換了一個假設,會不會是某個包依賴影響的?然後我嚐試依次刪除跑用例時 require 的包,終於發現是因為 egg-bin 默認引入的 power-assert 導致的。

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

power-assert 與 sourceMap

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

img

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

it('foo', function foo() {
  var a = 'foo';
  var b = 'b';
  assert(a === b);
});

經過 espower-source 處理後,變成了這樣

it('foo', function foo() {
  var a = 'foo';
  var b = 'b';
  assert(expr(capture(capture(a, '/0/left') === capture(b, '/0/right'), '/0'), {
      content: 'assert(a === b)',
      filepath: 'bizLogger.test.ts',
      line: 107
  }));
})

注:上麵的代碼不是真實運行的代碼,經過一些刪減

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

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

但隻運行 power-assert ,不引用 source-map-support 的時候,錯誤行號還是對的。這是因為 espower-source 返回重新編譯後的源碼後,還同時對源碼文件的 sourceMap 進行了重新轉換。所以大部分情況,我們是無法感知到源碼有經過重新編譯。

運行簡單流程圖如下

img

解決問題

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

  1. 測試文件源碼會被 power-assert 修改,增加一些信息收集代碼
  2. power-assert 同時有引入 source-map-support 來對錯誤堆棧進行重新定位

當我在我自己的業務中也引入 source-map-support 來重新定位錯誤堆棧時,我所拿到的源碼是被 power-assert 修改過的,所以這時候是無法重新定位到正確的 ts 位置了。

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

問題確定了,就可以自己動手解決了,我發了一個 mr 讓 espower-source 支持 sourceMap 文件,最新版的 power-assert 已經可以正確定位到 ts 源碼位置了。

另外,還有一個問題,正常情況 source-map-support 同時引入兩遍,隻要引入的文件路徑一直,也是不會有問題的。但 power-assert 使用的還是老版本 source-map-support ,而且老版定位位置信息還是不夠準確,這個也很好解決,升級依賴版本既可以。

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

總結

基於 V8 的 Stack Trace API 的使用,瀏覽器的 sourceMap 能力也可以應用到 Node 服務器場景下,使用 npm 包 source-map-support 就可以了。

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

最後更新:2017-04-10 18:00:11

  上一篇:go 智慧醫療雲平台,是顛覆傳統醫療的下一步!
  下一篇:go 醫療雲市場的十大領軍企業:穀歌、IBM、英特爾等醫療雲服務哪家好?