【总结】1263-弄懂SourceMap,前端开发提效100%
⼀、什么是SourceMap
通俗的来说,SourceMap就是⼀个信息⽂件,⾥⾯存储了代码打包转换后的位置信息,实质是⼀个json描述⽂件,维护了打包前后的代
码映射关系。关于SourceMap的解释可以看下IntroductiontoJavaScriptSourceMaps
[7]
。
我们线上的代码⼀般都是经过打包的,如果线上代码报错了,想要调试起来,那真是很费劲了,⽐如下⾯这个例⼦:
使⽤打包⼯具Webpack,编译这⼀段代码
(sourcemap)
(a);//这⼀⾏肯定会报错
浏览器打开后的效果:
点击进⼊报错⽂件之后:
这根本没法找到具体位置以及原因,所以这个时候,SourceMap的作⽤就来了,Webpack构建代码中,开启SourceMap:
然后重新执⾏构建,再次打开浏览器:
可以发现,可以成功定位到具体的报错位置了,这就是SourceMap的作⽤。需要注意⼀点的是,SourceMap并不是Webpack特有的,
其他打包⼯具同样⽀持SourceMap,打包⼯具只是将SourceMap这项技术通过配置化的⽅式引⼊进来。关于打包⼯具,下⽂会有介绍。
⼆、SourceMap的作⽤
上⾯的案例只是SourceMap的初体验,现在来说⼀下它的作⽤,我们为什么需要SourceMap?
阮⼀峰⽼师的JavaScriptSourceMap详解
[8]
指出,JavaScript脚本正变得越来越复杂。⼤部分源码(尤其是各种函数库和框架)都要
经过转换,才能投⼊⽣产环境。
常见的源码转换,主要是以下三种情况:
压缩,减⼩体积
多个⽂件合并,减少HTTP请求数
其他语⾔编译成JavaScript
这三种情况,都使得实际运⾏的代码不同于开发代码,除错(debug)变得困难重重,所以才需要SourceMap。结合上⾯的例⼦,即使
打包过后的代码,也可以找到具体的报错位置,这使得我们debug代码变得轻松简单,这就是SourceMap想要解决的问题。
三、如何⽣成SourceMap
各种主流前端任务管理⼯具,打包⼯具都⽀持⽣成SourceMap。
3.1UglifyJS
UglifyJS是命令⾏⼯具,⽤于压缩JavaScript代码
安装UglifyJS:
npminstalluglify-js-g
复制代码
压缩代码的同时⽣成SourceMap:
复制代码
SourceMap相关选项:
--source-mapSourceMap的⽂件的路径和名称
--source-map-root源⽂件的路径
--source-map-url//#sourceMappingURL的路径。默认为--source-map指定的值。
--source-map-include-sources是否将源代码的内容添加到sourcesContent数组
--source-map-inline是否将SourceMap写到压缩代码的最后⼀⾏
--in-source-map输⼊SourceMap,当源⽂件已经经过变换时使⽤
复制代码
3.2Grunt
Grunt是JavaScript项⽬构建⼯具
配置grunt-contrib-uglify插件以⽣成SourceMap:
nfig({
uglify:{
options:{
sourceMap:true
}
}
});
复制代码
使⽤grunt-umin打包源码时,grunt-umin会依次调⽤grunt-contrib-concat
[9]
与grunt-contrib-uglify
[10]
对源码进⾏打包和压缩。
因此都需要进⾏配置:
nfig({
concat:{
options:{
sourceMap:true
}
},
uglify:{
options:{
sourceMap:true,
sourceMapIn:function(uglifySource){
returnuglifySource+.map;
},
}
}
});
复制代码
3.3Gulp
Gulp是JavaScript项⽬构建⼯具
使⽤gulp-sourcemaps
[11]
⽣成SourceMap:
vargulp=require(gulp);
varplugin1=require(gulp-plugin1);
varplugin2=require(gulp-plugin2);
varsourcemaps=require(gulp-sourcemaps);
(javascript,function(){
(src/**/*.js)
.pipe(())
.pipe(plugin1())
.pipe(plugin2())
.pipe((../maps))
.pipe((dist));
});
复制代码
3.4SystemJS
SystemJS是模块加载器
使⽤SystemJSBuildTool
[12]
⽣成SourceMap:
(,,{
minify:true,
sourceMaps:true
});
复制代码
sourceMapContents选项可以指定是否将源码写⼊SourceMap⽂件
3.5Webpack
Webpack是前端打包⼯具(本⽂案例都会使⽤该打包⼯具)。在其配置⽂件中设置devtool[13]
即可⽣成SourceMap
⽂件:
constpath=require(path);
s={
entry:./src/,
output:{
filename:,
path:e(__dirname,dist)
},
devtool:"source-map"
};
复制代码
devtool有20多种不同取值,分别⽣成不同类型的SourceMap,可以根据需要进⾏配置。下⽂会详细介绍,这⾥不再赘述。
3.6ClosureCompiler
利⽤ClosureCompiler
[14]
⽣成
四、如何使⽤SourceMap
⽣成SourceMap之后,⼀般在浏览器中调试使⽤,前提是需要开启该功能,以Chrome为例:
打开开发者⼯具,找到Settins:
勾选以下两个选项:
再回到上⾯的案例中,源代码⽂件变成了,点击进⼊后显⽰真实的源代码,即说明成功开启并使⽤了SourceMap
五、SourceMap的⼯作原理
还是上⾯这个案例,执⾏打包后,⽣成dist⽂件夹,打开dist/:
可以看到尾部有这句注释:
//#sourceMappingURL=
复制代码
正是因为这句注释,标记了该⽂件的SourceMap地址,浏览器才可以正确的找到源代码的位置。sourceMappingURL指向SourceMap⽂
件的URL。
除了这种⽅式之外,MDN
[15]
中指出,可以通过responheader的SourceMap:
>SourceMap:/path/to/
>```
`dist`⽂件夹中,除了``还有``,这个⽂件才是`SourceMap`⽂件,也是`sourceMappingURL`指向的`URL`
![](/ur/20608/)
*`version`:`Sourcemap`的版本,⽬前为`v3`。
*`sources`:转换前的⽂件。该项是⼀个数组,表⽰可能存在多个⽂件合并。
*`names`:转换前的所有变量名和属性名。
*`mappings`:记录位置信息的字符串,下⽂会介绍。
*`file`:转换后的⽂件名。
*`sourceRoot`:转换前的⽂件所在的⽬录。如果与转换前的⽂件在同⼀⽬录,该项为空。
*`sourcesContent`:转换前⽂件的原始内容。
#5.1关于Sourcemap的版本
在2009年`Google`的⼀篇⽂章中,在介绍`CloureCompiler`时,`Google`也趁便推出了⼀款调试东西:`Firefox`插件`ClosureInspector`,以便利调试编译后代
>YoucanuthecompilerwithClosureInspector,aFirebugextensionthatmakesdebuggingtheobfuscatedcodealmostaasyasdebuggingthehuman-rea
2010年,在第⼆代即`ClosureCompilerSourceMap2.0`中,`SourceMap`招认了共同的`JSON`格式及其他标准,已⼏乎具有现在的雏形。最⼤的差异在于`ma
2011年,第三代即[**SourceMapRevision3Proposal**](/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#)出炉
`SourceMap`发展史的诙谐之处在于,它作为⼀款辅佐东西被开发出来。毕竟它辅佐的⽅针⽇渐式微,⽽它却成为了技能主体,被写进了浏览器中。
>SourceMapV1最初步⽣成的SourceMap⽂件⼤概有转化后⽂件的10倍⼤。SourceMapV2将之减少了50%,V3⼜在V2的基础上减少了50%。所以现在133k的⽂件
#5.2关于mappings属性
为了避免⼲扰,将案例改成如下不报错的情况:
```js
vara=1;
(a);
`
复制代码
打包编译的后⽂件:
/******/
(()=>{//webpackBootstrap
var__webpack_exports__={};
/*!**********************!*
!***./src/***!
**********************/
vara=1;
(a);
/******/
})();
//#sourceMappingURL=
复制代码
打包编译后的⽂件:
{
"version":3,
"sources":[
"webpack://learn-source-map/./src/"
],
"names":[],
"mappings":"AAAA;AACA,c",
"file":"",
"sourcesContent":[
"vara=1;
(a);"
],
"sourceRoot":""
}
复制代码
可以看到mappings属性的值是:AAAA;AACA,c,要想说清楚这个东西,需要先解释⼀下它的组成结构。这是⼀个字符串,它分成三
层:
第⼀层是⾏对应,以分号(;)表⽰,每个分号对应转换后源码的⼀⾏。所以,第⼀个分号前的内容,就对应源码的第⼀⾏,以此类
推。
第⼆层是位置对应,以逗号(,)表⽰,每个逗号对应转换后源码的⼀个位置。所以,第⼀个逗号前的内容,就对应该⾏源码的第⼀个
位置,以此类推。
第三层是位置转换,以VLQ编码
[16]
表⽰,代表该位置对应的转换前的源码位置。
在回到源代码,就可以分析出:
1.因为源代码中有两⾏,所以有⼀个分号,分号前后表⽰了第⼀⾏和第⼆⾏。即mappings中的AAAA和AACA,c。
2.分号后⾯表⽰第⼆⾏,也就是代码(a);可以拆分出两个位置,分别是console和log(a),所以存在⼀个逗号。即AACA,c中
的AACA和c。
总结,就是转换后的源码分成两⾏,第⼀⾏有⼀个位置,第⼆⾏有两个位置。
⾄于这个AAAA,AAcA等字母是怎么来的,可以参考阮⼀峰⽼师的JavaScriptSourceMap详解
[17]
有作详细的介绍。笔者⾃⼰的理解
是:
AAAA和AAcA以及c都是代表了位置,正常来说,每个位置最多由5个字母组成,5个字母的含义分别是:
第⼀位,表⽰这个位置在(转换后的代码的)的第⼏列。
第⼆位,表⽰这个位置属于sources属性中的哪⼀个⽂件。
第三位,表⽰这个位置属于转换前代码的第⼏⾏。
第四位,表⽰这个位置属于转换前代码的第⼏列。
第五位,表⽰这个位置属于names属性中的哪⼀个变量。
这⾥转换后最多只有4个字母,是因为没有names属性。
每⼀个位置都可以⽤VLQ编码
[18]
转换,形成⼀种映射关系。可以在这个⽹站
[19]
⾃⼰转换测试,将AAAA;AACA,c转换后的结果:
可以得到两组数据:
[0,0,0,0]
[0,0,1,0],[14]
复制代码
数字都是从0开始的,拿位置AAAA举例,转换后得到[0,0,0,0],所以代表的含义分别是;
1.压缩代码的第⼀列。
2.第⼀个源代码⽂件,即。
3.源代码的第⼀⾏。
4.源代码第⼀列
通过以上解析,我们就能知道源代码中vara=1;在打包后⽂件中,即的具体位置了。
六、Webpack中的SourceMap
上⽂介绍了SourceMap的作⽤,原理等。现在说⼀下打包⼯具WebPack中对SourceMap的应⽤,毕竟我们在开发中,都离不开它。
上⽂有说道,只需要在⽂件中配置devtool就可以使⽤SourceMap,这个devtool具体的值有哪些,可以参考
webpackdevtool
[20]
的介绍,官⽅罗列了20⼏种类型,我们当然不能全部都记住,可以记住⼏个关键的:
建议以下7种可选⽅案:
source-map:外部。可以查看错误代码准确信息和源代码的错误位置。
inline-source-map:内联。只⽣成⼀个内联SourceMap,可以查看错误代码准确信息和源代码的错误位置
hidden-source-map:外部。可以查看错误代码准确信息,但不能追踪源代码错误,只能提⽰到构建后代码的错误位置。
eval-source-map:内联。每⼀个⽂件都⽣成对应的SourceMap,都在eval中,可以查看错误代码准确信息和源代码的错误位置。
nosources-source-map:外部。可以查看错误代码错误原因,但不能查看错误代码准确信息,并且没有任何源代码信息。
cheap-source-map:外部。可以查看错误代码准确信息和源代码的错误位置,只能把错误精确到整⾏,忽略列。
cheap-module-source-map:外部。可以错误代码准确信息和源代码的错误位置,module会加⼊loader的SourceMap。
内联和外部的区别:
1.外部⽣成了⽂件(.map),内联没有。
2.内联构建速度更快。
以下通过具体的案例演⽰上⾯的7种类型:
⾸先,将案例改成报错状态,为了体现列的情况,将源代码修改成如下:
(sourcemap)
vara=1;
(a,b);//这⼀⾏肯定会报错
复制代码
6.1source-map
devtool:source-map
复制代码
编译后,可以查看错误代码准确信息和源代码的错误位置:
⽣成了.map⽂件:
6.2inline-source-map
devtool:inline-source-map
复制代码
编译后,可以查看错误代码准确信息和源代码的错误位置:
但是没有⽣成.map⽂件,⽽是以ba64的形式插⼊到sourceMappingURL中:
6.3hidden-source-map
devtool:hidden-source-map
复制代码
编译后,可以查看错误代码准确信息,但是⽆法查看源代码的位置:
⽣成了.map⽂件:
6.4eval-source-map
devtool:eval-source-map
复制代码
编译后,可以查看错误代码准确信息和源代码的错误位置:
但是没有⽣成.map⽂件,⽽是在eval函数中,包括sourceMappingURL:
6.5nosources-source-map
devtool:nosources-source-map
复制代码
编译后,可以查看⽆法查看错误代码的准确位置和源代码的错误位置,只能提⽰错误原因:
⽣成了.map⽂件:
6.6cheap-source-map
devtool:cheap-source-map
复制代码
编译后,可以查看错误代码准确信息和源代码的错误位置,但是忽略了具体的列(因为是b导致报错):
⽣成了.map⽂件:
6.7cheap-module-source-map
因为需要module,所以案例中增加loader:
module:{
rules:[{
test:/.css$/,
u:[
//style-loader:创建style标签,将js中的样式资源插⼊进去,添加到head中⽣效
style-loader,
//css-loader:将css⽂件变成commonjs模块加载到js中,⾥⾯内容是样式字符串
css-loader
]
}]
}
复制代码
在src⽬录下新建⽂件,添加样式代码:
body{
margin:0;
padding:0;
height:100%;
background-color:pink;
}
复制代码
然后在src/中引⼊:
//引⼊
import./;
(sourcemap)
vara=1;
(a,b);//这⼀⾏肯定会报错
复制代码
修改devtool:
devtool:cheap-module-source-map
复制代码
打包后,打开浏览器,样式⽣效,说明loader引⼊成功。可以查看错误代码准确信息和源代码的错误位置,但是忽略了具体的列(因为是
b导致报错):
⽣成了.map⽂件,同时,将loader的信息也⼀起打包进来:
6.8总结
(1)开发环境:需要考虑速度快,调试更友好
速度快(eval>inline>cheap>...)
-cheap-souce-map
-source-map
调试更友好
-map
-module-souce-map
-souce-map
最终得出最好的两种⽅案-->eval-source-map(完整度⾼,内联速度快)/eval-cheap-module-souce-map(错误提⽰忽略列但是包含
其他信息,内联速度快)
(2)⽣产环境:需要考虑源代码要不要隐藏,调试要不要更友好
内联会让代码体积变⼤,所以在⽣产环境不⽤内联
隐藏源代码
ces-source-map全部隐藏(打包后的代码与源代码)
-source-map只隐藏源代码,会提⽰构建后代码错误信息
最终得出最好的两种⽅案-->source-map(最完整)/cheap-module-souce-map(错误提⽰⼀整⾏忽略列)
七、总结
SourceMap是我们⽇常开发过程中必不可少的,它可以帮助我们调试,定位错误。尽管它涉及⾮常多的知识点,例如:VLQ[21]
、
ba64
[22]
等,但是我们核⼼关注的是它的⼯作原理,以及在打包⼯具中,如webpack等对SourceMap的应⽤。
SourceMap⾮常强⼤,不仅在应⽤于⽇常开发,还可以做更多的事情,如性能异常监控平台。⽐如FunDebug[23]
这个⽹站就是通过
SourceMap还原⽣产环境中的压缩代码,提供完整的堆栈信息,准确定位出错误源码,帮助⽤户快速修复Bug,像这样的案例还有许多。
总之,学习SourceMap是⾮常有必要的。
⼋、参考
IntroductiontoJavaScriptSourceMaps
[24]
MDN
[25]
JavaScriptSourceMap详解
[26]
VLQ
[27]
ba64
[28]
ba64vlq
[29]
FunDebug
[30]
绝了,没想到⼀个sourcemap居然涉及到那么多知识盲区
[31]
谈谈我是如何获得知乎的前端源码的
[32]
关于本⽂
来源:IDuxFE
TheEnd
“在看”吗?在看就点⼀下吧
本文发布于:2022-12-28 13:00:24,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/46777.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |