webpack5 与 source map

1,139 阅读7分钟
  1. sourcemap会影响网页性能吗?
  2. 如何选择webpack5的devtool属性?
  3. 如果生产环境没有map文件,出错了如何定位?

Source map介绍

简单理解source map就是存储源码实际运行代码的关系映射文件,它不是JavaScript独有的,也同样适用于样式,比如 SASS/LESS编译为CSS,也同样需要map文件,没有sourcemap,报错难以判断出具体哪个模块 image.png 有了soucemap,快速定位报错的模块 image.png

⚠️注意:: 记得开启浏览器devtools对javascript/CSS sourcemap的支持,基本都默认开启,没有就手动开启下

Source map原理

详见阮老师的JavaScript Source Map 详解 sourcemap本质就是JSON对象,如下是sourcemap的json对象构成元素

{
    “version”: 3, // Source map的版本,目前为3
    “file”: "out.js", // 转换后的文件名
    “sourceRoot”: "",// 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空
    “sources”: ["foo.js", "bar.js"], // 转换前的文件。该项是一个数组,表示可能存在多个文件合并
    “names”: ["src", "maps", "are", "fun"], //转换前的所有变量名和属性名
    “mappings”: "AAgBC,SAAQ,CAAEA" //记录位置信息的字符串
}

这也是为什么浏览器加载不到map文件的时候,报的是JSON的解析错误: image.png

在webpack中源码,mappings,打包后的代码三者是如何映射上的,有个可视化工具,有兴趣可以点来看看 source-map-visualization

webpack5中source map的生成

一般编译文件的工具都会伴随有生成sourcemap的功能,比如SASSTypeScript,这个tools 里面详细列举了有哪些支持sourcemap。以下着重讲一下webpack5中sourcemap的生成。
webppack4之后,development的模式下以eval的形式自动为你产生source maps,除了可以用devtool配置选项,还可以使用SourceMapDevToolPlugin/EvalSourceMapDevToolPlugin有更多的自定义及详细的配置选项,不过不要将devtoolplugin一起用,永远只选择一项

webpack5将devtool的值可以简化为该正则[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map,如何选择的问题总结来说从以下两个方面来讲:

⚠️注意:: 注意区分webpack的版本,每个版本略有不同,比如wepack4从cheap-module-eval-source-map改为webpack5的eval-cheap-module-source-map,webpack5对关键字的顺序有了严格的规定,以下以webpack5为例

1.生产环境

内联.map会增加打包的包大小,所以生产环境不会选择用内联inline的方式。所以webpack官网生产环境只推荐四个选项

  • none:不生成 source map
  • source-map:整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它
  • hidden-source-map:与 source-map 相同,但不会为 bundle 添加引用注释,应用场景经常是和错误监控系统一起使用,比如将sourcemap上传到sentry监控系统
  • nosources-source-map:不暴露源代码,但是出错的时候会有堆栈跟踪,可以看出具体哪个模块出错了,帮你确定了范围

那问题来了,生产环境下你会使用那个?

create-react-app在生产环境下是source-map的选项

  1. 你会选择让线上的用户看到源代码吗?太赤裸裸了,万一有个大佬直接给你线上code review(想太多),最主要还是因为安全性
  2. 那没有了map,我又该如何定位线上bug出错的原因?
  • 测试环境如果是内网的话,直接source-map问题也不大,保守点采用nosources-source-map模式,只暴露源码的目录结构与文件命名
  • hidden-source-map(打包文件没有引用关系)+ 监控系统(Eg:sentry),将map文件上传到sentry中,由它去关联定位线上错误
  • 网络拦截: 将map文件挑出放到本地服务器,将不含有map文件的部署到服务器,借助第三方软件(例如fiddler),将浏览器对map文件的请求拦截到本地服务器
  • Chrome开发者工具也有添加本地map文件的调试功能,但要一个个添加🤔

2. 开发环境:品质(Qualities) pk 速度(构建/重构建)

wepack官网对品质的介绍 生产环境下的除了nosources-source-map选项,都是原始源代码的品质,也就是你用map映射出来的源文件跟你实际开发的代码没有差别。而为什么开发环境下要有那么多配置呢?那就是速度跟品质的PK了,取决你debugger的时候定位错误的颗粒度 PK 构建和重新构建所需要的时间的权衡,先理解几个关键词

  • eval: 将每个模块都用eval包裹,由于提高的重新构建速度,所以开发环境下推荐使用
  • module: 含有Loaders产生的sourcemaps信息。比如你项目中用了less-loader去编译less,如果使用没含有的Loaders产生的sourcemaps信息的配置,如eval-cheap-source-map,你在浏览器中对看不到该样式对应的less文件,再比如如果你使用了babel-loader,出错了你只看到babel编译后的代码
  • cheap: 没有列信息及Loaders产生的sourcemaps信息。没有列信息?由于我们开发环境下为了可读性一行里面最多一个语句,所以对代码中的列的理解是模糊的。但是假设有这样一行代码console.log("你写了很多代码");throw Error("错误"),它只会告诉你这一行出错了,而不是定位到throw Error("错误")这个语句出错了。所以生产环境没有关于cheap的选项,因为生产环境为了减小体积下都是压缩成一行的。

了解了如上信息,你就理解为什么webpack官网在开发环境的推荐如下配置了

  • eval - 快速,每个模块都包裹在eval(),没有Loaders产生的sourcemaps信息
  • eval-source-map - 每个模块都使用 eval() 执行,初始化 source map 时比较慢,但是会在重新构建时提供比较快的速度,Loaders产生的sourcemaps信息,行数能够正确映射
  • eval-cheap-source-map - 为了更快,跟eval-source-map唯一的不同就是牺牲列信息跟Loaders产生的sourcemaps信息
  • eval-cheap-module-source-map - 跟eval-cheap-source-map的区别是有了Loaders产生的sourcemaps信息

那问题又来了,开发环境下你会使用那个?

  1. 没有Loaders产生的sourcemaps信息不能忍,因为现在很多代码都是经过Loader进行转换的,所以排除eval-cheap-source-map,eval
  2. 开发环境下你不会在一行内写多个语句,那使用eval-cheap-module-source-map何乐而不为呢。但是值得一提的是cheap-module-source-map这个配置(没有列信息,将 loader source map 简化为每行一个映射(mapping)),是目前Create-react-app脚手架采用的开发环境的默认sourcemap配置

官网还有对特定场景下使用的devtool配置做了介绍,webpack devtool 特殊场景

sourcemap会影响网页性能吗?

  • 如果你以inline内联的方式直接打包进文件,会。因为通常map会增大包的大小从而影响文件的加载。所以通常不会在生产环境下选择inline的方式嵌入source map,通常用于开发模式,提高构建/重构建的速度
  • 如果你是独立map文件,几乎不会。map文件只有浏览器开启开发者工具(F12),且开启source map的时候才会去请求相应的map文件。

⚠️注意:你在浏览器的network找不到相应的map请求的,浏览器把.map文件过滤掉了

参考