深入淺出 Source Map
作者:IDuxFE
原文:https://juejin.cn/post/7023537118454480904
專欄地址:前端編譯和工程化[1]**系列文章:Babel 那些事兒[2]、一個預(yù)覽 vue 文件 CLI 工具[3]、通過一個“時髦”的例子學(xué) Babel 插件[4]、深入淺出 vue-loader 自定義塊[5]**本文作者:前端小東[6]**
一、什么是 Source Map
通俗的來說, Source Map 就是一個信息文件,里面存儲了代碼打包轉(zhuǎn)換后的位置信息,實質(zhì)是一個 json 描述文件,維護(hù)了打包前后的代碼映射關(guān)系。關(guān)于 Source Map 的解釋可以看下 Introduction to JavaScript Source Maps[7]。
我們線上的代碼一般都是經(jīng)過打包的,如果線上代碼報錯了,想要調(diào)試起來,那真是很費勁了,比如下面這個例子:
使用打包工具 Webpack ,編譯這一段代碼
console.log('source map!!!')
console.log(a); //這一行肯定會報錯
瀏覽器打開后的效果:
點擊進(jìn)入報錯文件之后:
這根本沒法找到具體位置以及原因,所以這個時候, Source Map 的作用就來了, Webpack 構(gòu)建代碼中,開啟 Source Map :
然后重新執(zhí)行構(gòu)建,再次打開瀏覽器:
可以發(fā)現(xiàn),可以成功定位到具體的報錯位置了,這就是 Source Map 的作用。需要注意一點的是, Source Map 并不是 Webpack 特有的,其他打包工具同樣支持 Source Map ,打包工具只是將 Source Map 這項技術(shù)通過配置化的方式引入進(jìn)來。關(guān)于打包工具,下文會有介紹。
二、Source Map 的作用
上面的案例只是 Source Map 的初體驗,現(xiàn)在來說一下它的作用,我們?yōu)槭裁葱枰?Source Map ?
阮一峰老師的JavaScript Source Map 詳解[8]指出,JavaScript 腳本正變得越來越復(fù)雜。大部分源碼(尤其是各種函數(shù)庫和框架)都要經(jīng)過轉(zhuǎn)換,才能投入生產(chǎn)環(huán)境。
常見的源碼轉(zhuǎn)換,主要是以下三種情況:
-
壓縮,減小體積 -
多個文件合并,減少 HTTP 請求數(shù) -
其他語言編譯成 JavaScript
這三種情況,都使得實際運行的代碼不同于開發(fā)代碼,除錯( debug )變得困難重重,所以才需要 Source Map 。結(jié)合上面的例子,即使打包過后的代碼,也可以找到具體的報錯位置,這使得我們 debug 代碼變得輕松簡單,這就是 Source Map 想要解決的問題。
三、如何生成 Source Map
各種主流前端任務(wù)管理工具,打包工具都支持生成 Source Map 。
3.1 UglifyJS
UglifyJS 是命令行工具,用于壓縮 JavaScript 代碼
安裝 UglifyJS :
npm install uglify - js - g
壓縮代碼的同時生成 Source Map :
uglifyjs app.js - o app.min.js--source - map app.min.js.map
Source Map 相關(guān)選項:
--source - map Source Map的文件的路徑和名稱
--source - map - root 源文件的路徑
--source - map - url //#sourceMappingURL的路徑。 默認(rèn)為--source-map指定的值。
--source - map - include - sources 是否將源代碼的內(nèi)容添加到sourcesContent數(shù)組
--source - map - inline 是否將Source Map寫到壓縮代碼的最后一行
-- in -source - map 輸入Source Map, 當(dāng)源文件已經(jīng)經(jīng)過變換時使用
3.2 Grunt
Grunt 是 JavaScript 項目構(gòu)建工具
配置 grunt-contrib-uglify 插件以生成 Source Map :
grunt.initConfig({
uglify: {
options: {
sourceMap: true
}
}
});
使用 grunt-usemin 打包源碼時, grunt-usemin 會依次調(diào)用grunt-contrib-concat[9]與grunt-contrib-uglify[10]對源碼進(jìn)行打包和壓縮。因此都需要進(jìn)行配置:
grunt.initConfig({
concat: {
options: {
sourceMap: true
}
},
uglify: {
options: {
sourceMap: true,
sourceMapIn: function(uglifySource) {
return uglifySource + '.map';
},
}
}
});
3.3 Gulp
Gulp 是 JavaScript 項目構(gòu)建工具
使用gulp-sourcemaps[11]生成 Source Map :
var gulp = require('gulp');
var plugin1 = require('gulp-plugin1');
var plugin2 = require('gulp-plugin2');
var sourcemaps = require('gulp-sourcemaps');
gulp.task('javascript', function() {
gulp.src('src/**/*.js')
.pipe(sourcemaps.init())
.pipe(plugin1())
.pipe(plugin2())
.pipe(sourcemaps.write('../maps'))
.pipe(gulp.dest('dist'));
});
3.4 SystemJS
SystemJS 是模塊加載器
使用SystemJS Build Tool[12]生成 Source Map :
builder.bundle('myModule.js', 'outfile.js', {
minify: true,
sourceMaps: true
});
-
sourceMapContents選項可以指定是否將源碼寫入Source Map文件
3.5 Webpack
Webpack 是前端打包工具(本文案例都會使用該打包工具)。在其配置文件 webpack.config.js 中設(shè)置devtool[13]即可生成 Source Map 文件:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: "source-map"
};
-
devtool有 20 多種不同取值,分別生成不同類型的 Source Map,可以根據(jù)需要進(jìn)行配置。下文會詳細(xì)介紹,這里不再贅述。
3.6 Closure Compiler
利用 Closure Compiler[14] 生成
四、如何使用 Source Map
生成 Source Map 之后,一般在瀏覽器中調(diào)試使用,前提是需要開啟該功能,以 Chrome 為例:
打開開發(fā)者工具,找到 Settins :
勾選以下兩個選項:
再回到上面的案例中,源代碼文件變成了 index.js ,點擊進(jìn)入后顯示真實的源代碼,即說明成功開啟并使用了 Source Map
五、Source Map 的工作原理
還是上面這個案例,執(zhí)行打包后,生成 dist 文件夾,打開 dist/bundld.js :
可以看到尾部有這句注釋:
//# sourceMappingURL=bundle.js.map
正是因為這句注釋,標(biāo)記了該文件的 Source Map 地址,瀏覽器才可以正確的找到源代碼的位置。 sourceMappingURL 指向 Source Map 文件的 URL 。
除了這種方式之外,MDN[15]中指出,可以通過 response header 的 SourceMap: <url> 字段來表明。
> SourceMap: /path/to/file.js.map
dist 文件夾中,除了 bundle.js 還有 bundle.js.map ,這個文件才是 Source Map 文件,也是 sourceMappingURL 指向的 URL
-
version:Source map的版本,目前為v3。 -
sources:轉(zhuǎn)換前的文件。該項是一個數(shù)組,表示可能存在多個文件合并。 -
names:轉(zhuǎn)換前的所有變量名和屬性名。 -
mappings:記錄位置信息的字符串,下文會介紹。 -
file:轉(zhuǎn)換后的文件名。 -
sourceRoot:轉(zhuǎn)換前的文件所在的目錄。如果與轉(zhuǎn)換前的文件在同一目錄,該項為空。 -
sourcesContent:轉(zhuǎn)換前文件的原始內(nèi)容。
5.1 關(guān)于Source map的版本
在2009年 Google 的一篇文章中,在介紹 Cloure Compiler 時, Google 也趁便推出了一款調(diào)試東西: Firefox 插件 Closure Inspector ,以便利調(diào)試編譯后代碼。這便是 Source Map 的初步代啦!
You can use the compiler with Closure Inspector , a Firebug extension that makes debugging the obfuscated code almost as easy as debugging the human-readable source.
2010年,在第二代即 Closure Compiler Source Map 2.0 中, Source Map 招認(rèn)了共同的 JSON 格式及其他標(biāo)準(zhǔn),已幾乎具有現(xiàn)在的雛形。最大的差異在于 mapping 算法,也是 Source Map 的要害地址。第二代中的 mapping 已決定運用 base 64 編碼,可是算法同現(xiàn)在有收支,所以生成的 .map 比較現(xiàn)在要大許多。 2011年,第三代即**Source Map Revision 3 Proposal**[16]出爐了,這也是咱們現(xiàn)在運用的 Source Map版別。從文檔的命名看來,此刻的 Source Map 已脫離 Clousre Compiler ,演化成了一款獨立東西,也得到了瀏覽器的支撐。這一版相較于二代最大的改動是 mapping 算法的緊縮換代,運用VLQ[17]編碼生成base64[18]前的 mapping ,大大縮小了 .map 文件的體積。
Source Map 發(fā)展史的詼諧之處在于,它作為一款輔佐東西被開發(fā)出來。畢竟它輔佐的方針日漸式微,而它卻成為了技能主體,被寫進(jìn)了瀏覽器中。
Source Map V1最初步生成的Source Map文件大概有轉(zhuǎn)化后文件的10倍大。Source Map V2將之減少了50%,V3又在V2的基礎(chǔ)上減少了50%。所以現(xiàn)在133k的文件對應(yīng)的Source Map文件巨細(xì)大概在300k左右。
5.2 關(guān)于mappings屬性
為了避免干擾,將案例改成如下不報錯的情況:
var a = 1;
console.log(a);
打包編譯的后 bundle.js 文件:
/******/
(() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
var a = 1;
console.log(a);
/******/
})();
//# sourceMappingURL=bundle.js.map
打包編譯后的 bundle.js.map 文件:
{
"version": 3,
"sources": [
"webpack://learn-source-map/./src/index.js"
],
"names": [],
"mappings": "AAAA;AACA,c",
"file": "bundle.js",
"sourcesContent": [
"var a = 1;\r\nconsole.log(a);"
],
"sourceRoot": ""
}
可以看到 mappings 屬性的值是: AAAA; AACA, c ,要想說清楚這個東西,需要先解釋一下它的組成結(jié)構(gòu)。這是一個字符串,它分成三層:
-
第一層是行對應(yīng),以分號(; )表示,每個分號對應(yīng)轉(zhuǎn)換后源碼的一行。所以,第一個分號前的內(nèi)容,就對應(yīng)源碼的第一行,以此類推。 -
第二層是位置對應(yīng),以逗號(, )表示,每個逗號對應(yīng)轉(zhuǎn)換后源碼的一個位置。所以,第一個逗號前的內(nèi)容,就對應(yīng)該行源碼的第一個位置,以此類推。 -
第三層是位置轉(zhuǎn)換,以VLQ 編碼[19]表示,代表該位置對應(yīng)的轉(zhuǎn)換前的源碼位置。
在回到源代碼,就可以分析出:
-
因為源代碼中有兩行,所以有一個分號,分號前后表示了第一行和第二行。即 mappings中的AAAA和AACA,c。 -
分號后面表示第二行,也就是代碼 console.log(a);可以拆分出兩個位置,分別是console和log(a),所以存在一個逗號。即AACA,c中的AACA和c。
總結(jié),就是轉(zhuǎn)換后的源碼分成兩行,第一行有一個位置,第二行有兩個位置。
至于這個 AAAA , AAcA 等字母是怎么來的,可以參考阮一峰老師的JavaScript Source Map 詳解[20]有作詳細(xì)的介紹。筆者自己的理解是:
AAAA 和 AAcA 以及 c 都是代表了位置,正常來說,每個位置最多由 5 個字母組成,5 個字母的含義分別是:
-
第一位,表示這個位置在(轉(zhuǎn)換后的代碼的)的第幾列。 -
第二位,表示這個位置屬于 sources 屬性中的哪一個文件。 -
第三位,表示這個位置屬于轉(zhuǎn)換前代碼的第幾行。 -
第四位,表示這個位置屬于轉(zhuǎn)換前代碼的第幾列。 -
第五位,表示這個位置屬于 names 屬性中的哪一個變量。
這里轉(zhuǎn)換后最多只有 4 個字母,是因為沒有 names 屬性。
每一個位置都可以用VLQ 編碼[21]轉(zhuǎn)換,形成一種映射關(guān)系??梢栽?span style="font-weight: bold;color: rgb(90, 185, 131);">這個網(wǎng)站[22]自己轉(zhuǎn)換測試,將 AAAA; AACA, c 轉(zhuǎn)換后的結(jié)果:
可以得到兩組數(shù)據(jù):
[0, 0, 0, 0]
[0, 0, 1, 0], [14]
數(shù)字都是從 0 開始的,拿位置 AAAA 舉例,轉(zhuǎn)換后得到 [0, 0, 0, 0] ,所以代表的含義分別是;
-
壓縮代碼的第一列。 -
第一個源代碼文件,即 index.js。 -
源代碼的第一行。 -
源代碼第一列
通過以上解析,我們就能知道源代碼中 var a = 1; 在打包后文件中,即 bundle.js 的具體位置了。
六、Webpack 中的 Source Map
上文介紹了 Source Map 的作用,原理等?,F(xiàn)在說一下打包工具 WebPack 中對 Source Map 的應(yīng)用,畢竟我們在開發(fā)中,都離不開它。
上文有說道,只需要在 webpack.config.js 文件中配置 devtool 就可以使用 Source Map ,這個 devtool 具體的值有哪些,可以參考webpack devtool[23]
的介紹,官方羅列了 20 幾種類型,我們當(dāng)然不能全部都記住,可以記住幾個關(guān)鍵的:
建議以下 7 種可選方案:
-
source-map:外部??梢圆榭村e誤代碼準(zhǔn)確信息和源代碼的錯誤位置。 -
inline-source-map:內(nèi)聯(lián)。只生成一個內(nèi)聯(lián) Source Map,可以查看錯誤代碼準(zhǔn)確信息和源代碼的錯誤位置 -
hidden-source-map:外部??梢圆榭村e誤代碼準(zhǔn)確信息,但不能追蹤源代碼錯誤,只能提示到構(gòu)建后代碼的錯誤位置。 -
eval-source-map:內(nèi)聯(lián)。每一個文件都生成對應(yīng)的 Source Map,都在eval中,可以查看錯誤代碼準(zhǔn)確信息 和 源代碼的錯誤位置。 -
nosources-source-map:外部??梢圆榭村e誤代碼錯誤原因,但不能查看錯誤代碼準(zhǔn)確信息,并且沒有任何源代碼信息。 -
cheap-source-map:外部??梢圆榭村e誤代碼準(zhǔn)確信息和源代碼的錯誤位置,只能把錯誤精確到整行,忽略列。 -
cheap-module-source-map:外部??梢藻e誤代碼準(zhǔn)確信息和源代碼的錯誤位置, module會加入loader的Source Map。
內(nèi)聯(lián)和外部的區(qū)別:
-
外部生成了文件( .map),內(nèi)聯(lián)沒有。 -
內(nèi)聯(lián)構(gòu)建速度更快。
以下通過具體的案例演示上面的 7 種類型:
首先,將案例改成報錯狀態(tài),為了體現(xiàn)列的情況,將源代碼修改成如下:
console.log('source map!!!')
var a = 1;
console.log(a, b); //這一行肯定會報錯
6.1 source-map
devtool: 'source-map'
編譯后,可以查看錯誤代碼準(zhǔn)確信息和源代碼的錯誤位置:
生成了 .map 文件:
6.2 inline-source-map
devtool: 'inline-source-map'
編譯后,可以查看錯誤代碼準(zhǔn)確信息和源代碼的錯誤位置:
但是沒有生成 .map文件 ,而是以 base64 的形式插入到 sourceMappingURL 中:
6.3 hidden-source-map
devtool: 'hidden-source-map'
編譯后,可以查看錯誤代碼準(zhǔn)確信息,但是無法查看源代碼的位置:
生成了 .map 文件:
6.4 eval-source-map
devtool: 'eval-source-map'
編譯后,可以查看錯誤代碼準(zhǔn)確信息和源代碼的錯誤位置:
但是沒有生成 .map文件 ,而是在 eval函數(shù) 中,包括 sourceMappingURL :
6.5 nosources-source-map
devtool: 'nosources-source-map'
編譯后,可以查看無法查看錯誤代碼的準(zhǔn)確位置和源代碼的錯誤位置,只能提示錯誤原因:
生成了 .map 文件:
6.6 cheap-source-map
devtool: 'cheap-source-map'
編譯后,可以查看錯誤代碼準(zhǔn)確信息和源代碼的錯誤位置,但是忽略了具體的列( 因為是b導(dǎo)致報錯 ):
生成了 .map 文件:
6.7 cheap-module-source-map
因為需要 module ,所以案例中增加 loader :
module: {
rules: [{
test: /\.css$/,
use: [
// style-loader:創(chuàng)建style標(biāo)簽,將js中的樣式資源插入進(jìn)去,添加到head中生效
'style-loader',
// css-loader:將css文件變成commonjs模塊加載到j(luò)s中,里面內(nèi)容是樣式字符串
'css-loader'
]
}]
}
在 src 目錄下新建 index.css 文件,添加樣式代碼:
body {
margin: 0;
padding: 0;
height: 100%;
background-color: pink;
}
然后在 src/index.js 中引入 index.css :
//引入index.css
import './index.css';
console.log('source map!!!')
var a = 1;
console.log(a, b); //這一行肯定會報錯
修改 devtool :
devtool: 'cheap-module-source-map'
打包后,打開瀏覽器,樣式生效,說明 loader 引入成功。可以查看錯誤代碼準(zhǔn)確信息和源代碼的錯誤位置,但是忽略了具體的列( 因為是b導(dǎo)致報錯 ):
生成了 .map 文件,同時,將 loader 的信息也一起打包進(jìn)來:
6.8 總結(jié)
(1)開發(fā)環(huán)境:需要考慮速度快,調(diào)試更友好
-
速度快(
eval>inline>cheap>... ) -
eval-cheap-souce-map -
eval-source-map -
調(diào)試更友好
-
souce-map -
cheap-module-souce-map -
cheap-souce-map
最終得出最好的兩種方案 --> eval-source-map(完整度高,內(nèi)聯(lián)速度快) / eval-cheap-module-souce-map(錯誤提示忽略列但是包含其他信息,內(nèi)聯(lián)速度快)
(2)生產(chǎn)環(huán)境:需要考慮源代碼要不要隱藏,調(diào)試要不要更友好
-
內(nèi)聯(lián)會讓代碼體積變大,所以在生產(chǎn)環(huán)境不用內(nèi)聯(lián)
-
隱藏源代碼
-
nosources-source-map全部隱藏(打包后的代碼與源代碼) -
hidden-source-map只隱藏源代碼,會提示構(gòu)建后代碼錯誤信息
最終得出最好的兩種方案 --> source-map(最完整) / cheap-module-souce-map(錯誤提示一整行忽略列)
七、總結(jié)
Source Map 是我們?nèi)粘i_發(fā)過程中必不可少的,它可以幫助我們調(diào)試,定位錯誤。盡管它涉及非常多的知識點,例如:VLQ[24]、base64[25]等,但是我們核心關(guān)注的是它的工作原理,以及在打包工具中,如 webpack 等對 Source Map 的應(yīng)用。
Source Map 非常強大,不僅在應(yīng)用于日常開發(fā),還可以做更多的事情,如 性能異常監(jiān)控平臺 。比如FunDebug[26]這個網(wǎng)站就是通過 Source Map 還原生產(chǎn)環(huán)境中的壓縮代碼,提供完整的堆棧信息,準(zhǔn)確定位出錯誤源碼,幫助用戶快速修復(fù) Bug ,像這樣的案例還有許多。
總之,學(xué)習(xí) Source Map 是非常有必要的。
八、參考
-
Introduction to JavaScript Source Maps[27] -
MDN[28] -
JavaScript Source Map 詳解[29] -
VLQ[30] -
base64[31] -
base64vlq[32] -
FunDebug[33] -
絕了,沒想到一個 source map 居然涉及到那么多知識盲區(qū)[34] -
談?wù)勎沂侨绾潍@得知乎的前端源碼的[35]
參考資料
https://juejin.cn/column/6992030342987120677: https://juejin.cn/column/6992030342987120677
[2]https://juejin.cn/post/6992371845349507108: https://juejin.cn/post/6992371845349507108
[3]https://juejin.cn/post/7005351791671902244: https://juejin.cn/post/7005351791671902244
[4]https://juejin.cn/post/7013149595068792845: https://juejin.cn/post/7013149595068792845
[5]https://juejin.cn/post/7021687704999952415: https://juejin.cn/post/7021687704999952415
[6]https://juejin.cn/user/932815872994359: https://juejin.cn/user/932815872994359
[7]https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/: https://link.juejin.cn?target=https%3A%2F%2Fwww.html5rocks.com%2Fen%2Ftutorials%2Fdevelopertools%2Fsourcemaps%2F
[8]http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html: https://link.juejin.cn?target=http%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2013%2F01%2Fjavascript_source_map.html
[9]https://github.com/gruntjs/grunt-contrib-concat: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fgruntjs%2Fgrunt-contrib-concat
[10]https://github.com/gruntjs/grunt-contrib-uglify: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fgruntjs%2Fgrunt-contrib-uglify
[11]https://github.com/floridoo/gulp-sourcemaps: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Ffloridoo%2Fgulp-sourcemaps
[12]https://github.com/systemjs/builder: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fsystemjs%2Fbuilder
[13]https://webpack.js.org/configuration/devtool/: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.js.org%2Fconfiguration%2Fdevtool%2F
[14]https://github.com/google/closure-compiler: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fgoogle%2Fclosure-compiler
[15]https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/SourceMap: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FHTTP%2FHeaders%2FSourceMap
[16]https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#: https://link.juejin.cn?target=https%3A%2F%2Fdocs.google.com%2Fdocument%2Fd%2F1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k%2Fedit%23
[17]https://en.wikipedia.org/wiki/Variable-length_quantity: https://link.juejin.cn?target=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FVariable-length_quantity
[18]https://zh.wikipedia.org/zh-cn/Base64: https://link.juejin.cn?target=https%3A%2F%2Fzh.wikipedia.org%2Fzh-cn%2FBase64
[19]https://en.wikipedia.org/wiki/Variable-length_quantity: https://link.juejin.cn?target=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FVariable-length_quantity
[20]http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html: https://link.juejin.cn?target=http%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2013%2F01%2Fjavascript_source_map.html
[21]https://en.wikipedia.org/wiki/Variable-length_quantity: https://link.juejin.cn?target=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FVariable-length_quantity
[22]https://www.murzwin.com/base64vlq.html: https://link.juejin.cn?target=https%3A%2F%2Fwww.murzwin.com%2Fbase64vlq.html
[23]https://webpack.docschina.org/configuration/devtool/#root: https://link.juejin.cn?target=https%3A%2F%2Fwebpack.docschina.org%2Fconfiguration%2Fdevtool%2F%23root
[24]https://en.wikipedia.org/wiki/Variable-length_quantity: https://link.juejin.cn?target=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FVariable-length_quantity
[25]https://zh.wikipedia.org/zh-cn/Base64: https://link.juejin.cn?target=https%3A%2F%2Fzh.wikipedia.org%2Fzh-cn%2FBase64
[26]https://www.fundebug.com/: https://link.juejin.cn?target=https%3A%2F%2Fwww.fundebug.com%2F
[27]https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/: https://link.juejin.cn?target=https%3A%2F%2Fwww.html5rocks.com%2Fen%2Ftutorials%2Fdevelopertools%2Fsourcemaps%2F
[28]https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/SourceMap: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FHTTP%2FHeaders%2FSourceMap
[29]http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html: https://link.juejin.cn?target=http%3A%2F%2Fwww.ruanyifeng.com%2Fblog%2F2013%2F01%2Fjavascript_source_map.html
[30]https://en.wikipedia.org/wiki/Variable-length_quantity: https://link.juejin.cn?target=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FVariable-length_quantity
[31]https://zh.wikipedia.org/zh-cn/Base64: https://link.juejin.cn?target=https%3A%2F%2Fzh.wikipedia.org%2Fzh-cn%2FBase64
[32]https://www.murzwin.com/base64vlq.html: https://link.juejin.cn?target=https%3A%2F%2Fwww.murzwin.com%2Fbase64vlq.html
[33]https://www.fundebug.com/: https://link.juejin.cn?target=https%3A%2F%2Fwww.fundebug.com%2F
[34]https://juejin.cn/post/6963076475020902436: https://juejin.cn/post/6963076475020902436
[35]https://zhuanlan.zhihu.com/p/26033573: https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F26033573
最后
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我個小忙:
點個「喜歡」或「在看」,讓更多的人也能看到這篇內(nèi)容
我組建了個氛圍非常好的前端群,里面有很多前端小伙伴,歡迎加我微信「sherlocked_93」拉你加群,一起交流和學(xué)習(xí)
關(guān)注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。

