webpack打包的體積越小,對於部署應用的網站來說,性能越好,加載速度越快。
1. 分析打包文件
1. 生成統計信息文件
首先需要通過webpack命令生成統計信息的文件。在package.json的腳本中添加命令
"scripts": { "stats": "webpack --config webpack.prod.js --profile --json > stats.json", //... }
上面的命令會在根目錄下生成一個stats.json的打包統計信息文件。
2. 可視化分析
使用插件可視化分析插件:webpack-bundle-analyzer
npm install --save-dev webpack-bundle-analyzer
配置插件的使用信息;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.export = { //... plugins: [ new BundleAnalyzerPlugin({ analyzerMode: 'disabled', // 關閉默認啟動的展示信息的http服務器 generateStatsFile: true // 打包的時候生成stats.json文件; }), }
從上面配置信息可知,使用該插件,不需要再手動生成stats.json文件,第一步可以省略。
然后,在腳本中添加手動啟動分析器的http
// 分析命令的使用,應該在打包命令之后。因為它的作用就是分析打包后的文件 "scripts": { "build": "webpack --config webpack.prod.js", "analyzer": "webpack-bundle-analyzer ./dist/stats.json --port 8081" }
3. 優化建議
將生成的stats.json文件拖入該網站https://webpack.jakoblind.no/optimize/。
會給出打包體積太大的優化措施。
該插件基於webpack-optimize-helper插件。
2. 抽離css並壓縮
1.抽離css
使用mini-css-extract-plugin抽離css
npm install --save-dev mini-css-extract-plugin
在plugins配置文件中使用插件
module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css' // 將css文件統一放入css文件夾 }) ] }
2. 壓縮css
使用optimize-css-assets-webpack-plugin壓縮css文件
npm install -D optimize-css-assets-webpack-plugin
在optimization中使用該插件
module.exports = { optimization: { minimize: true, minimizer: [ new OptimizeCssAssetsWebpackPlugin() ] }
3. 移除未使用的css
在大型項目中,經常會有很多樣式內容,在代碼中根本未使用,但是會被打包,這些樣式需要打包時應該移除。
使用purgecss-webpack-plugin移除未使用的css樣式。
npm i purgecss-webpack-plugin -D
在pluigns中配置插件
const glob = require('glob'); // 根據路徑查找文件 module.exports = {
plugins:[ new PurgecssWebpackPlugin({ //paths要求是絕對路徑 paths: glob.sync(`${path.join(__dirname, 'src/**/*')}`, { nodir: true }) })
]
3. 復用babal轉化時runtime代碼
@babel/plugin-transform-runtime
✅該插件的引入只能通過.babelrc文件,不能在babel-loader的options中,否則報錯。
4. 使用CDN
1. 第三方庫使用CDN
new HtmlWebpackExternalsPlugin({ externals: [ { module: 'lodash', global: '_', entry: 'https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js' } ] })
然后為了避免用戶在模塊中手動又導入,需要添加
externals: { lodash: '_' },
2. 項目生成的靜態文件可以部署到CDN服務器中
需要配置publicPath為CDN服務器的域名
// output, module->MiniCssWebpackLoader.loader, image等可以部署publicPath publicPath: 'http://lyralee.com'
5. webpack.IgnorePlugin
忽略第三方庫中多余的文件夾。
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
上面忽略了moment庫中的語言包
6. 圖片壓縮image-webpack-loader
{ test: /\.(gif|png|jpe?g|svg)$/i, use: [ { loader: 'url-loader', options: { limit: 2 * 1024, outputPath: 'images', name: '[name].[contenthash:8].[ext]' } }, { loader: 'image-webpack-loader', options: { mozjpeg: { progressive: true, quality: 65 }, // optipng.enabled: false will disable optipng optipng: { enabled: false }, pngquant: { quality: [0.65, 0.90], speed: 4 }, gifsicle: { interlaced: false }, // the webp option will enable WEBP webp: { quality: 75 } } } ] }
7. 使用babel-polyfill的替代方案
如果項目中允許使用外部鏈接,可以使用
<script src="https://polyfill.io/v3/polyfill.min.js/"></script>
它按照瀏覽器類型返回需要的內容
8. TreeShaking
1. 原理:
1)利用es6模塊的靜態結構,依賴模塊命令import/export,進行代碼分析,分析出無用/永遠不會訪問的代碼。
1)對於自己寫的代碼,禁止babel將esModule轉為require rules: [ { test: /\.jsx?$/, use: { loader: 'babel-loader', options: { // 設置{moduels: false} 則保留ES6的import/export presets: [['@babel/preset-env', {modules: false}], '@babel/preset-react'], plugins: [ ['@babel/plugin-proposal-decorators', {legacy: true}], ['@babel/plugin-proposal-class-properties', {loose: true}] ] }, }, exclude: /node_modules/ } ] 2) 對於第三方庫,要使用es模塊的包, 如: lodash-es(有問題,先不用) import {join } from 'lodash-es'
2)通過terser-webpack-plugin移除分析出的多余代碼
// ⚠️只要寫了optimization屬性(即使是空),即使是production,也不會啟動該Terser插件 ; 如果是完整的配置,但是mode=development,該屬性也不起作用 optimization: { minimizer: [ new TerserWebpackPlugin({ parallel: true, cache: true }) ], ... },
2. 注意事項
1. 要使TreeShaking起作用,必須使用ES6語法import/export 2. 減少sideEffects(副作用)代碼的書寫 3. 引入的第三方庫,需要引入他們的ES版本
3. 特殊情形
當多入口文件存在時,只要有一個入口中使用了某方法,就不會被TreeShaking掉
9. 代碼分割
1. 入口文件代碼分割
entry: { index: './src/index.js', vendors: ['lodash'] }
2. 動態import導入實現懶加載
當代碼中觸發使用import()動態引入文件時,會發起請求,並返回一個webpackJsonp方法,
該方法中實現代碼的按需加載。
另外,React16.6.0中引入的React.lazy()就是基於該方法實現模塊的代碼分割和懶加載。
為了提高網站的性能,可以配合webpackPrefetch實現預加載。
window.addEventListener('click', function(e) { import(/*webpackPrefetch: true */ /*webpackChunkName: 'hello' */'./test.js').then(() => { console.log('代碼分割') }) });
它的實際作用是在入口文件中生成一段js腳本,腳本用於向html頁面插入以下標簽
<link rel="prefetch" as="script" href="hello.c671.js"></link>
實際應用
react16.6.0中添加了React.lazy()和React.Suspense組件。結合React-router用於實現代碼分割。
React.lazy()加載的組件,必須用於React.Suspense組件中。React.Suspense組件含有一個fallback屬性,用於添加加載過程的過渡效果。
最好的是使用瀏覽器的prefetch性能。如: webpackPrefetch屬性
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import React, { Suspense, lazy } from 'react'; const Home = lazy(() => import(/*webpackPrefetch: true*/'./routes/Home')); const About = lazy(() => import(/*webpackPrefetch: true*/'./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> </Switch> </Suspense> </Router> );
3. 使用splitChunks插件
webpack4.0中默認加入了splitchunks插件,並設置了默認屬性。默認只分割異步加載(按需)的代碼。但是通過optimization.splitChunk,可以修改配置。
對於import()加載的代碼,任何情況下都可以分割,而且沒有大小限制。
非import()加載的代碼,如果想要實現webpack默認代碼分割,其規則如下:
1. 代碼塊在模塊之間共享或者來自node_modules 2. 代碼塊的大小至少30Kb,即30000byte 3. 並行加載的按需代碼塊<=5 4. 頁面初始化的並行加載代碼塊<=3
要滿足4,5 需要修改代碼塊的體積。
其默認配置如下:
optimization: { splitChunks: { /** * chunks的有效類型: * 1. async: 默認值,異步;如import('./test.js),以及test.js中同步方式引入的模塊 * 2. initial: 同步;設為該值,代碼中import,require引入的模塊會自動根據規則分割代碼 * 3. all: 所有類型 * 也可以是函數function(chunk) {} */ chunks: 'async', minChunks: 1, // 模塊至少被引用的次數 /** * 配置的優先級順序 * maxInitialRequest/maxAsyncRequests < maxSize < minSize */ minSize: 30000, // 約30Kb maxSize: 0, // 用於http2和長期緩存 maxAsyncRequests: 5, // 異步並行加載的最大代碼塊數量 maxInitialRequests: 3, // 頁面初始化加載的最大代碼塊數量 automaticNameDelimiter: '~', // 自動生成的名字之間的間隔符號 /** * 值為true表示基於cacheGroups的key值自動生成名稱; * 也可以是函數name (module, chunks, cacheGroupKey) {return } * ⚠️名稱不能和入口代碼塊名稱相同,否則會被移除 * production模式下建議使用false */ name: true, /** * cacheGroups可以繼承和覆蓋👆提到的屬性; * 它還有三個額外的屬性: * 1. test * 2. priority * 3. reuseExistingChunk * 禁止默認緩存行為可以設置default: false */ cacheGroups: { // 默認node_modules中的代碼打入venders中 vendors: { /** * test指定被打入該代碼塊的模塊 * 1. regExp * 2. 函數function(module, chunks) {} */ test: /[\\/]node_modules[\\/]/, priority: -10, name: 'xxx', // 覆蓋自動生成的名字 enforce: true //忽略繼承的minSize等屬性,總是生成代碼塊 }, // 默認本地代碼的打包行為 default: { minChunks: 2, priority: -20, reuseExistingChunk: true } } } },
可以根據上面的配置信息,自定義需要打包的模塊
// 將node_modules中的react和react-dom打包 optimization: { splitChunks: { vendors: { test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, name: 'vendors', chunks: 'all' } } },