SourceMap 知多少:介绍与实践

5

module

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

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

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

6

总结

1、开发环境

综上所述,考虑到我们在开发环境对sourceMap的要求是:快(eval),信息全(module),且由于此时代码未压缩,我们并不那么在意代码列信息(cheap),所以开发环境比较推荐配置: devtool: cheap-module-eval-source-map

2、生产环境

一般情况下,我们并不希望任何人都可以在浏览器直接看到我们未编译的源码,所以我们不应该直接提供sourceMap给浏览器。但我们又需要sourceMap来定位我们的错误信息, 这时我们可以设置 hidden-source-map

一方面webpack会生成sourcemap文件以提供给错误收集工具比如 sentry ,另一方面又不会为 bundle 添加引用注释,以避免浏览器使用。

当然如果没有这一类的错误处理工具,可以看看webpack推荐的其他配置:

https://www.webpackjs.com/configuration/devtool/

03

CSS sourceMap

说起sourceMap我们第一反应通常是JavaScript的sourceMap,实际上现在css也可以使用sourceMap。因为sourceMap本质只是一个json,里面包含了源码的映射信息。所以其实只要了解sourcemap的编码规范,我们可以对任何我们想要的资源生成sourceMap,当然sourceMap 的支持也还是要取决于浏览器的支持。

现在,对于css我们也有同样诉求,比如我现在打开调试器看到的样式配置没有任何源信息。如果想像js一样,知道这个css样式是在哪个文件需要怎么弄呢?

上面讲解的配置其实都是针对js的sourceMap,配置后webpack会自动帮我们生成各类js sourceMap。因为本质上webpack只处理js,对于webpack来说,css是否有sourceMap依赖于对css处理的loader是否有sourceMap输出,所以loader需要开启并传递sourceMap,这样最后生成的css才会带上sourceMap 。

目前使用的css-loader,sass-loader都已经提供了生成sourceMap的能力,只需要我们加上配置即可。

需要注意的是,这里如果要拿到sass编译前的源码信息,那么sourceMap一定要从sass-loader一直传递到css-loader,中间如有其他loader处理,也要透传sourceMap

我们可以看到,加了sourceMap 配置后,sourceMap会被内联在css代码里(这一层是css-loader处理的,与你是否使用min-extract-css-plugin抽出css无关)

加了css sourceMap后,我们可以很轻松的定位到sass编译前的源码路径了。

通过debug,打印出生成的css sourceMap,和js sourceMap对比并无他样:

0 4

利用css sourceMap 解决css url resolve的问题

如果大家用了sass的话,很可能会遇到一个css url resolve的问题,在之前的一篇讲webpack 配置的文章里我也提到过:

实际上,利用css sourceMap这个问题便可以在不改变源码的情况下就可以完美解决。

这里会增加一个loader去处理,loader处理流程主要分为二步:

1、根据sourceMap的sourcesContent和url内容进行匹配,然后从sources定位到原有的css资源路径

2、将传递给下个loader的url内容替换成绝对路径

代码如下:

module.exports = function (content, map) {
   const res = content.replace(/url\((?:\'|")?((\.\/|\.\.\/)+([^\'"\)]*))(\'|")?\)/g, (str, img, p2, imgPath) => {
       let index = -1;
       const {sourcesContent = [], sources = [], sourceRoot = []} = map || {};
       sourcesContent.some((item, i)=> {
           if (item.indexOf(img) !== -1) {
               index = i;
               return true;
           }
       });
       if (index !== -1) {
           const dir = path.dirname(sources[index]); // 获取文件所在目录
           str = str.replace(img, `~${path.join(dir, img)}`);
       }
       return str;
   });
   this.callback(null, res, map);
   return;
}

因为依赖sass-loader 处理之后的sourceMap, 所以@tencent/im-resolve-url-loader应配置在sass-loader 前面,配置如下: