0%

Webpack SourceMap 回顾

本文同步发布在我的 Github 个人博客

前几天在优化项目时,发现next.config.js的配置里,development 模式下的 sourcemap 为cheap-module-sourcemap,仔细想了想,记忆中好像有个更推荐常用的开发模式 sourcemap 配置:cheap-module-eval-source-map

看了一下是三年前添加的代码,于是又回顾研究了下 webpack 的 sourcemap 配置,开个 PR。

什么是 Sourcemap

我们的项目在打包后,将开发环境中源代码经过压缩,去空格,babel 编译等工程化转换,最终的项目代码和源代码之间差异性很大,会造成无法 debug 的问题,在线上环境定位到的代码是压缩处理后的代码。

而 Sourcemap 就是是为了解决开发代码与实际运行代码不一致时帮助我们 debug 到原始开发代码的技术,解决上述代码定位的问题,是源代码和目标代码出错位置的映射

Sourcemap 关键词

Sourcemap 的关键词组合眼花缭乱,我们不可能一个一个去记每种 sourcemap 用途,我们只需知道几个关键词的意思,便可推测它们组合的 sourcemap 类型。

eval

每一个模块都执行 eval() 过程,执行后不会生成.map文件,而是在每一个模块后追加//@ sourceURL来关联代码处理前后的对应关系。使用 eval 包裹模块代码,可以提高 rebuild 的速度。故一般 sourcemap 带有eval的选项,rebuild 速度都快一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
webpackJsonp([1],[
function(module,exports,__webpack_require__){
eval(
...
//# sourceURL=webpack:///./src/js/index.js?'
)
},
function(module,exports,__webpack_require__){
eval(
...
//# sourceURL=webpack:///./src/static/css/app.less?./~/.npminstall/css-loader/0.23.1/css-loader!./~/.npminstall/postcss-loader/1.1.1/postcss-loader!./~/.npminstall/less-loader/2.2.3/less-loader'
)
},
function(module,exports,__webpack_require__){
eval(
...
//# sourceURL=webpack:///./src/tmpl/appTemplate.tpl?"
)
},
...])

image.png

为什么 eval 模式 rebuild 的速度会快?

因为带 eval 的构建模式能够 cache Sourcemap,猜测可能是带 eval 的模式能把每个模块的生成代码单独打在一行里,并且都以 DataURL 的形式添加 sourcemap,这样它在 rebuild 时会对每一个模块进行检查,如果没有发生改动,就直接使用之前的 sourcemap,故只需要为改动的模块重新生成 sourcemap;而非 eval 的构建模式下生成代码不管有任何模块作出修改,都会影响到最后 bundle 文件整体的行列格式,所以它每次都要重新生成整体的 sourcemap,rebuild 的速度会很慢。

source-map

会为每一个打包后的模块生成独立的.map文件,会在 bundle 文件末尾追加 sourceURI=指定.map文件路径,会在浏览器开发者工具中看到webpack://的文件夹

1
2
3
4
5
6
7
webpackJsonp([1],[
function(e,t,i){...},
function(e,t,i){...},
function(e,t,i){...},
function(e,t,i){...},
...
])//# sourceMappingURL=index.js.map

打包后的模块在模块后面会对应引用一个.map文件,同时在打包好的目录下会针对每一个模块生成相应的.map 文件,在上例中会生成一个 index.js.map 文件,这个文件是一个典型的 sourcemap 文件,形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version":3,
"sources":[
"webpack:///js/index.js","webpack:///./src/js/index.js",
"webpack:///./~/.npminstall/css-loader/0.23.1/css-loader/lib/css-base.js",
...
],
"names":["webpackJsonp","module","exports"...],
"mappings":"AAAAA,cAAc,IAER,SAASC...",
"file":"js/index.js",
"sourcesContent":[...],
"sourceRoot":""
}

cheap

不包含列信息也不包含 loader 的 sourcemap,会为每一个模块生成.map文件,与source-map的区别在于 cheap 生成的.map文件会忽略原始代码中的列信息。

由于生成的 sourcemap 不会有列信息而只有行信息,编译计算量少,所以速度较快。

module

包含 loader 模块之间的 sourcemap(比如 jsx 语法代码经 loader 编译为原生 js 代码),这样可以看到 loader 处理前的原始代码

inline

正常的 sourcemap 的生成是在 dist 目录下创建一个.map 文件, inline 的含义就是不产生独立的 .map 文件,把 sourcemap 的内容以 DataURI 的方式追加到 bundle 件末尾,即将.map 作为 DataURI 嵌入。(一般使用该类型如造成体积过大,该类型比较少用)

1
2
3
4
5
6
7
8
webpackJsonp([1],[
function(e,t,i){...},
function(e,t,i){...},
function(e,t,i){...},
function(e,t,i){...},
...
])
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9...

DataURL 最早是出现在 HTML 文件 img 标签中的关于图片的引用,如 base64 图片

DataURL 使用于如下的场景

  • 访问外部资源受限
  • 图片体积小,占用一个 HTTP 会话资源浪费

hidden

bundle 里不包含 sourcemap 的引用地址,这样浏览器开发者工具里看不到原始代码

Sourcemap 类型

上述的关键词混一起搭配,又增加了好几种 sourcemap 类型

  • eval-source-map:每一个模块在执行 eval()过程之后,并且会为每一个模块生成 .map 文件,生成的 sourcemap 文件通过 DataURL 的方式添加(把 eval 的 sourceURL 换成了完整 sourcemap 信息的 DataURL)

  • cheap-eval-source-map:跟 eval-source-map 相同,唯一不同的就是增加了”cheap”,”cheap”是指忽略了行信息。这个属性同时也不会生成不同 loader 模块之间的 sourcemap。

  • cheap-module-eval-source-map:与 cheap-eval-source-map 相同,但是包含了不同 loader 模块之间的 sourcemap

  • cheap-source-map: 不包含列信息,不包含 loader 的 sourcemap

  • inline-source-map: 为每一个文件添加 sourcemap 的 DataURL,注意这里的文件是打包前的每一个文件而不是最后打包出来的,同时这个 DataURL 是包含一个文件完整 sourcemap 信息的 base64 格式化后的字符串

-hidden-source-map:不在 bundle 文件结尾处追加 sourceURL 指定其 sourcemap 文件的位置,但是仍然会生成 sourcemap 文件。这样,浏览器开发者工具就无法应用 sourcemap, 目的是避免把 sourcemap 文件发布到生产环境,造成源码泄露。

不仅还有这几种,还可以继续组合,这里就列举这么多了

如何选择 Sourcemap

  • 首先在源代码的列信息意义不大,因为只要有行信息就能完整的建立打包前后代码之间的依赖关系,够我们定位了。
  • 其次,不管在生产环境还是开发环境,我们都需要定位 debug 到最最原始的资源,比如定位错误到 jsx 的原始代码处,而不是编译成 js 的代码处,因此,不能忽略 module 属性。
  • eval-source-map 使用 DataURL 本身包含完整 sourcemap 信息,并不需要像 sourceURL 那样,浏览器需要发送一个完整请求去获取 sourcemap 文件,这会略微提高点效率

开发环境中使用:cheap-module-eval-source-map(该配置值能保留 loader 处理前的原始代码信息,而打包速度也较快,是一个较佳的选择。)

生产环境中使用 sourcemap 会有泄露源代码的风险,但如果要保留定位线上的错误,应该禁止浏览器开发者工具看到源代码,而是用一些错误收集系统,将 sourcemap 文件传到系统上,通过系统 source map 分析出原始代码的错误堆栈,如使用hidden-source-map

当然,也有使用nosources-source-map, source-map的,依据你们需求场景选择。总之,最终结果是不能被人通过开发者工具看到源代码的。

image.png

结语

我们项目目前还是 Webpack4.x,如果你们项目已经用 Webpack5,sourcemap 名称有些不
同,具体请查阅 Webpack5 文档。

参考

Webpack 中的 sourcemap 以及如何在生产和开发环境中合理的设置 sourcemap 的类型


ps:

-------------本文结束感谢您的阅读-------------