閱讀目錄
2. 在webpack中如何使用 tree-shaking 呢?
3. 使用webpack-deep-scope-plugin 優化
1. 什么是tree-shaking?
在webpack中,tree-shaking的作用是可以剔除js中用不上的代碼,但是它依賴的是靜態的ES6的模塊語法。
也就是說沒有被引用到的模塊它是不會被打包進來的,可以減少我們的包的大小,減少文件的加載時間,提高用戶體驗。
webpack2版本中就開始引入了 tree shaking的概念,它可以在打包時可以忽略哪些沒有被使用到的代碼。
注意:要讓 Tree Shaking 正常工作的前提是:提交給webpack的javascript代碼必須采用了 ES6的模塊化語法,因為ES6模塊化語法是靜態的(在導入,導出語句中的路徑必須是靜態的字符串)。
2. 在webpack中如何使用 tree-shaking 呢?
在配置代碼前,我們來看看我們項目中的目錄結構如下:
### 目錄結構如下: demo1 # 工程名 | |--- dist # 打包后生成的目錄文件 | |--- node_modules # 所有的依賴包 | |--- js # 存放所有js文件 | | |-- demo1.js | | |-- main.js # js入口文件 | |--- common # js公用的文件 | | |-- util.js # 公用的util.js文件 | |--- webpack.config.js # webpack配置文件 | |--- index.html # html文件 | |--- styles # 存放所有的css樣式文件 | | |-- main.styl # main.styl文件 | | |-- index.styl | |--- .gitignore | |--- README.md | |--- package.json | |--- .babelrc # babel轉碼文件
webpack.config.js 代碼如下:
const path = require('path'); // 引入 mini-css-extract-plugin 插件 const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 清除dist目錄下的文件 const ClearWebpackPlugin = require('clean-webpack-plugin'); const webpack = require('webpack'); // 引入打包html文件 const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入HappyPack插件 const HappyPack = require('happypack'); module.exports = { // 入口文件 entry: { main: './js/main.js' }, output: { filename: '[name].[contenthash].js', // 將輸出的文件都放在dist目錄下 path: path.resolve(__dirname, 'dist') }, module: { rules: [ { // 使用正則去匹配 test: /\.styl$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: {} }, { loader: 'postcss-loader', options: { ident: 'postcss', plugins: [ require('postcss-cssnext')(), require('cssnano')(), require('postcss-pxtorem')({ rootValue: 16, unitPrecision: 5, propWhiteList: [] }), require('postcss-sprites')() ] } }, { loader: 'stylus-loader', options: {} } ] }, { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, 'happypack/loader?id=css-pack' ] }, { test: /\.(png|jpg)$/, use: ['happypack/loader?id=image'] }, { test: /\.js$/, // 將對.js文件的處理轉交給id為babel的HappyPack的實列 use: ['happypack/loader?id=babel'], // loader: 'babel-loader', exclude: path.resolve(__dirname, 'node_modules') // 排除文件 } ] }, resolve: { extensions: ['*', '.js', '.json'] }, devtool: 'cheap-module-eval-source-map', devServer: { port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, inline: true, overlay: true, stats: 'errors-only' }, mode: 'development', plugins: [ new HtmlWebpackPlugin({ template: './index.html' // 模版文件 }), new ClearWebpackPlugin(['dist']), new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css' }), /**** 使用HappyPack實例化 *****/ new HappyPack({ // 用唯一的標識符id來代表當前的HappyPack 處理一類特定的文件 id: 'babel', // 如何處理.js文件,用法和Loader配置是一樣的 loaders: ['babel-loader'] }), new HappyPack({ id: 'image', loaders: [{ loader: require.resolve('url-loader'), options: { limit: 10000, name: '[name].[ext]' } }] }), // 處理styl文件 new HappyPack({ id: 'css-pack', loaders: ['css-loader'] }) ] };
.babelrc 配置如下:
{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "env", { "modules": false // 關閉Babel的模塊轉換功能,保留ES6模塊化語法 } ], "stage-2" ] }
common/util.js 代碼如下:
export function a() { alert('aaaa'); } export function b() { alert('bbbbb'); } export function c() { alert('cccc'); }
js/main.js 代碼如下:
import { a } from '../common/util';
a();
執行 webpack后,打包文件如下:
然后繼續查看 dist/main.xxx.js代碼如下:
"use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return a; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return b; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return c; }); function a() { alert('aaaa'); } function b() { alert('bbbbb'); } function c() { alert('cccc'); } /***/ }),
如上代碼,還是會包含 b,c 兩個函數代碼進來,那是因為 webpack 想要使用tree-shaking功能的話,我們需要壓縮代碼,就能把沒有引用的代碼剔除掉,因此我們需要在webpack中加上壓縮js代碼如下:
// 引入 ParallelUglifyPlugin 插件 const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); module.exports = { plugins: [ // 使用 ParallelUglifyPlugin 並行壓縮輸出JS代碼 new ParallelUglifyPlugin({ // 傳遞給 UglifyJS的參數如下: uglifyJS: { output: { /* 是否輸出可讀性較強的代碼,即會保留空格和制表符,默認為輸出,為了達到更好的壓縮效果, 可以設置為false */ beautify: false, /* 是否保留代碼中的注釋,默認為保留,為了達到更好的壓縮效果,可以設置為false */ comments: false }, compress: { /* 是否在UglifyJS刪除沒有用到的代碼時輸出警告信息,默認為輸出,可以設置為false關閉這些作用 不大的警告 */ warnings: false, /* 是否刪除代碼中所有的console語句,默認為不刪除,開啟后,會刪除所有的console語句 */ drop_console: true, /* 是否內嵌雖然已經定義了,但是只用到一次的變量,比如將 var x = 1; y = x, 轉換成 y = 5, 默認為不 轉換,為了達到更好的壓縮效果,可以設置為false */ collapse_vars: true, /* 是否提取出現了多次但是沒有定義成變量去引用的靜態值,比如將 x = 'xxx'; y = 'xxx' 轉換成 var a = 'xxxx'; x = a; y = a; 默認為不轉換,為了達到更好的壓縮效果,可以設置為false */ reduce_vars: true } } }) ] }
再運行下打包命令后。我們繼續查看代碼,如下所示:
可以看到還是會把無用的 b函數 和 c函數代碼打包進去。這是什么情況?那是因為我們在webpack中配置了 mode: 'development',我們現在把它改成 mode: 'production',后,就可以看到只用 a函數了,我們可以到dist目錄下的main.js代碼內部搜索下 alert, 就可以看到了,只有一個alert('a')了。說明b函數和c函數被剔除掉了。
tree-shaking 目前的缺陷:
tree-shaking 能夠利用ES6的靜態引入規范,減少包的體積,避免不必要的代碼引入,但是webpack只能做一點簡單的事情。
比如 我現在在main.js代碼改成如下:
import { func2 } from '../common/util'; var a = func2(222); alert(a);
common/util.js 代碼如下:
import lodash from 'lodash-es' var func1 = function(v) { alert('111'); return lodash.isArray(v); } var func2 = function(v) { return v; }; export { func1, func2 }
如上代碼,在main.js中引入了 func2, 但是並沒有引入func1, 但是func1引入了lodash-es。webpack在檢查的時候發現func1中確實用到了lodash-es,因此不會把lodash去掉,但是func1函數會去掉的。但是我們在js中也並沒有使用到lodash。因此在這種情況下,webpack中的 tree-shaking 解決不了這種情況,因此 webpack-deep-scope-plugin 插件就可以解決這種問題了,如下沒有使用 webpack-deep-scope-plugin 插件打包后的文件大小。如下:
如上main.js 打包壓縮后的js代碼大小有81.1kb。打開dist/main.js代碼搜索 lodash后,可以搜索到,因此lodash插件被打包進去main.js中了,但是實際上我們項目並沒有使用到lodash,因此lodash的庫我們按常理來講並不需要打包進去的。
3. 使用webpack-deep-scope-plugin 優化
1. 首先需要安裝 webpack-deep-scope-plugin, 安裝命令如下:
npm i -D webpack-deep-scope-plugin
在webpack.config.js 代碼引入如下:
// 引入 webpack-deep-scope-plugin 優化 const WebpackDeepScopeAnalysisPlugin = require('webpack-deep-scope-plugin').default; module.exports = { plugins: [ new WebpackDeepScopeAnalysisPlugin() ] }
然后我們繼續打包如下所示:
打包后發現如上,只有969字節,1kb都不到,再打開dist/main.js 查看代碼,搜索下 lodash, 發現搜索不到。
注意點:
1. 要使用 tree-shaking,必須保證引用的插件的模塊是ES6模塊規范編寫的,這也是我為什么引用了的是 lodash-es,而不是 'lodash', 如果引用的是lodash的話,是不能去掉的。
2. 在 .babelrc 中,babel設置 module: false, 避免babel將模塊轉換為成 CommonJS規范。引入模塊包也必須符合ES6規范的。如下 babelrc代碼:
{ "plugins": [ [ "transform-runtime", { "polyfill": false } ] ], "presets": [ [ "env", { "modules": false // 關閉Babel的模塊轉換功能,保留ES6模塊化語法 } ], "stage-2" ] }
且需要在 package.json 中定義 sideEffect: false, 這也是為了避免出現 import xxx 導致模塊內部的一些函數執行后影響全局環境, 卻被去除掉的情況.
3. webpack-deep-scope-plugin 插件依賴 node8.0+ 和 webpack 4.14.0 +