[webpack学习]梳理一下sourcemap的知识点

1,277 阅读8分钟

sourcemap的作用

在生产环境和开发环境下,经过webpack打包的代码在运行发生错误时,控制台报出的错误信息让人很难定位。如下:

如果我们引入sourcemap,在webpack的配置文件中导出配置添加devtool如下所示:

webpack.config.js

module.exports = {
      mode:'production',
      devtool:'source-map', // 添加devtool配置
    //..以下配置省略
}

则同样的代码以及同样的抛出错误,控制台报出的信息会更详细。如下:

为什么运行的是已经打包压缩后的代码,然而在输出报错时可以精准定位到开发代码。其中是借助sourcemap实现的,sourcemap作用在于映射转换过后的代码与源代码之间的关系。

sourcemap的原理

这里简单说一下sourcemap的原理,当经过启用了sourcemap的webpack打包时,会对每个打包压缩后的文件生成对应的map代码。我们拿jquery1.10.2map代码作例子,其结构如下:

{
      "version": 3, // SourceMap的版本,目前为3
      "file": "jquery.min.js", // 转换后的文件名称
      "sources": ["jquery.js"], // 转换前的文件名称的集合,因为转换后的文件可能是多个文件打包合并的,所以此属性的值是数组类型
      "names":[ // 源代码使用的成员名称,压缩代码时会通过把变量名替换成简短的形式以减少体积。故此值用于记录原始对应的名称
      	"global",
        "factory",
        ...
      ],
      "mappings":";;;CAaA,SAAWA,EAAQC,GAOnB,..." // BASE64-VLQ编码的字符串,记录转换后的代码和源代码之间的映射关系
      // "sourcesContent": "", 转换后的代码,jquery的map不存在此项
      // "sourceRoot" : "" 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空,jquery的map不存在此项
}

拓展: 至于里面的mappings是怎么映射的,可以看:阮一峰 JavaScript Source Map 详解

不同的sourcemap会对该map代码的放置方式不同。

  • inline-*eval-*命名的sourcemap会把map代码内联到对应的打包后的原文件中,
  • 而其他的会把map代码放置到与对应的打包压缩后的原文件同名的以.map为后缀的新的文件中。

devtool的分类

为什么不直接说sourcemap的不同模式?因为devtool中除了可以设置"sourcemap"模式还可以设置"eval"模式。这里先介绍"eval""sourcemap",后再介绍"sourcemap"的不同模式。

以下的构建重构分别指webpack初次构建和在监视模式下文件变化时的重新构建。

eval

效果: 该模式下,每个模块都使用 eval() 执行,并且都有 //@ sourceURL。但其错误定位在经过webpack打包生成的独立模块的代码上。如下:

使用场景: 此模式下,webpack无论是初次构建还是重新构建的速度都快。主要缺点是,由于会映射到转换后的独立模块代码,而不是映射到源代码(没有从 loader 中获取 sourcemap),所以不能正确的显示行数。主要在简单的项目中的开发模式下可以使用。

source-map

效果: 每个打包后的生成的文件都会生成一个对应的map文件作为转换代码和源代码之间的映射。对比于上述的eval模式,source-map模式的定位精准度高很多,可以定位到列位置。

使用场景: 虽然精准度高,然而构建速度和重构速度非常的慢,因此用于对错误代码定位要求高的生产环境中。

在如今前端框架普遍使用的时代,用eval模式定位错误非常鸡肋,然而用source-map模式时webpack的构建速度和重构又非常慢。针对此,source-map模式下又分了好几种模式,以对其构建过程进行一些调整从而达到加快webpack的构建和重构速度。

我们可以引用webpackdevtool的校验规则去了解sourcemap的分类

