webpack中sourcemap详细配置

# 简介

source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map;

map文件只要不打开开发者工具,浏览器是不会加载的;

sourcemap是为了解决开发代码与实际运行代码不一致时帮助我们debug到原始开发代码的技术

webpack通过配置可以自动给我们source maps文件,map文件是一种对应编译文件和源文件的方法;

# 类型

类型 含义
source-map 原始代码 最好的sourcemap质量有完整的结果,但是会很慢
eval-source-map 原始代码 同样道理,但是最高的质量和最低的性能
hidden-source-map 和 source-map 一样,但不会在 bundle 末尾追加注释
cheap-module-eval-source-map 原始代码(只有行内) 同样道理,但是更高的质量和更低的性能
cheap-eval-source-map 转换代码(行内) 每个模块被eval执行,并且sourcemap作为eval的一个dataurl
eval 生成代码 每个模块都被eval执行,并且存在@sourceURL,带eval的构建模式能cache SourceMap
cheap-source-map 转换代码(行内) 生成的sourcemap没有列映射,从loaders生成的sourcemap没有被使用
cheap-module-source-map 原始代码(只有行内) 与上面一样除了每行特点的从loader中进行映射
module.exports = { 
  devtool: 'cheap-module-eval-source-map', //开发环境
  devtool: 'cheap-module-source-map', //正式环境
}
1
2
3
4

# 配置项

# 关键字

配置项其实只是五个关键字eval、source-map、cheap、module和inline的组合

关键字 含义
source-map 产生.map文件
eval 使用eval包裹模块代码
cheap 不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap
module 包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件
inline 将.map作为DataURI嵌入,不单独生成.map文件

# 最佳实践【要点】

# 考虑的因素

  • 源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加cheap的基本类型来忽略打包前后的列信息。

  • 不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个vue文件报错了,我们希望能定位到具体的vue文件,因此我们也需要module配置。

  • 需要生成map文件的形式,因此我们需要增加 source-map属性

  • 我们介绍了eval打包代码的时候,知道eval打包后的速度非常快,因为它不生成map文件,但是可以对eval组合使用 eval-source-map使用会将map文件以DataURL的形式存在打包后的js文件中; 它的效果类似于inline的效果**,因此在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。**如下:

    img

# 开发环境

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

# 生产环境

  • 一般情况下,我们并不希望任何人都可以在浏览器直接看到我们未编译的源码,所以我们不应该直接提供sourceMap给浏览器。但我们又需要sourceMap来定位我们的错误信息, 这时我们可以设置cheap-module-source-map
  • 一方面webpack会生成sourcemap文件以提供给错误收集工具比如sentry,另一方面又不会为 bundle 添加引用注释,以避免浏览器使用。
module.exports = { 
   //cheap不包含列信息(关于列信息的解释下面会有详细介绍)也不包含loader的sourcemap
  //module包含loader的sourcemap(比如jsx to js ,babel的sourcemap),否则无法定义源文件
  //eval使用eval包裹模块代码
  //source-map产生.map文件
  devtool: 'cheap-module-eval-source-map', //开发环境
  devtool: 'cheap-module-source-map', //正式环境
}
1
2
3
4
5
6
7
8

# 详细分析

# 示例及环境配置

main.js 代码如下:

import demo1Func from './demo1.js';
console.log('main.js');
1
2

demo1.js 代码如下:

export default function printMe() {
 console.log('11111111');
}
1
2
3

webpack.config.js 代码配置如下:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ClearWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
  entry: './js/main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),// 将输出的文件都放在dist目录下
    publicPath: '/dist'
  },
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.css$/,// 使用正则去匹配要用该loader转换的css文件
        loaders: ExtractTextPlugin.extract({
          use: ['css-loader'] // 转换 .css文件需要使用的Loader
        })
      },
      {
        test: /\.(png|jpg)$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: '[name].[ext]'
        }
      },
      {
        test: /\.js$/,
        exclude: /(node_modules)/, // 排除文件
        loader: 'babel-loader'
      }
    ]
  },
  resolve: {
    // modules: ['plugin', 'js']
  },
  externals: {
    jquery: 'jQuery'
  },
  devtool: 'eval',
  devServer: {
    // contentBase: path.join(__dirname, "dist"),
    port: 8081,
    host: '0.0.0.0',
    headers: {
      'X-foo': '112233'
    },
    // hot: true,
    inline: true,
    open: true,
    overlay: true,
    stats: 'errors-only'
  },
  plugins: [
    // new ClearWebpackPlugin(['dist']),
    new ExtractTextPlugin({
      // 从js文件中提取出来的 .css文件的名称
      filename: `main.css`
    })
  ]
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

基本结构如上,现在我们可以来理解 webpack中的SourceMap的几种常见方式了。

# eval

  ({
    "./src/index.js":
      (function (module, exports) {
        eval("let a=1;\r\nlet b=2;\r\nlet c=3;\n\n//# sourceURL=webpack:///./src/index.js?");
      })
  });
