一文了解source-map

2022-10-24 18:23:45 浏览数 (1)

一文了解source-map

背景

作为一个开发工程师——无论是什么开发,要求开发环境最不可少的一点功能就是——debug功能。当我们通过webpack 将我们的源码打包成了 bundle.js 。试想:实际上客户端(浏览器)读取的是打包后的bundle.js ,那么当浏览器执行代码报错的时候,报错的信息自然也是bundle的内容。我们如何将报错信息(bundle错误的语句及其所在行列)映射到源码上?为了解决这个问题,google 提出了sourcemap 的想法,并在chorme上最先支持sourcemap的使用。sourcemap可以帮我们直接定位到编译前代码的特定位置。

webpack已经内置了sourcemap的功能,我们只需要通过简单的配置,将可以开启它。

代码语言:javascript复制
module.exports = { 
    // 开启 source map 
    // 生产环境一般不开启 sourcemap 
    devtool: 'source-map', 
}

source-map外,还可以基于我们的需求设置其他值,webpack——devtool官网一共提供了多种Sourcemap模式:[官网链接](Devtool | webpack 中文文档 (docschina.org))

这么多种配置项其实只是五个关键字 evalsource-mapcheapmoduleinline的组合罢了,下面列出它们的意义:

「关键字」

「含义」

特点

source-map

产生.map 文件

定位信息最全,但也.map 文件最大,效率最低

eval

使用 eval 包裹模块代码

利用字符串可缓存从而提效

cheap

不包含列信息,也不包含 loader 的 sourcemap

精准度降低换取文件内容的缩小

module

包含 loader 的 sourcemap(比如 jsx to js ,babel 的 sourcemap),否则无法定义源文件

解决对于使用cheap 配置项导致无法定位到 loader 处理前的源代码问题

inline

将.map 作为 DataURI 嵌入,不单独生成.map 文件

减少文件数

举例

为了方便演示,这里的源代码只包含了一行代码:

代码语言:javascript复制
console.log('hello webpack');

source-map

代码语言:javascript复制
module.exports = { 
    // 开启 source map 
    // 生产环境一般不开启 sourcemap 
    devtool: 'source-map', 
}

当我们执行打包命令之后,我们发现bundle的最后一行总是会多出一个注释,指向打包出的bundle.map.js(sourcemap文件)。sourcemap文件用来描述 源码文件和bundle文件的代码位置映射关系。基于它,我们将bundle文件的错误信息映射到源码文件上。

代码语言:javascript复制
console.log("hello webpack");
//# sourceMappingURL=main.built.js.map

接下来我们看看sourcemap文件包含了一些什么信息:

代码语言:javascript复制
{
  "version": 3,
  "file": "main.built.js",
  "mappings": "AAAAA,QAAQC,IAAI",
  "sources": ["webpack://webpack-demo/./src/index.js"],
  "sourcesContent": ["console.log('hello webpack');"],
  "names": ["console", "log"],
  "sourceRoot": ""
}

上面可以看到,sourcemap其实就是就是一段维护了前后代码映射关系的json描述文件,包含了以下一些信息:

  • versionsourcemap版本(现在都是v3)
  • file:转换后的文件名。
  • sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
  • sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。
  • names:转换前的所有变量名和属性名。
  • mappings:记录位置信息的字符串。mappings 信息是关键,它使用Base64 VLQ编码,包含了源代码与生成代码的位置映射信息。mappings的编码原理详解可见:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html,这里就不再详述。

eval

代码语言:javascript复制
module.exports = { 
    // 开启 source map 
    // 生产环境一般不开启 sourcemap 
    devtool: 'source-map', 
}

输出的 bundle文件如下:

代码语言:javascript复制
(() => {
  var __webpack_modules__ = {
      138: () => {
        eval("console.log('hello webpack');nn//# sourceURL=webpack://webpack-demo/./src/index.js?")
      }
    },
    __webpack_exports__ = {};
  __webpack_modules__[138]()
})();

每个module会封装到 eval 里包裹起来执行,并且会在末尾追加注释//@ sourceURL。只是它映射的是转换后的代码,而不是映射到原始代码。

eval-source-map

代码语言:javascript复制
module.exports = { 
    // 开启 source map 
    // 生产环境一般不开启 sourcemap 
    devtool: 'eval-source-map', 
}

输出的 bundle文件如下:

代码语言:javascript复制
(() => {
  var __webpack_modules__ = {
      138: () => {
        eval("console.log('hello webpack');//# sourceURL=[module]n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMTM4LmpzIiwibWFwcGluZ3MiOiJBQUFBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vd2VicGFjay1kZW1vLy4vc3JjL2luZGV4LmpzP2I2MzUiXSwic291cmNlc0NvbnRlbnQiOlsiY29uc29sZS5sb2coJ2hlbGxvIHdlYnBhY2snKTsiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=n//# sourceURL=webpack-internal:///138n")
      }
    },
    __webpack_exports__ = {};
  __webpack_modules__[138]()
})();

可以看到,加了eval的配置生成的sourcemap会作为DataURI嵌入,不单独生成.map文件。

inline-source-map

代码语言:javascript复制
module.exports = { 
    // 开启 source map 
    // 生产环境一般不开启 sourcemap 
    devtool: 'inline-source-map', 
}

输出的 bundle文件如下:

代码语言:javascript复制
console.log("hello webpack");
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5idWlsdC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsUUFBUUMsSUFBSSIsInNvdXJjZXMiOlsid2VicGFjazovL3dlYnBhY2stZGVtby8uL3NyYy9pbmRleC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjb25zb2xlLmxvZygnaGVsbG8gd2VicGFjaycpOyJdLCJuYW1lcyI6WyJjb25zb2xlIiwibG9nIl0sInNvdXJjZVJvb3QiOiIifQ==

可以看出它将map作为DataURI嵌入,不单独生成.map文件。

cheap-source-map

代码语言:javascript复制
module.exports = { 
    // 开启 source map 
    // 生产环境一般不开启 sourcemap 
    devtool: 'cheap-source-map', 
}

接下来我们看看sourcemap文件包含了一些什么信息:

代码语言:javascript复制
{
  "version": 3,
  "file": "main.built.js",
  "mappings": "AAAA",
  "sources": ["webpack://webpack-demo/./src/index.js"],
  "sourcesContent": ["console.log('hello webpack');"],
  "names": [],
  "sourceRoot": ""
}

可以看到比之前source-map的,mappings字段短了很多,实际上是因为它没有生成「列映射」(column mapping),只是「映射行数」

cheap-module-source-map

Webpack会利用loader将所有非js模块转化为webpack可处理的js模块,而增加上面的cheap配置后也不会有loader模块之间对应的sourceMap

什么是模块之间的sourceMap呢?比如jsx文件会经历loader处理成js文件再混淆压缩, 如果没有loader之间的sourcemap,那么在debug的时候定义到上图中的压缩前的js处,而不能追踪到jsx中。

所以为了映射到loader处理前的代码,我们一般也会加上module配置。

总结

开发环境

在开发环境中,我们希望速度快,调试更友好。

以此这里推荐配置:eval-cheap-module-souce-map

生产环境

生产环境我们一般不会开启sourcemap功能,主要有两点原因:

  1. 通过bundlesourcemap文件,可以反编译出源码————也就是说,线上产物有soucemap文件的话,就意味着有暴漏源码的风险。
  2. 我们可以观察到,sourcemap文件的体积相对比较巨大,这跟我们生产环境的追求不同(生产环境追求更小更轻量的bundle)。

0 人点赞