验证 devtool 名称时, 我们期望使用某种模式, 注意不要混淆 devtool 字符串的顺序, 模式是: [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map.

inline-*

效果: 该模式下会把生成的map文件转换为DataUrl后以//# sourceMappingURL=DataUrl添加到转换后的文件中中。如下所示:

使用场景: 这种模式下生成的map文件体积较大,所以一般在开发模式和生产模式都不会被使用。他们是一些特定场景下需要的,例如,针对一些第三方工具。

hidden-*

效果: 该模式下会生成map文件,但不会为打包后的代码添加引用注释。因此报错时控制台的输出错误效果会和没设置生成sourcemap的一样。

使用场景: 该模式适用于生产环境,尤其是用于开发第三方包,开发者想生成map文件而达到映射那些源自错误报告的错误堆栈跟踪信息,但不想在打包代码中引用。

当使用第三方包的开发者想引用map文件时,可以手动通过注入//# sourceMappingURL=(map文件名称)把这个包引用回来。目前jquery就是用这种模式。

eval-*

效果: 该模式下打包生成的每个模块使用eval()执行,并且map文件会转换为DataUrl后添加到eval()中。如下所示:

使用场景: 第一次构建map文件时比较慢,但在重新构建时速度较快。eval-cheap-source-map,eval-cheap-module-source-map以及eval-source-map都适用于开发环境

nosources-*

效果: 该模式下,当抛出错误时,输出的错误栈会详细列出错误来源,但当点击错误栈查看源码时,源码为空或无法加载。

使用场景: 可以在生产环境下使用,在错误抛出时找到错误来源的同时,可以保护源代码不会被暴露。

cheap-*

效果: 使用该模式下,map文件没有生成列映射(column mapping),只是映射行数。下面可以对一下cheap-source-mapsource-map

使用cheap-source-map:

使用source-map:

使用场景: 可以看出使用cheap-*会让错误信息只定位到行,较source-map的定位到列会显得稍微不太精准。但其好处就是低开销,构建与重构速度快。无论哪种环境都可以用该模式以准确性为代价提高构建速度。

cheap-module-*

效果: 使用该模式下,错误栈定位的代码是loader加工转换前的,否则,定位的代码是loader加工转换后的。下面详细说明以下:

假设下面是开发时的源代码:

/src/index.js

const fn=()=>{
  console.log1('hello')
}

fn()

webpack.config.js中的module.rules这么设置:

module:{
  rules:[
    {
      test:/\.js$/,
      use:{
        loader:'babel-loader',
        options:{
          presets:['@babel/preset-env']
        }
      }
    }
  ]
},

此时 /src/index.js中的箭头函数在打包过程中经过babel-loader的转换会被替换成普通函数。

现在再对比一下cheap-source-mapcheap-module-source-map

cheap-source-map:

cheap-module-source-map:

对比得知,cheap-module-source-map定位的是loader转换前的代码,cheap-source-map定位的是loader转换后的代码。其错误定位是到源代码行的,因为cheap-module-* 模式是在cheap-* 模式下进一步调整的。

使用场景: 不分环境,能提高定位的精准度,但会降低初次构建map文件的速度。

如何选择适合的sourcemap

同上面不同的sourcemap模式的特性可知,有些适合在开发环境使用,有些适合生产环境使用,有些适合特殊环境下使用。

下面来总结一下不同环境下适用的sourcemap

开发模式

以下选项非常适合开发环境:

devtool构建速度重构速度定位精准度
eval-source-mapslowestok源代码
eval-cheap-source-mapokfastloader转换后的代码
eval-cheap-module-source-mapslowfast源代码行

生产环境

这些选项通常用于生产环境中:

devtool构建速度重构速度定位精准度
(none)fastestfastest生成且打包后的代码
source-mapslowestslowest源代码
hidden-source-mapslowestslowest源代码
nosources-source-mapslowestslowest源代码

个人认为:一般不允许普通用户访问sourcemap文件,因此,不能把sourcemap文件部署到线上(RC)环境。

特殊环境

以下选项对于开发环境和生产环境并不理想。他们是一些特定场景下需要的,例如,针对一些第三方工具。

devtool构建速度重构速度定位精准度
inline-source-mapslowestslowest源代码
cheap-source-mapokslowloader转换后的代码
inline-cheap-source-mapokslowloader转换后的代码
cheap-module-source-mapslowslow源代码行
inline-cheap-module-source-mapslowslow源代码行

其余更详细的sourcemap请查看webpack devtool