細說 js 壓縮、sourcemap、通過 sourcemap 查找原始報錯信息
1. js
壓縮
js
壓縮對前端開發者來說是一門必修課。
一般來說,壓縮 js
主要出於以下兩個目的:
- 減小代碼體積,加快前端資源加載速度
- 保護源代碼不被別人獲取
壓縮 js
使用的工具庫:
-
UglifyJS2: 壓縮
es5
-
uglify-es: 壓縮
es6+
-
closure-compiler、closure-compiler-js:
google
的js
壓縮、優化工具
壓縮 js
的主要過程:
- 移除無用代碼
- 混淆代碼中變量名稱、函數名稱等
- 預編譯代碼
- 對結構進行扁平化處理
1. 移除無用代碼
去掉所有對解析引擎來說無用的字符,包括空格、註釋、換行、沒有用的變量聲明、函數聲明等。
2. 混淆代碼中變量名稱、函數名稱等
把一些局部變量名稱、函數名稱等用 a, b, ...
、$1, $2, ...
、_1, _2, ...
之類的簡略字符進行替換,達到混淆的目的。
源代碼
(function () {
var hello = 'hi';
var print = function (str) {
console.log(str);
};
print(hello);
})();
壓縮後的代碼(僅演示混淆功能)
(function () {
var a = 'hi';
var b = function (c) {
console.log(c);
};
b(a);
})();
3. 預編譯代碼
把不依賴外部環境的邏輯提前進行運算,並把運算結果替換到相應的源碼處,然後從源碼中移除這段邏輯。
源代碼
(function () {
var hello = 'hi' + ' everyone, ';
var count = 3 * 5;
console.log(hello + count + ' girls');
})();
壓縮後的代碼(僅演示預編譯功能)
(function () {
var hello = 'hi everyone, ';
var count = 15;
console.log(hello + count + ' girls');
})();
4. 對結構進行扁平化處理
對於 js
來說,嵌套越深,執行越慢,對代碼進行扁平化處理也是優化代碼的一種方式。
源代碼
(function () {
var say = {
hello: function (str) {
console.log('hello ' + str);
}
};
say.hello('everyone');
})();
壓縮後的代碼(僅演示扁平化結構功能)
!function(str){console.log("hello "+str)}("everyone");
完整示例
源代碼
(function () {
var say = {
hello: function (str) {
console.log('hello ' + str);
}
};
say.hello('everyone');
})();
壓縮後的代碼
!function(l){console.log("hello "+l)}("50 girls");
2. sourcemap
通常 js
壓縮後只有一行代碼,並且裏面的變量名與函數名等都是混淆了的,這在實際運行中會有一個問題,就是 js
的報錯信息將會失真,無法追蹤到是在源代碼哪一行哪一列報的錯。
sourcemap
便是爲了解決這個問題而生的。
sourcemap
文件就是記錄了從源代碼文件到壓縮文件的一個代碼對應關係記錄表,通過壓縮文件和 sourcemap
文件可以原原本本找出源代碼文件。
查看阮一峯老師的 JavaScript Source Map 詳解 瞭解 sourcemap
的原理與格式。
一般在壓縮 js
的過程中,會生成相應的 sourcemap
文件,並且在壓縮的 js
文件末尾追加 sourcemap
文件的鏈接 //# sourceMappingURL=bundle-file-name.js.map
。這樣,瀏覽器在加載這個壓縮 js
的時候,就知道還有一個相應的 sourcemap
文件,也一併加載下來,運行的過程中如果 js
報錯,也會給出相應源代碼的行號與列號,而非壓縮文件的。
比如,對下面的源碼進行壓縮:
(function () {
var say = {
hi: function () {
console.log('hi');
}
};
say.hello();
return say;
})();
未加 sourcemap
文件時,報錯信息是:
加上 sourcemap
文件時,報錯信息是:
sourcemap
擴展
webpack 對 sourcemap
做了擴展,定義在 devtool
配置項中:
-
eval
: 每個模塊都使用eval()
執行,並且都有//@ sourceURL
,構建很快,但無法正確顯示行號 -
eval-source-map
: 每個模塊使用eval()
執行,並且source map
轉換爲DataUrl
後添加到eval()
中,一般開發模式中使用這種方式 -
cheap-eval-source-map
: 類似eval-source-map
,但只映射行,不映射列,並忽略源自loader
的source map
,僅顯示轉譯後的代碼 -
cheap-module-eval-source-map
: 類似cheap-eval-source-map
,但會保留源自loader
的source map
-
inline-source-map
:source map
轉換爲DataUrl
後添加到bundle
中 -
cheap-source-map
: 只映射行,不映射列,並忽略源自loader
的source map
,僅顯示轉譯後的代碼 -
inline-cheap-source-map
:inline-source-map
與cheap-source-map
的結合 -
cheap-module-source-map
: 類似cheap-module-eval-source-map
,但不使用eval()
執行 -
inline-cheap-module-source-map
:inline-source-map
與cheap-module-source-map
的結合 -
source-map
: 整個source map
作爲一個單獨的文件生成,產品環境一般使用這種模式 -
hidden-source-map
: 類似source-map
,但不會把//# sourceMappingURL=bundle-file-name.js.map
追加到壓縮文件後面 -
nosources-source-map
: 類似source-map
,但只有堆棧信息,沒有源碼信息
更詳細信息可以參考:
使用建議
對於使用 webpack 來構建項目,建議在開發時使用 eval-source-map
,產品環境使用 source-map
。
因爲用壓縮文件與 sourcemap
文件是可以原原本本的找到源代碼的,所以,爲了保護源代碼,可以這樣隱藏 sourcemap
文件:
- 在
web
服務器設置外部不能訪問sourcemap
文件,只能內部訪問 - 直接把
sourcemap
文件存放到其他地方
3. 通過 sourcemap
查找原始報錯信息
一般而言,在產品階段,我們會用 window.onerror
來捕獲 js
報錯,然後上報到服務器,以此來收集用戶使用時發生的 bug
:
window.onerror = function(message, source, lineno, colno, error) {
// message: 錯誤信息
// source: 報錯腳本的 url 地址
// lineno: 行號
// colno: 列號
// error: 錯誤對象
// 上報必要的信息到服務器
}
但產品環境的代碼都是壓縮的,行號和列號都是失真的,所以就需要用 sourcemap
文件來找到錯誤對應源代碼的行號與列號,以及其他的信息。
使用工具: mozilla/source-map
源代碼
(function () {
var say = {
hi: function () {
console.log('hi');
}
};
say.hello();
return say;
})();
壓縮後報錯信息
window.onerror = function(message, source, lineno, colno, error) {
console.log(`message: ${message}`);
console.log(`source: ${source}`);
console.log(`lineno: ${lineno}`);
console.log(`colno: ${colno}`);
console.log(`error: ${error}`);
}
// message: Uncaught TypeError: e.hello is not a function
// source: url/to/bundle.min.js
// lineno: 1
// colno: 982
// error: TypeError: e.hello is not a function
通過 source-map
查找原始報錯信息
const fs = require('fs');
const SourceMap = require('source-map');
const { readFileSync } = fs;
const { SourceMapConsumer } = SourceMap;
const rawSourceMap = JSON.parse(readFileSync('path/to/js/map/file', 'utf8'));
SourceMapConsumer.with(rawSourceMap, null, consumer => {
const pos = consumer.originalPositionFor({
line: 1,
column: 982
});
console.log(pos);
});
查找到的原始信息
{
source: 'path/to/index.js',
line: 8,
column: 7,
name: 'hello'
}
這樣,便找到了原始報錯信息:
- 原始報錯文件:
path/to/index.js
- 原始報錯行號:8
- 原始報錯列號:7
- 原始對象名稱:
hello
如此,便能一下子就找到錯誤在哪裏了。
更多用法,參考 mozilla/source-map
後續
更多博客,查看 https://github.com/senntyou/blogs
版權聲明:自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)