Webpack5学习 --- source-map

3,562 阅读6分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

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

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

因为对应的代码行号、列号和内容在编译前后肯定会不一致

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

简介

什么是source-map

  1. source-map是从已转换的代码,映射到原始的源文件

    ​ --- 是编译前的代码和编译后的代码的映射文件

    ​ --- source-map文件组合上编译后的代码,通过浏览器可以反向推导出编译前的源文件

  2. 让浏览器可以重构原始源并在调试器中显示重建的原始源;

    ​ --- 如果浏览器支持source-map, 那么浏览器在调试的时候会自动将错误映射到编译前的文件

如何开启source-map

  1. 根据源文件,生成source-map文件,webpack在打包时,可以通过配置生成source-map;

    --- 如 配置 devtoolsource-map

  2. 第二步:在转换后的代码,最后添加一个注释,它指向sourcemap

    // 1. source-map 的文件名一般为 xxx.js.map 其内容看起来是一个对象,map文件是不支持注释的 
    
    // 2. 在打包后的文件(如bundle.js)的最后一行,添加该文件所对应的source-map文件
    //    浏览器会根据我们的注释,查找相应的source-map,并且根据source-map还原我们的代码,方便进行调试
    
    // 3. source-map注释格式是固定的 需要以//# 开头
    //# sourceMappingURL=common.bundle.js.map
    
    // 4. 不单单js可以有source-map, css也可以有自己的source-map
    

source-map各字段含义

具体信息可以参考MDN中对于source-map的介绍

// source-map文件是不支持注释的,此处添加注释是为了便于理解
// 为了便于阅读,其中部分属性的值有做适度删减
{
  // source-map有3个版本
  /*
  	第一版: source-map生成的文件带下是原始文件的10倍
  	第二版:source-map生成的文件带下是原始文件的5倍   是第一版的50%
  	第三版:source-map生成的文件带下是原始文件的2.5倍  是第二版的50%
  */
  "version": 3,
    
  // 从哪些文件转换过来的source-map和打包的代码(最初始的文件)
  "sources": [
    "webpack://webpack-demo/./src/js/format.js", 
    "webpack://webpack-demo/./src/js/math.js", 
    "webpack://webpack-demo/webpack/bootstrap",
    "webpack://webpack-demo/./src/index.js"
  ],
    
  // 转换前的变量和属性名称,如果是development这类不需要转换名称的编译,names的值就是[]
  "names": [
    "module",
    "exports", 
    "num1", 	
    "__webpack_module_cache__", 
    "__webpack_require__" 
    ],
    
  // source-map用来和源文件映射的信息(比如位置信息等),一串base64 VLQ(veriable- length quantity可变长度值)编码 
  "mappings": "mCAAAA,EAAOC,QAAQC,MAAQ,IAAKC,QAAQC,IAAI,W,0ECAjC,MAAMC,EAAM,CAACC,EAAMC,IAASD,EAAOC,ICCtCC,EAA2B,GAG/B,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaV,QAGrB,IAAID,EAASQ,EAAyBE,GAAY,CAGjDT,QAAS,IAOV,OAHAY,EAAoBH,GAAUV,EAAQA,EAAOC,QAASQ,GAG/CT,EAAOC,QCpBfQ,EAAoBK,EAAI,SAASb,EAASc,GACzC,IAAI,IAAIC,KAAOD,EACXN,EAAoBQ,EAAEF,EAAYC,KAASP,EAAoBQ,EAAEhB,EAASe,IAC5EE,OAAOC,eAAelB,EAASe,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,MCJ3EP,EAAoBQ,EAAI,SAASK,EAAKC,GAAQ,OAAOL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,ICC/Fd,EAAoBkB,EAAI,SAAS1B,GACX,oBAAX2B,QAA0BA,OAAOC,aAC1CX,OAAOC,eAAelB,EAAS2B,OAAOC,YAAa,CAAEC,MAAO,WAE7DZ,OAAOC,eAAelB,EAAS,aAAc,CAAE6B,OAAO,K,qCCJvD,MAAMC,EAAO,EAAQ,KAErB,UAEA5B,QAAQC,IAAI2B,EAAK1B,IAAI,EAAE,I",
    
  // 打包后的文件(浏览器加载的文件)
  "file": "js/bundle.js",
    
   // 转换前的具体代码信息 --- 便于在点击的错误信息的时候,可以在sources标签页下查看对应的源代码
  "sourcesContent": ["module.exports.print = () =>console.log('format')", "export const sum = (num1, num2) => num1 + num2", "// 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", "// 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};", "import format from './js/format'\nconst math = require('./js/math')\n\nformat.print()\n\nconsole.log(math.sum(2,5))"],
    
  // 所有的sources相对的根目录
  "sourceRoot": ""
}

eval可选值

webpack为我们提供了非常多的选项(目前是26个),来处理source-map

不生成source-map

说明
false不使用source-map,也就是没有任何和source-map相关的内容
(none)production模式下的默认值,不生成source-map
(none)不是一个具体的值,其仅仅表示的是省略devtool选项
evaldevelopment模式下的默认值,不生成source-map
但是它会在eval执行的代码中,添加 //# sourceURL=
它会被浏览器在执行时解析,并且在调试面板中生成对应的一些文件目录,方便我们调试代码
// 在development模式下,每一个模块后会存在类似于//# sourceURL=webpack://webpack-demo/./src/js/format.js?"的代码
// 用以标明打包后的代码对应的源文件路径
eval("module.exports.print = () =>console.log('format')\n\n//# sourceURL=webpack://webpack-demo/./src/js/format.js?");

source-map

生成一个独立的source-map文件,并且在bundle文件中有一个注释,指向source-map文件

开发工具会根据这个注释找到source-map文件,并且解析

//# sourceMappingURL=bundle.js.map

eval-source-map

// 代码会使用eval函数包裹,不会生成独立的source-map
// source-map会以base64编码的形式嵌入到每一个打包后的代码后边
eval("module.exports.print = () =>console.log('format')//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly93ZWJwYWNrLWRlbW8vLi9zcmMvanMvZm9ybWF0LmpzPzhlNDIiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CIiwiZmlsZSI6Ii4vc3JjL2pzL2Zvcm1hdC5qcy5qcyIsInNvdXJjZXNDb250ZW50IjpbIm1vZHVsZS5leHBvcnRzLnByaW50ID0gKCkgPT5jb25zb2xlLmxvZygnZm9ybWF0JykiXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./src/js/format.js\n");

inline-source-map

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

cheap-source-map

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

因为在开发中,我们只需要行信息通常就可以定位到错误了

I8Nt8G.png

cheap-module-source-map

会生成sourcemap,类似于cheap-source-map,但是对源自loader的sourcemap处理会更好

即我们的一些模块会经过loader的处理,即会对我们的代码进行相应的转换

如果使用cheap-source-map,那么代码只能被还原到经过loader处理后的代码

如果使用的是cheap-module-source-map,代码会还原到经过loader处理前的代码

I8NNk4.png

hidden-source-map

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

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

// 下边这行注释被删除了
//# sourceMappingURL=bundle.js.map

如果我们需要调试,在对应文件末尾手动添加source-map

nosources-source-map

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

I8No8S.png

值的组合

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

 # inline-|hidden-|eval:三个值时三选一
 # nosources:可选值
 # cheap可选值,并且可以跟随module的值;
 [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
 # 此外 可选值还有 <none>(不写) 和 eval

在开发和测试阶段,推荐使用 source-map或者cheap-module-source-map

在发布和生产阶段,推荐使用false或<none>