閱讀目錄
二:理解webpack中的SourceMap的eval,inline,sourceMap,cheap,module
一:什麼是SourceMap?
我們在專案進行打包後,會將開發中的多個檔案程式碼打包到一個檔案中,並且經過壓縮,去掉多餘的空格,且babel編譯化後,最終會用於線上環境,那麼這樣處理後的程式碼和原始碼會有很大的差別,當有bug的時候,我們只能定位到壓縮處理後的程式碼位置,無法定位到開發環境中的程式碼,對於開發不好調式,因此sourceMap出現了,它就是為了解決不好調式程式碼問題的。官網devtool https://webpack.docschina.org/configuration/devtool/
在講解之前,我們首先看下專案中的目錄結構如下:
### 目錄結構如下: 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轉碼檔案
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轉換的css檔案 test: /\.css$/, loaders: ExtractTextPlugin.extract({ // 轉換 .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` }) ] };
基本結構如上,現在我們可以來理解 webpack中的SourceMap的幾種常見方式了。
二:理解webpack中的SourceMap的配置項
2.1. eval
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?"); }) })
如上打包後的程式碼,每一個打包後的模組後面都增加了包含sourceURL的註釋,sourceURL的值是壓縮前存放的程式碼的位置,這樣就通過sourceURL關聯了壓縮前後的程式碼。並沒有為每一個模組生成相對應的sourcemap。
優點是:打包速度非常快,因為不需要生成sourcemap檔案。
缺點是:由於會對映到轉換後的程式碼,而不是對映到原始程式碼,所以不能正確的顯示行數。
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目錄下生產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
如上打包後的程式碼最後面一句程式碼是 //# 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: Source Map 的版本,目前為3.
sources: 轉換前的檔案,該項是一個陣列,表示可能存在多個檔案合併.
names: 轉換前的所有變數名和屬性名。
mappings: 記錄位置資訊的字串。
sourcesContent: 轉換前的檔案內容列表,與sources列表依次對應。
sourceRoot: 轉換前的檔案所在的目錄,如果與轉換前的檔案在同一個目錄,該項為空。
chrome和firefox如何使用Source Map呢?
1. 開啟開發者工具
使用快捷鍵 option + command + i; 或者在 選單欄選擇檢視 -> 開發者 -> 開發者工具。
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 後,開啟頁面會發現報錯,報錯如下:
然後我們點選 main.js:5 後,會進入main.js程式碼內,如下圖:
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的區別在於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就不會生成列資訊,調式程式碼列資訊沒有什麼用,因此使用cheap後,檔案大小相對於source-map來講,bundle.js 檔案會變得更小。
如下圖使用的是 source-map 生成的bundle.js.map 檔案, 會包含列的資訊,如下圖所示:
使用cheap屬性後,也不會有loader模組之間對應的sourcemap,因為webpack打包最終會將所有的非js資源,通過loader形式轉換成js資源,比如 vue 中的檔案,xx.vue -> vue-loader轉換 -> js -> 壓縮 -> 壓縮後的js
所以說如果沒有loader之間的sourcemap檔案的話,那麼在debug的時候,定義到壓縮前的js中的時候,不能跟蹤到vue中。
2.5 module(如:cheap-module-source-map)
該屬性的配置也是生成一個沒有列的資訊的sourceMaps檔案,同時loader的sourcemap也被簡化成為只包含對應行的。
三:開發環境和線上環境如何選擇sourceMap?
從上面的eval, inline, source-map, cheap, module中可以看到,各自屬性值代表打包後的具體含義,因此我們可以分析下開發環境和正式環境要如何選擇sourceMap;我們可以從如下幾個方面考慮:
1. 原始碼中的列資訊是沒有任何作用,因此我們打包後的檔案不希望包含列相關資訊,只有行資訊能建立打包前後的依賴關係。因此不管是開發環境或生產環境,我們都希望新增cheap的基本型別來忽略打包前後的列資訊。
2. 不管是開發環境還是正式環境,我們都希望能定位到bug的原始碼具體的位置,比如說某個vue檔案報錯了,我們希望能定位到具體的vue檔案,因此我們也需要module配置。
3. 我們需要生成map檔案的形式,因此我們需要增加 source-map屬性。
4. 我們介紹了eval打包程式碼的時候,知道eval打包後的速度非常快,因為它不生成map檔案,但是可以對eval組合使用 eval-source-map使用會將map檔案以DataURL的形式存在打包後的js檔案中,比如如下:
它的效果類似於inline的效果,因此在正式環境中不要使用 eval-source-map, 因為它會增加檔案的大小,但是在開發環境中,可以試用下,因為他們打包的速度很快。
因此我們可以總結如下:
在開發環境中我們可以使用
module.exports = { devtool: 'cheap-module-eval-source-map' }
在正式環境中我們可以使用
module.exports = { devtool: 'cheap-module-source-map'; }
如上是總結的在開發環境和正式環境使用的sourcemap進行打包的簡單思路。