一:什么是SourceMap?
我們在項目進行打包后,會將開發(fā)中的多個文件代碼打包到一個文件中,并且經(jīng)過壓縮,去掉多余的空格,且
babel編譯化后,最終會用于線上環(huán)境,那么這樣處理后的代碼和源代碼會有很大的差別,當有bug的時候,我們只能定位到壓縮處理后的代碼位置,無法定位到開發(fā)環(huán)境中的代碼,對于開發(fā)不好調(diào)式,因此sourceMap出現(xiàn)了,它就是為了解決不好調(diào)式代碼問題的。官網(wǎng)devtool https://webpack.docschina.org/configuration/devtool/
我們首先看下項目中的目錄結(jié)構如下:
### 目錄結(jié)構如下:
demo1 # 工程名
| |--- dist # 打包后生成的目錄文件
| |--- node_modules # 所有的依賴包
| |--- js # 存放所有js文件
| | |-- demo1.js
| | |-- main.js # js入口文件
| |
| |--- webpack.config.js # webpack配置文件
| |--- index.html # html文件
| |--- styles # 存放所有的css樣式文件
| |--- .gitignore
| |--- README.md
| |--- package.json
| |--- .babelrc # babel轉(zhuǎn)碼文件
main.js 代碼如下:
import demo1Func from './demo1.js';
console.log('main.js');
demo1.js 代碼如下:
export default function printMe() {
console.log('11111111');
}
webpack.config.js 代碼配置如下:
const path = require('path');
// 提取css的插件
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ClearWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './js/main.js',
output: {
filename: 'bundle.js',
// 將輸出的文件都放在dist目錄下
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist'
},
mode: 'development',
module: {
rules: [
{
// 使用正則去匹配要用該loader轉(zhuǎn)換的css文件
test: /\.css$/,
loaders: ExtractTextPlugin.extract({
// 轉(zhuǎn)換 .css文件需要使用的Loader
use: ['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`
})
]
};
基本結(jié)構如上,現(xiàn)在我們可以來理解 webpack中的SourceMap的幾種常見方式了。
二:理解webpack中的SourceMap的配置項
2.1. eval
eval 會將每一個module模塊,執(zhí)行eval,執(zhí)行后不會生成sourcemap文件,僅僅是在每一個模塊后,增加sourceURL來關聯(lián)模塊處理前后對應的關系。在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?");
})
})
如上打包后的代碼,每一個打包后的模塊后面都增加了包含sourceURL的注釋,sourceURL的值是壓縮前存放的代碼的位置,這樣就通過sourceURL關聯(lián)了壓縮前后的代碼。并沒有為每一個模塊生成相對應的sourcemap。
優(yōu)點是:打包速度非常快,因為不需要生成sourcemap文件。
缺點是:由于會映射到轉(zhuǎn)換后的代碼,而不是映射到原始代碼,所以不能正確的顯示行數(shù)。
2.2 source-map
在webpack中配置加上 devtool: 'source-map' 配置完成后,source-map會為每一個打包后的模塊生成獨立的sourcemap文件,比如在package.json文件中 這樣配置:
"scripts": {
"build": "webpack --progress --colors --devtool source-map"
}
然后運行 npm run build 后,會在dist目錄下生產(chǎn)map文件。我們繼續(xù)打包后的代碼如下:
(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
如上打包后的代碼最后面一句代碼是 //# 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": ""
}
上面生成后的map文件是一個javascript對象,可以被解析器讀取,它主要有以下幾個屬性:
version:e Map 的版本,目前為3.
sources: 轉(zhuǎn)換前的文件,該項是一個數(shù)組,表示可能存在多個文件合并.
names: 轉(zhuǎn)換前的所有變量名和屬性名。
mappings: 記錄位置信息的字符串。
sourcesContent: 轉(zhuǎn)換前的文件內(nèi)容列表,與sources列表依次對應。
sourceRoot: 轉(zhuǎn)換前的文件所在的目錄,如果與轉(zhuǎn)換前的文件在同一個目錄,該項為空。
chrome和firefox如何使用Source Map呢?
1. 開啟開發(fā)者工具
使用快捷鍵 option + command + i; 或者在 菜單欄選擇視圖 -> 開發(fā)者 -> 開發(fā)者工具。
2. 打開設置
點擊右上角的三個點的圖標,選擇Settings, 如下圖所示:

3. 開啟Source Map
在Sources中,選中 Enable Javascript source maps 如下圖所示

開啟完成后,我們在 package.json 配置如下代碼:
scripts: {
"dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline",
}
然后在main.js 代碼中,添加如下代碼:
require('../styles/main.css');
import demo1Func from './demo1.js';
console.log('main.js');
console.log(a)
如上a未定義,直接打印a,肯定會報錯的。我們在命令行中 運行 npm run dev 后,打開頁面會發(fā)現(xiàn)報錯,報錯如下:

然后我們點擊 main.js:5 后,會進入main.js代碼內(nèi),如下圖:

2.3 inline(比如 inline-source-map)
該屬性不會生成獨立的 .map文件,而是將 .map文件以dataURL的形式插入。
如下代碼:
/***/ (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.....
inline-source-map 使用缺點:它會使得bundle.js文件變得非常大,因為它需要把 sourceMappingURL 以dataurl的形式插入到bundle.js里面去。如下圖所示:

2.4 cheap(如:cheap-source-map)
該屬性在打包后同樣會為每一個文件模塊生成 .map文件,但是與source-map的區(qū)別在于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;;;;;;;;;;
如上可以看到,它不會生成列的信息,有逗號就表示包含了列信息。增加該屬性后,cheap就不會生成列信息,調(diào)式代碼列信息沒有什么用,因此使用cheap后,文件大小相對于source-map來講,bundle.js 文件會變得更小。
如下圖使用的是 source-map 生成的bundle.js.map 文件, 會包含列的信息,如下圖所示:

使用cheap屬性后,也不會有l(wèi)oader模塊之間對應的sourcemap,因為webpack打包最終會將所有的非js資源,通過loader形式轉(zhuǎn)換成js資源,比如 vue 中的文件,xx.vue -> vue-loader轉(zhuǎn)換 -> js -> 壓縮 -> 壓縮后的js
所以說如果沒有l(wèi)oader之間的sourcemap文件的話,那么在debug的時候,定義到壓縮前的js中的時候,不能跟蹤到vue中。
2.5 module(如:cheap-module-source-map)
該屬性的配置也是生成一個沒有列的信息的sourceMaps文件,同時loader的sourcemap也被簡化成為只包含對應行的。
三:開發(fā)環(huán)境和線上環(huán)境如何選擇sourceMap?
從上面的eval, inline, source-map, cheap, module中可以看到,各自屬性值代表打包后的具體含義,因此我們可以分析下開發(fā)環(huán)境和正式環(huán)境要如何選擇sourceMap;我們可以從如下幾個方面考慮:
1. 源代碼中的列信息是沒有任何作用,因此我們打包后的文件不希望包含列相關信息,只有行信息能建立打包前后的依賴關系。因此不管是開發(fā)環(huán)境或生產(chǎn)環(huán)境,我們都希望添加cheap的基本類型來忽略打包前后的列信息。
2. 不管是開發(fā)環(huán)境還是正式環(huán)境,我們都希望能定位到bug的源代碼具體的位置,比如說某個vue文件報錯了,我們希望能定位到具體的vue文件,因此我們也需要module配置。
3. 我們需要生成map文件的形式,因此我們需要增加 source-map屬性。
4. 我們介紹了eval打包代碼的時候,知道eval打包后的速度非??欤驗樗簧蒻ap文件,但是可以對eval組合使用 eval-source-map 使用會將map文件以DataURL的形式存在打包后的js文件中,比如如下:

它的效果類似于inline的效果,因此在正式環(huán)境中不要使用 eval-source-map, 因為它會增加文件的大小,但是在開發(fā)環(huán)境中,可以試用下,因為他們打包的速度很快。
因此我們可以總結(jié)如下:
**在開發(fā)環(huán)境中我們可以使用 **
module.exports = {
devtool: 'cheap-module-eval-source-map'
}
**在正式環(huán)境中我們可以使用 **
module.exports = {
devtool: 'cheap-module-source-map';
}
如上是總結(jié)的在開發(fā)環(huán)境和正式環(huán)境使用的sourcemap進行打包的簡單思路。