CcbeanBlog CcbeanBlog
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Ccbean

靡不有初,鲜克有终
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 邂逅Webpack
  • Webpack的配置和处理CSS资源
  • Webpack加载和处理其它资源
  • Webpak模块化原理
  • Webpack中的source-map
    • 认识source-map
    • 使用source-map
    • 分析source-map
    • 生成source-map
      • 不生成source-map的配置值
      • 值source-map
      • 值eval-source-map
      • 值inline-source-map
      • 值cheap-source-map
      • 值cheap-module-source-map
      • 值hidden-source-map
      • 值nosources-source-map
    • 多个值组合
  • Webpack中的babel
  • Webpack中的DevServer和HMR
  • Webpack环境分离和代码分离
  • Webpack中的DLL、Terser和ScopeHoisting
  • Webpack中的TreeShaking以及其它优化
  • Webpack打包分析
  • Webpack自定义Loader
  • Webpack自定义Plugin
  • 分析React和Vue脚手架
  • Gulp的使用
  • Rollup的使用
  • Vite的使用
  • Webpack5使用学习
ccbean
2022-04-27
目录

Webpack中的source-map

# Webpack中的source-map

# 认识source-map

我们的代码通常运行在浏览器上时,是通过打包压缩的,也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的。

  • 比如ES6的代码可能被转换成ES5;

  • 比如对应的代码行号、列号在经过编译后肯定会不一致;

  • 比如代码进行丑化压缩时,会将编码名称等修改;

  • 比如我们使用了TypeScript等方式编写的代码,最终转换成JavaScript;

但是,当代码报错需要调试时(debug),调试转换后的代码是很困难的。

那么如何可以调试这种转换后不一致的代码呢?答案就是source-map

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

# 使用source-map

http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html

webpack在打包时,可以通过配置devtool选项生成source-map;

这里设置devtool: source-map并执行构建来看下效果。

我们可以看到bundle.js文件的最后一行是一行魔法注释:

//# sourceMappingURL=bundle.js.map

它指向对应的source-map文件bundle.js.map。

浏览器会根据这一行注释,查找响应的source-map,并且根据source-map还原我们的代码,方便进行调试。

可在Chrome浏览器中开启source-map,默认是开启的:

webpack中的source-map01

# 分析source-map

最初source-map生成的文件带下是原始文件的10倍,第二版减少了约50%,第三版又减少了50%,所以目前一个133kb的文件,最终的source-map的大小大概在300kb。

整个bundle.js.map代码如下:

{
  "version": 3,
  "file": "bundle.js",
  "mappings": ";;;;;;;;;AAAA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;ACXO;AACP;AACA;;AAEO;AACP;AACA;;;;;;;UCNA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCtBA;WACA;WACA;WACA,eAAe,4BAA4B;WAC3C,eAAe;WACf,iCAAiC,WAAW;WAC5C;WACA;;;;;WCPA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA,8CAA8C;;;;;WCA9C;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;;;;;;;;;;ACNA;AACA,QAAQ,WAAW,EAAE,mBAAO,CAAC,mCAAW;AACxC;AACsD;AACtD;AACA;AACA;AACA;AACA,YAAY,sDAAU;AACtB,YAAY,uDAAW",
  "sources": [
    "webpack://06_learn_webpack/./src/js/format.js",
    "webpack://06_learn_webpack/./src/js/math.js",
    "webpack://06_learn_webpack/webpack/bootstrap",
    "webpack://06_learn_webpack/webpack/runtime/compat get default export",
    "webpack://06_learn_webpack/webpack/runtime/define property getters",
    "webpack://06_learn_webpack/webpack/runtime/hasOwnProperty shorthand",
    "webpack://06_learn_webpack/webpack/runtime/make namespace object",
    "webpack://06_learn_webpack/./src/main.js"
  ],
  "sourcesContent": [
    "const dateFormat = (date) => {\n  return \"2020-12-12\";\n}\n\nconst priceFormat = (price) => {\n  return \"100.00\";\n}\n\nmodule.exports = {\n  dateFormat,\n  priceFormat\n}\n",
    "export const sum = (num1, num2) => {\n  return num1 + num2;\n}\n\nexport const mul = (num1, num2) => {\n  return num1 * num2;\n}\n",
    "// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n",
    "// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};",
    "// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};",
    "__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }",
    "// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};",
    "// commonjs模式导入es module\r\nconst { sum, mul } = require('./js/math');\r\n// es module模式导入commonjs\r\nimport { dateFormat, priceFormat } from './js/format';\r\n\r\nconsole.log(sum(20, 30));\r\nconsole.log(mul(20, 30));\r\n\r\nconsole.log(dateFormat(\"1213\"));\r\nconsole.log(priceFormat(\"1213\"));\r\n"
  ],
  "names": [],
  "sourceRoot": ""
}

字段含义如下:

  • version:当前使用的版本,也就是最新的第三版;
  • sources:从哪些文件转换过来的source-map和打包的代码(最初始的文件);
  • names:转换前的变量和属性名称(因为目前使用的是development模式,所以不需要保留转换前的名称);
  • mappings:source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable-length quantity可变长度值)编码;
  • file:打包后的文件(浏览器加载的文件);
  • sourceContent:转换前的具体代码信息(和sources是对应的关系);
  • sourceRoot:所有的sources相对的根目录;

关于source-map的具体细节可参考JavaScript Source Map 详解 (opens new window)

# 生成source-map

如何在使用webpack打包的时候,生成对应的source-map呢?

webpack为提供了非常多的选项(目前是26个),来处理source-map,详见Devtool (opens new window)。

选择不同的值,生成的source-map会稍微有差异,打包的过程也会有性能的差异,可以根据不同的情况进行选择。

例如我们可以设置none、eval、eval-cheap,这里不需要记住每个值的含义,只需要理解其中几个关键字eval、cheap、inline、source-map、nosources的含义,其它值都是这些关键字的组合。

# 不生成source-map的配置值

下面几个值不会生成source-map:

  • false:不使用source-map,也就是没有任何和source-map相关的内容。
  • (none):(省略devtool),即什么都不写,production模式下的默认值,不生成source-map。
  • eval:development模式下的默认值,不生成source-map。
    • 但是它会在eval执行的代码中,添加 //# sourceURL=xxx的注释,会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码;

设置devtool: 'eval'可看到生成代码中有如下片段:

eval("const dateFormat = (date) => {\n  return \"2020-12-12\";\n}\n\nconst priceFormat = (price) => {\n  return \"100.00\";\n}\n\nmodule.exports = {\n  dateFormat,\n  priceFormat\n}\n\n\n//# sourceURL=webpack://06_learn_webpack/./src/js/format.js?");

我们编写的代码,都会转成了字符串代码包裹在eval函数中,之所以这样来写就是为了可以在函数结尾加上注释://# sourceURL=webpack://06_learn_webpack/./src/js/format.js?,这个注释只会在eval函数中生效。目的就是为了让这行注释生效,让我们的打包后的代码可以在浏览器中被重新转回到对应文件中,方便进行调试。

在format.js文件中添加一行代码如下









 






const dateFormat = (date) => {
  return "2020-12-12";
}

const priceFormat = (price) => {
  return "100.00";
}

console.log(hello)

module.exports = {
  dateFormat,
  priceFormat
}

重新打包后再运行页面,很显然会有如下报错

format.js:9 Uncaught ReferenceError: hello is not defined
    at eval (format.js:9:13)
    at Object../src/js/format.js (bundle.js:18:1)
    at __webpack_require__ (bundle.js:64:41)
    at eval (main.js:2:68)
    at Object../src/main.js (bundle.js:40:1)
    at __webpack_require__ (bundle.js:64:41)
    at bundle.js:116:37
    at bundle.js:118:12

点击打开后可看到浏览器已经根据eval中的注释将我们的代码还原了。

image

但是可以注意到,浏览器并未还原所有的JS代码文件,这里就没有还原math.js文件。因为执行到此处时已经报错了,JS代码不再继续执行,就只还原了已经执行的eval函数中的原代码。

这个参数也不会还原所有的文件,这里的导入使用的还是webpack中实现的加载函数

webpack中的source-map06

# 值source-map

source-map:会生成一个独立的source-map文件,并且在bundle文件中末尾有一个注释//# sourceMappingURL=xxx.js.map,指向source-map文件。还原所有源文件

设置devtool: 'source-map'再进行打包,我们可以在浏览器中看到还原代码:

webpack中的source-map03

它会还原我们代码中使用到的所有代码,包括webpack中使用到的代码,以及我们引用库中的代码。

这样无论是哪里的代码出现错误,都可以准确地定位到。

# 值eval-source-map

eval-source-map:会生成sourcemap,但是source-map是以DataUrl添加到eval函数的后面,即以base64为转码的source-map添加到eval函数的后面。只还原执行到的源文件

配置devtool: 'eval-source-map'再进行打包,可以看到打包代码使用eval函数包裹,末尾添加了base64位编码的字符串:

eval("const dateFormat = (date) => {\n  return \"2020-12-12\";\n}\n\nconst priceFormat = (price) => {\n  return \"100.00\";\n}\n\nconsole.log(hello)\n\nmodule.exports = {\n  dateFormat,\n  priceFormat\n}\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvanMvZm9ybWF0LmpzLmpzIiwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8wNl9sZWFybl93ZWJwYWNrLy4vc3JjL2pzL2Zvcm1hdC5qcz84ZTQyIl0sInNvdXJjZXNDb250ZW50IjpbImNvbnN0IGRhdGVGb3JtYXQgPSAoZGF0ZSkgPT4ge1xuICByZXR1cm4gXCIyMDIwLTEyLTEyXCI7XG59XG5cbmNvbnN0IHByaWNlRm9ybWF0ID0gKHByaWNlKSA9PiB7XG4gIHJldHVybiBcIjEwMC4wMFwiO1xufVxuXG5jb25zb2xlLmxvZyhoZWxsbylcblxubW9kdWxlLmV4cG9ydHMgPSB7XG4gIGRhdGVGb3JtYXQsXG4gIHByaWNlRm9ybWF0XG59XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./src/js/format.js\n");

浏览器也只是还原了部分源文件,也就是说置只还原了已经执行的JS文件,未执行的不还原。

webpack中的source-map04

# 值inline-source-map

inline-source-map:会生成sourcemap,但是source-map是以DataUrl添加到bundle文件的末尾,即base64编码的source-map添加到bundle文件的末尾。还原所有源文件

配置devtool: 'inline-source-map'再进行打包,在bundle.js文件末尾我们可以看到如下注释:

//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozt...

浏览器中还原了所有的源文件:

webpack中的source-map05

# 值cheap-source-map

cheap-source-map:会生成sourcemap,但它不生成列映射(Column Mapping),只精确到行。会更加高效一些(cheap低开销)。

但是目前发现配合loader使用时候才可以看到差别,目前测试不使用loader时,所有配置都是一整行都会标红。

这里配合babel-loader进行测试,整行标红:

webpack中的source-map07

修改devtool: source-map可以看到效果,具体到列标红:

webpack中的source-map10

# 值cheap-module-source-map

cheap-module-source-map:会生成sourcemap,类似于cheap-source-map,但是对源自loader的sourcemap处理会更好。也就是说如果使用loader对我们的源码进行了特殊的处理,比如babel,那么使用此配置会更好地还原原代码。

这里使用babel-loader做下演示,安装babel相关依赖

npm i @babel/core @babel/preset-env babel-loader -D

在webpack中配置babel:

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

此时,如果使用devtool: cheap-source-map进行打包,我们会看到浏览器还原的的代码文件是ES5风格的,具体对应的源文件哪一行也有误差(源文件有15行,这里13行):

webpack中的source-map08

改用devtool: cheap-module-source-map再进行打包,浏览器会还原成我们的源代码风格:

webpack中的source-map09

# 值hidden-source-map

hidden-source-map:会生成sourcemap,但是不会对source-map文件进行引用;相当于删除了打包文件中对sourcemap的引用注释;即删除打包文件最后一行的注释//# sourceMappingURL=bundle.js.map。

配置devtool: 'hidden-source-map'进行打包,我们可以看到打包文件bundle.js中没有最后一行的注释,但是也生成了相应的bundle.js.map文件。

如果我们手动添加进来注释,那么sourcemap就又会生效了。bundle.js末尾添加注释,sourcemap就会又生效。

//# sourceMappingURL=bundle.js.map

# 值nosources-source-map

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

配置devtool: 'hidden-source-map'进行打包,在浏览器的控制台中我们可以看到正确的错误提示:

webpack中的source-map11

但是点击跳转后,可以看到没有对应的原代码文件

webpack中的source-map12

# 多个值组合

事实上,webpack提供给我们的26个值,是可以进行多组合的。26个值就是上面几个值的组合。

组合的规则如下:

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

语法如下:

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

那么在开发中,最佳的实践是什么呢?

  • 开发阶段:推荐使用source-map或者cheap-module-source-map。
    • 这分别是Vue和React使用的值,可以获取调试信息,方便快速开发;
  • 测试阶段:推荐使用source-map或者cheap-module-source-map
    • 测试阶段我们也希望在浏览器下看到正确的错误提示
  • 发布阶段:false、缺省值(不写)
编辑 (opens new window)
上次更新: 2022/05/08, 09:21:39
Webpak模块化原理
Webpack中的babel

← Webpak模块化原理 Webpack中的babel→

最近更新
01
阅读精通正则表达式总结
09-29
02
项目搭建规范的配置
07-15
03
Vite的使用
07-03
更多文章>
Theme by Vdoing | Copyright © 2018-2023 Ccbeango
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式