likes
comments
collection
share

一文加深你对 webpack 的 Source Map 理解

作者站长头像
站长
· 阅读数 15

单词直译

source: 源。比如说:源码(source code)

map: 映射

单词组合 + 场景分析(构建工具,打包): 源文件映射

概念描述

Source Map 就是一个信息文件,里面存储了代码打包转换后的位置信息,实质是一个 json 描述文件,维护了打包前后的代码映射关系。

为什么需要 Source Map

无论针对 React 开发,还是 Vue 开发,如果它们的构建工具是使用 webpack 的话,那么针对本地运行,或者时说部署到线上,都是使用了一个构建好的打包文件(就是把多文件转化为一个文件,分包除外)。

一文加深你对 webpack 的 Source Map 理解

使用一份测试 demo,做解释:

无论是本地运行,还是部署线上都是针对 build.js 文件的运行和部署,src 文件夹的文件称为源文件,但是都会集中合并到 build.js 文件中,这一过程叫做打包

打包过程中会发生:

  1. ES6 转化为 ES5;
  2. TypeScript 转化为 JavaScrip;
  3. 代码丑化,变量名称发生变化;
  4. ...

上面一些列的操作,就会出现行号,列号都会在编译后发生变化。

那么这里问题就出现了,如果源文件中的代码出现了错误,那么也会把错误代码打包到 build.js,然后运行 build.js 代码,浏览器就会飘红,那么这时候就需要解决错误,但是呢?

生产环境:

一文加深你对 webpack 的 Source Map 理解

开发环境:

一文加深你对 webpack 的 Source Map 理解

生产环境是经过丑化后的代码,只有一行代码,所以错误提示出现在 build.js:1,错误信息等于没说, 一脸懵逼。

开发环境的错误提示 build.js:37,虽然也不知道具体的,但是可以深入看错误信息,也可以知道源文件哪里存在错误。

但是呢,这些针对开发者都不是很友好的。

因此,source map 就出现了,就是为了解决此类问题。

  • 从已转换的代码,映射到原始的源文件;
  • 使浏览器可以重构原始文件源并在调试器中显示重建的原始文件源

配置 source map 之后,快速定位错误文件及行数。

一文加深你对 webpack 的 Source Map 理解

webpack 配置 Source Map

webpack 配置提供了一个属性: devtool ;此选项控制是否生成,以及如何生成 source map。

module.exports = {
  mode: "development",
  devtool: 'source-map'
};

如果 mode 为 production,devtool 的默认值为 none

如果 mode 为 development,devtool 的默认值为 eval

那么针对不是默认值,需要手动设置的话,可以设置哪些值呢?devtool 的属性值可以高达 26 种

www.webpackjs.com/configurati…

在上面的网址可以看看到底存在哪些属性值?有什么区别?适用用于什么环境?构建与重构建的速度?

在下面会抽出几个属性值来学习学习,不过在学习的前面,先来认识认识 source map 文件,简单的理解一下,为后面的学习打点基础。

Source Map 文件的认识

生成最简单的 source map 文件

module.exports = {
  mode: "production",
  devtool: 'source-map'
};

执行脚本,生成打包文件的同时也会生成打包文件的 source map 文件。

一文加深你对 webpack 的 Source Map 理解

这里的 mappings 属性是一串 base64 编码,里面保存了源文件的行号等一些列信息。重构源文件,就是从里面读取信息。

sourceRoot:所有的 sources 相对的根目录(没有截图出来)

浏览器识别 Source Map

浏览器运行的是 build.js 文件,那么是怎么关联的 source map 文件呢?

一文加深你对 webpack 的 Source Map 理解

细心可以发现,build.js 文件的最后一行存在://# sourceMappingURL=build.js.map,那么浏览器就会去解析 sourceMappingURL,获取对应的 source map 文件,并且根据 source map 重构源文件,还原代码,方便进行调试。

一文加深你对 webpack 的 Source Map 理解

谷歌浏览器是默认打开的,支持解析 source map 文件;如果关闭了这个选项,就不会重新构建源文件了。

一文加深你对 webpack 的 Source Map 理解

devtool 的属性值

devtool 的属性值,决定了是否生成,以及如何生成 source map。

在学习配属属性值之前,希望你去看看 www.webpackjs.com/configurati… ,这里里面写了所有的属性值,以及适用环境,构建与重构建的速度。

一文加深你对 webpack 的 Source Map 理解

下面就来学习几个常见的属性

false

不使用 source map,也就是没有任何和 source map 相关的内容

none

production 模式下的默认值(什么值都不写) ,不生成 source map 文件。

是否适用生产环境:yes。

速度:build: fastest;rebuild: fastest

注意在 development 模式下,配置该属性会报错。

eval

development 模式下的默认值,不生成source map 文件。

是否适用生产环境:no。

速度:build: fast;rebuild: fastest

但是会在 eval 函数后面添加 sourceURL 信息

{
  // webpack 对应模块
  __webpack_modules__: {
    "./src/utils/format.js": eval("function upper(str) {\n  return str.toLocaleUpperCase();\n}\n\nmodule.exports = {\n  upper,\n};\n\n\n//# sourceURL=webpack://weboack_study/./src/utils/format.js?");
  }
}

上面的 __webpack_modules__ 对象,如果理解 webpack 的打包原理,就很好理解这个属性。如果不理解打包原理也没有太大关系,只需要知道 eval 函数里面最后处有一个注解(sourceURL) ,映射源文件的路径。

source-map

生成 source map 文件,最全的信息。

是否适用生产环境:yes。

速度:build: slowest;rebuild: slowest

在前面的 Source map文件的认识已经对该属性的生成文件,做了一定的解释。

也就是在 build.js 文件的最后处有一个注解(sourceMappingURL) ,指向 source map 文件。

eval-source-map

会生成 source map,但是 source map 是以 DataUr l添加到 eval 函数的后面。

是否适用生产环境:no。

速度:build: slowest;rebuild: ok;

简单理解就是生成的 source map 是直接插入到 eval 函数的最后面。

截图太长不好截全,直接代码展示也许更好懂。后面添加了//# sourceMappingURL=data:application/json;......

eval(
  "function upper(str) {\n  return str.toLocaleUpperCase();\n}\n\nmodule.exports = {\n  upper,\n};\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvdXRpbHMvZm9ybWF0LmpzLmpzIiwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly93ZWJvYWNrX3N0dWR5Ly4vc3JjL3V0aWxzL2Zvcm1hdC5qcz82YmU4Il0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIHVwcGVyKHN0cikge1xuICByZXR1cm4gc3RyLnRvTG9jYWxlVXBwZXJDYXNlKCk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0ge1xuICB1cHBlcixcbn07XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./src/utils/format.js\n"
);

inline-source-map

会生成 sourcemap,但是 source-map 是以 DataUrl 添加到 bundle 文件的后面。

是否适用生产环境:no。

速度:build: slowest;rebuild: slowest

在 build.js 文件的后面插入注解 (sourceMappingURL) ,指向 DataUrl。

一文加深你对 webpack 的 Source Map 理解

cheap-source-map

会生成 source map,但是会更加高效一些(cheap低开销),因为它没有生成列映射(Column Mapping) 。

是否适用生产环境:no。

速度:build: ok;rebuild: slow

一文加深你对 webpack 的 Source Map 理解

看看报错信息,cheap-source-map 省略了具体的列号,没有生成列映射

cheap-module-source-map

会生成 source map 文件,类似于cheap-source-map,但是对源自 loader 的 source map 处理会更好。什么意思呢?

就比如 babel-loader 对 js 代码进行转化(源代码 -> AST -> 新源代码),就是 cheap-source-map 在构建源文件的时候,把空行去掉了,那么报错的位置的行号也就相应的错位了。

cheap-module-source-map 就不会把空行去掉,展示正确的行号。

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        use: ['babel-loader']
      }
    ]
  },
}

一文加深你对 webpack 的 Source Map 理解

hidden-source-map

会生成 source map,但是不会对 source-map 文件进行引用;

相当于删除了打包文件中对 source map 的引用注释;

一文加深你对 webpack 的 Source Map 理解

报错信息定位到 build.js 文件。如果手动加上 注解(sourceMappingURL) ,报错信息就会定位源文件了。

nosources-source-map

会生成 source map,但是生成的 source map只有错误信息的提示,不会生成源代码文件;

一文加深你对 webpack 的 Source Map 理解

但是报错信息,还是显示的正确的行号和列号,只是不能进行点击,进入源文件(或者源文件的内容对应不上)

devtool 的属性值组合

devtool 的属性值有 26 个,总不可能一一去牢记,那么直接裂开。

上面说了一些 source map 的特性,以及效果场景。

其他的就不用了介绍了,其他的属性值,一个公式就可以解决了。

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

  • inline-|hidden-|eval: 三个值时三选一;
  • nosources: 可选值;
  • cheap可选值,并且可以跟随 module 的值;

devtool 属性值的适用场景

开发阶段:推荐使用 source-map 或者 cheap-module-source-map

  • vue-cli 采用的是 source-map
  • create-react-app 采用的是 cheap-module-source-map

发布阶段:false、缺省值(不写)

  • 客户不需要看懂错误信息
  • 防止黑客看懂错误信息,发起攻击。

总结

相信各位掘友对 webpack 的 source map 有了一定的认知。

如有错误,请指教。