1
2
3
4
5
6
  • eval-source-map就会带上源码的sourceMap
  • 加了eval的配置生成的sourceMap会作为DataURI嵌入,不单独生成.map文件
  • 官方比较推荐开发场景下使用eval的构建模式,因为它能cache sourceMap,从而rebuild的速度会比较快
  ({
    "./src/index.js":
      (function (module, exports) {
        eval("let a=1;\r\nlet b=2;\r\nlet c=3;//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,In0=\n//# sourceURL=webpack-internal:///./src/index.js\n");
      })
  });
1
2
3
4
5
6

devtool: "eval-source-map" is really as good as devtool: "source-map", but can cache SourceMaps for modules. It’s much faster for rebuilds.

eval 会将每一个module模块,执行eval,执行后不会生成sourcemap文件,仅仅是在每一个模块后,增加sourceURL来关联模块处理前后对应的关系。在webpack中配置devtool: 'eval', 如下打包后的代码:

(function(modules) { // webpackBootstrap
  "use strict";
  eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return printMe; });\n\nfunction printMe() {\n  console.log('11111111');\n}\n\n//# sourceURL=webpack:///./js/demo1.js?");

  /***/ "./js/main.js":
  /*!********************!*\
    !*** ./js/main.js ***!
    \********************/
  /*! no exports provided */
  /***/ 
  (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\nconsole.log('main.js');\n\n//# sourceURL=webpack:///./js/main.js?");
  })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如上打包后的代码,每一个打包后的模块后面都增加了包含sourceURL的注释,sourceURL的值是压缩前存放的代码的位置,这样就通过sourceURL关联了压缩前后的代码。并没有为每一个模块生成相对应的sourcemap。

**优点是:**打包速度非常快,因为不需要生成sourcemap文件。 **缺点是:**由于会映射到转换后的代码,而不是映射到原始代码,所以不能正确的显示行数。

# source-map

src\index.js

let a=1;
let b=2;
let c=3;
1
2
3

dist\main.js

   ({
     "./src/index.js":
       (function (module, exports) {
         let a = 1;
         let b = 2;
         let c = 3;
       })
   });
//# sourceMappingURL=main.js.map
1
2
3
4
5
6
7
8
9

在webpack中配置加上 devtool: 'source-map' 配置完成后,source-map会为每一个打包后的模块生成独立的sourcemap文件,比如在package.json文件中 这样配置:

"scripts": {
  "build": "webpack --progress --colors --devtool source-map"
}
1
2
3

然后运行 npm run build 后,会在dist目录下生产map文件。我们继续打包后的代码如下:

(function(module, __webpack_exports__, __webpack_require__) {

  "use strict";
  __webpack_require__.r(__webpack_exports__);
  /* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ "./js/demo1.js");
  __webpack_require__(/*! ../styles/main.css */ "./styles/main.css");
  console.log('main.js');
/***/ }),

  /***/ "./styles/main.css":
  /*!*************************!*\
    !*** ./styles/main.css ***!
    \*************************/
  /*! no static exports found */
  /***/ (function(module, exports) {

  // removed by extract-text-webpack-plugin

  /***/ })

  /******/ });
  //# sourceMappingURL=bundle.js.map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

如上打包后的代码最后面一句代码是 //# sourceMappingURL=bundle.js.map ,同时在dist目录下会针对每一个模块生成响应的 .map文件, 比如我们在dist目录中会生成 bundle.js.map文件,我们可以打开看下这个文件代码会如下:

{
  "version":3,
  "sources":[
    "webpack:///webpack/bootstrap","webpack:///./js/demo1.js",
    "webpack:///./js/main.js","webpack:///./styles/main.css"
  ],
  "names":["printMe","console","log","require"],
  "mappings":";AAAA;AACA;;AAEA;AACA...",
  "file":"bundle.js",
  "sourcesContent":[],
  "sourceRoot": ""
}
1
2
3
4
5
6
7
8
9
10
11
12

上面生成后的map文件是一个javascript对象,可以被解析器读取,它主要有以下几个属性:

version: Source Map 的版本,目前为3. sources: 转换前的文件,该项是一个数组,表示可能存在多个文件合并. names: 转换前的所有变量名和属性名。 mappings: 记录位置信息的字符串。 sourcesContent: 转换前的文件内容列表,与sources列表依次对应。 sourceRoot: 转换前的文件所在的目录,如果与转换前的文件在同一个目录,该项为空。

注意事项:记得在浏览器中打开调试方式;

1**. 开启开发者工具**

使用快捷键 option + command + i; 或者在 菜单栏选择视图 -> 开发者 -> 开发者工具。

2. 打开设置

点击右上角的三个点的图标,选择Settings, 如下图所示:

img

3. 开启Source Map 在Sources中,选中 Enable Javascript source maps 如下图所示

img

开启完成后,我们在 package.json 配置如下代码:

scripts: {
  "dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline",
}
1
2
3

然后在main.js 代码中,添加如下代码:

require('../styles/main.css');
import demo1Func from './demo1.js';

console.log('main.js');
console.log(a)
1
2
3
4
5

如上a未定义,直接打印a,肯定会报错的。我们在命令行中 运行 npm run dev 后,打开页面会发现报错,报错如下:

img

然后我们点击 main.js:5 后,会进入main.js代码内,如下图:

img

# inline

  • inline就是将map作为DataURI嵌入,不单独生成.map文件;比如inline-source-map
({
    "./src/index.js":
      (function (module, exports) {
        let a = 1;
        let b = 2;
        let c = 3;
      })
});
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIj
1
2
3
4
5
6
7
8
9

如下代码:

/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ "./js/demo1.js");
__webpack_require__(/*! ../styles/main.css */ "./styles/main.css");


console.log('main.js');
console.log(a);

/***/ }),

/***/ "./styles/main.css":
/*!*************************!*\
  !*** ./styles/main.css ***!
  \*************************/
/*! no static exports found */
/***/ (function(module, exports) {

// removed by extract-text-webpack-plugin

/***/ })

/******/ });
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2Vz.....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

inline-source-map 使用缺点:它会使得bundle.js文件变得非常大,因为它需要把 sourceMappingURL 以dataurl的形式插入到bundle.js里面去。如下图所示:

image-20201013000736698

# cheap(低开销)

  • cheap(低开销)的sourcemap,因为它没有生成列映射(column mapping),只是映射行数
  • 开发时我们有行映射也够用了,开发时可以使用cheap; 比如cheap-source-map

该属性在打包后同样会为每一个文件模块生成 .map文件,但是与source-map的区别在于cheap生成的 map文件会忽略原始代码中的列信息; 比如生成后的bundle.js.map中的mappings的代码如下:

"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;AClFA;AAAA;AAAA;AACA;AACA;AACA;;;;;;;;;;
1

如上可以看到,它不会生成列的信息,有逗号就表示包含了列信息。增加该属性后,cheap就不会生成列信息,调式代码列信息没有什么用,因此使用cheap后,文件大小相对于source-map来讲,bundle.js 文件会变得更小。

如下图使用的是 source-map 生成的bundle.js.map 文件, 会包含列的信息,如下图所示:

img

使用cheap属性后,也不会有loader模块之间对应的sourcemap,因为webpack打包最终会将所有的非js资源,通过loader形式转换成js资源,比如 vue 中的文件,xx.vue -> vue-loader转换 -> js -> 压缩 -> 压缩后的js

所以说如果没有loader之间的sourcemap文件的话,那么在debug的时候,定义到压缩前的js中的时候,不能跟踪到vue中。

# module

  • 该属性的配置也是生成一个没有列的信息的sourceMaps文件,同时loader的sourcemap也被简化成为只包含对应行的。

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

    什么是模块之间的sourceMap呢?

    • 比如jsx文件会经历loader处理成js文件再混淆压缩, 如果没有loader之间的sourceMap,那么在debug的时候定义到上图中的压缩前的js处,而不能追踪到jsx中
    • 所以为了映射到loader处理前的代码,我们一般也会加上module配置; 比如:cheap-module-source-map

# 实践

# 安装及设置

npm i  -D webpack webpack-cli webpack-dev-server babel-loader @babel/core @babel/preset-env style-loader css-loader less-loader less file-loader url-loader
1

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode:'development',
  devtool:'cheap-module-source-map',
  entry:'./src/index.js',
  module: {
      rules: [
        {
          test: /\.js$/,
          use: [{
            loader:'babel-loader',
            options:{
              presets:["@babel/preset-env"]
            }
          }]
        } 
      ]
  },
  plugins:[
    new HtmlWebpackPlugin({
      template:'./src/index.html'
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

src\index.js

import './sum';
sum(1,2);
//console.log(window.a.b);
1
2
3

# CSS sourceMap

  • css-loader,sass-loader都已经提供了生成sourceMap的能力

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  mode: 'development',
  devtool: 'cheap-module-source-map',
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [{
          loader: 'babel-loader',
          options: {
            presets: ["@babel/preset-env"]
          }
        }]
      },
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: { sourceMap: true }
          }
        ]
      },
      {
        test: /\.scss$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: { sourceMap: true }
          },
          { loader: "resolve-url-loader" },
          {
            loader: 'sass-loader',
            options: { sourceMap: true }
          }
        ]
      },
      {
        test: /\.(jpg|png|gif|bmp)$/,
        use: [
          { loader: 'url-loader' }
        ]
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

src\index.scss

@import "includes/bg.scss";
$color:red;
body{
    color: $color;
}
1
2
3
4
5

includes\bg.scss

$color:red;
body{
    background-image: url(./kf.jpg);
}
1
2
3
4

# 相关链接

source-map (opens new window)

上次更新: 2022/04/15, 05:41:27
×