項目:jeecgboot-Vue2
在項目二次開發后,在本人電腦打包時間為3分35秒左右
webpack5默認優化:
- Tree Shaking(搖樹優化):刪除未使用的代碼
- base64 內聯: 小于 8KB 的資源(圖片等)進行 Base64 內聯,減少 HTTP 請求,會增加33%的大小
- …
原配置: vue.config.js
- 生產環境取消 console,productionSourceMap
- 生產環境js、css Gzip壓縮
- less轉譯
- …
const path = require('path')
const CompressionPlugin = require("compression-webpack-plugin")function resolve(dir) {return path.join(__dirname, dir)
}module.exports = {productionSourceMap: false,publicPath:'/',configureWebpack: config => {// 生產環境取消 console.logif (process.env.NODE_ENV === 'production') {config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true}},chainWebpack: (config) => {config.resolve.alias.set('@$', resolve('src')).set('@api', resolve('src/api')).set('@assets', resolve('src/assets')).set('@comp', resolve('src/components')).set('@views', resolve('src/views'))// 生產環境,開啟js\css壓縮if (process.env.NODE_ENV === 'production') {config.plugin('compressionPlugin').use(new CompressionPlugin({test: /\.(js|css|less)$/, // 匹配文件名threshold: 10240, // 對超過10k的數據壓縮deleteOriginalAssets: false // 不刪除源文件}))}// 配置 webpack 識別 markdown 為普通的文件config.module.rule('markdown').test(/\.md$/).use().loader('file-loader').end()// 編譯vxe-table包里的es6代碼,解決IE11兼容問題config.module.rule('vxe').test(/\.js$/).include.add(resolve('node_modules/vxe-table')).add(resolve('node_modules/vxe-table-plugin-antd')).end().use().loader('babel-loader').end()},css: {loaderOptions: {less: {modifyVars: {/* less 變量覆蓋,用于自定義 ant design 主題 */'primary-color': '#1890FF','link-color': '#1890FF','border-radius-base': '4px',},javascriptEnabled: true,}}},devServer: {port: 3000,proxy: {'/jeecg-boot': {target: 'http://localhost:8080',ws: false,changeOrigin: true},}},lintOnSave: undefined
}
優化后: 首次打包2分30秒,二次打包25秒
- CDN 外部依賴 (externals),HTML 自動注入CDN
- 持久化緩存 (cache),極大加速二次構建,修改配置后失效
- 移除 prefetch,避免預加載未使用的資源
- Moment.js 語言包裁剪,只保留中文語言包
- 圖片壓縮(image-webpack-loader)
- CSS壓縮(CssMinimizerPlugin)
- JS壓縮(terser)
- 多線程構建 (thread-loader),適用于中大型項目
const path = require('path')
const webpack = require('webpack')
const CompressionPlugin = require("compression-webpack-plugin")
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin')const assetsCDN = {externals: {'viser-vue': 'ViserVue'},js: ['//unpkg.com/viser-vue/umd/viser-vue.min.js'],css: []
}function resolve(dir) {return path.join(__dirname, dir)
}module.exports = {publicPath: '/',assetsDir: 'static',outputDir: 'dist',lintOnSave: undefined,filenameHashing: true, // 文件名哈希productionSourceMap: false, // 生產環境的 source mapconfigureWebpack: (config) => {if (process.env.NODE_ENV === 'production') {// terser 配置,壓縮JavaScript config.optimization.minimizer = [new TerserPlugin({terserOptions: {compress: {drop_console: true // 移除 console}}}),// css壓縮new CssMinimizerPlugin(),]// 配置外部依賴config.externals = assetsCDN.externals;}// 緩存配置config.cache = {type: 'filesystem',buildDependencies: {config: [__filename], // vue.config.js 變更時失效// dependencies: ['package.json'] // 依賴變更時失效(存在兼容性問題)},// 指定緩存位置,默認位置:node_modules/.cache/webpackcacheDirectory: path.resolve(__dirname, '.webpack_cache') }},chainWebpack: (config) => {// 添加多線程構建config.module.rule('js').use('thread-loader').loader('thread-loader').options({workers: require('os').cpus().length - 1, // CPU核心數減1}).end()// 移除 prefetch,避免加載多余的資源config.plugins.delete('prefetch')// 只保留中文語言包config.plugin('ContextReplacementPlugin').use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/, /zh-cn/])// 配置別名config.resolve.alias.set('@$', resolve('src')).set('@api', resolve('src/api')).set('@assets', resolve('src/assets')).set('@comp', resolve('src/components')).set('@views', resolve('src/views'))if (process.env.NODE_ENV === 'production') {// 添加 CDN 配置到 HTMLconfig.plugin('html').tap(args => {args[0].cdn = assetsCDNreturn args})// 開啟js\css Gzip壓縮config.plugin('compressionPlugin').use(new CompressionPlugin({test: /\.(js|css|less)$/, // 匹配文件名threshold: 10240, // 對超過10k的數據壓縮deleteOriginalAssets: false // 不刪除源文件minRatio: 0.8, // 壓縮比小于0.8不予壓縮}))// 圖片壓縮config.plugin('image-optimizer').use(ImageMinimizerPlugin, [{minimizer: {implementation: ImageMinimizerPlugin.squooshMinify,options: {encodeOptions: {mozjpeg: { quality: 80 },webp: { lossless: true },},},},}]);// 分包策略config.optimization.splitChunks({chunks: 'all',minSize: 20000,maxSize: 244000,maxAsyncRequests: 30, // 按需加載時的最大并行請求數maxInitialRequests: 20, // 入口點上的最大并行請求數cacheGroups: {vendors: {test: /[\\/]node_modules[\\/]/,priority: -10},common: {minChunks: 2,priority: -20,reuseExistingChunk: true}}})}// 配置 webpack 識別 markdown 為普通的文件config.module.rule('markdown').test(/\.md$/).use().loader('file-loader').end()// 編譯vxe-table包里的es6代碼,解決IE11兼容問題config.module.rule('vxe').test(/\.js$/).include.add(resolve('node_modules/vxe-table')).add(resolve('node_modules/vxe-table-plugin-antd')).end().use().loader('babel-loader').end()},css: {loaderOptions: {less: {modifyVars: {/* less 變量覆蓋,用于自定義 ant design 主題 */'primary-color': '#1890FF','link-color': '#1890FF','border-radius-base': '4px'},javascriptEnabled: true}}},devServer: {port: 3000,client: {overlay: {runtimeErrors: false}},proxy: {target: 'http://localhost:8080',ws: false,changeOrigin: true}}}
}
繼續優化: 首次打包1分50秒,二次打包25秒
- ESBuild :替代
terser
+babel
+thread-loader
,ESBuild 本身啟用多線程打包,用ESBuild加速JS轉譯,極大提升構建速度,需要考慮兼容性(現代瀏覽器) - Brotli 壓縮:Gzip 壓縮率高 15%~25%,但壓縮速度稍慢。需要服務器支持和瀏覽器支持,前端構建時預生成 .br 和 .gz,可通過瀏覽器請求頭判斷支持,服務器動態響應
const path = require('path')
const webpack = require('webpack')
const CompressionPlugin = require("compression-webpack-plugin")
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
const { EsbuildPlugin } = require('esbuild-loader')
const BrotliPlugin = require('brotli-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');// 注意版本
const assetsCDN = {externals: {'viser-vue': 'ViserVue','ant-design-vue': 'antd','vue': 'Vue','vue-router': 'VueRouter','vuex': 'Vuex'},js: ['//unpkg.com/viser-vue/umd/viser-vue.min.js','//unpkg.com/ant-design-vue@3.2.6/dist/antd.min.js','//unpkg.com/vue@3.2.47/dist/vue.global.prod.js','//unpkg.com/vue-router@4.2.2/dist/vue-router.global.prod.js','//unpkg.com/vuex@4.1.0/dist/vuex.global.prod.js'],css: ['//unpkg.com/ant-design-vue@3.2.6/dist/antd.min.css']
}function resolve(dir) {return path.join(__dirname, dir)
}module.exports = {publicPath: '/',assetsDir: 'static',outputDir: 'dist',lintOnSave: undefined,filenameHashing: true, // 文件名哈希productionSourceMap: false, // 生產環境的 source mapconfigureWebpack: (config) => {if (process.env.NODE_ENV === 'production') {config.optimization.minimizer = [new EsbuildPlugin ({target: 'es2015', // 壓縮為 ES6drop: ['console'], // 移除 consolecss: false}),new CssMinimizerPlugin()]// 配置外部依賴config.externals = assetsCDN.externals;}// 緩存配置config.cache = {type: 'filesystem',buildDependencies: {config: [__filename], // vue.config.js 變更時失效// dependencies: ['package.json'] // 依賴變更時失效(存在兼容性問題)},// 指定緩存位置,默認位置:node_modules/.cache/webpackcacheDirectory: path.resolve(__dirname, '.webpack_cache') }},chainWebpack: (config) => {// 移除 prefetch,避免加載多余的資源config.plugins.delete('prefetch')// 只保留中文語言包config.plugin('ContextReplacementPlugin').use(webpack.ContextReplacementPlugin, [/moment[/\\]locale$/, /zh-cn/])// 配置別名config.resolve.alias.set('@$', resolve('src')).set('@api', resolve('src/api')).set('@assets', resolve('src/assets')).set('@comp', resolve('src/components')).set('@views', resolve('src/views'))if (process.env.NODE_ENV === 'production') {// 添加 CDN 配置到 HTMLconfig.plugin('html').tap(args => {args[0].cdn = assetsCDNreturn args})// 開啟 Gzip 和 Brotli 壓縮config.plugin('compressionPlugin').use(new CompressionPlugin({test: /\.(js|css|less|scss|html|svg)$/,threshold: 10240,minRatio: 0.8,deleteOriginalAssets: false}))config.plugin('brotliPlugin').use(new BrotliPlugin({test: /\.(js|css|less|scss|html|svg)$/,threshold: 10240,minRatio: 0.8,deleteOriginalAssets: false}))// 圖片壓縮config.plugin('image-optimizer').use(ImageMinimizerPlugin, [{minimizer: {implementation: ImageMinimizerPlugin.squooshMinify,options: {encodeOptions: {mozjpeg: { quality: 80 },webp: { lossless: true },},},},}]);// 分包策略config.optimization.splitChunks({chunks: 'all',minSize: 20000,maxSize: 244000,maxAsyncRequests: 30, // 按需加載時的最大并行請求數maxInitialRequests: 20, // 入口點上的最大并行請求數cacheGroups: {vendors: {test: /[\\/]node_modules[\\/]/,priority: -10},common: {minChunks: 2,priority: -20,reuseExistingChunk: true}}})}// 配置 webpack 識別 markdown 為普通的文件config.module.rule('markdown').test(/\.md$/).use().loader('file-loader').end()// ESBuild 轉譯 JS(替代 Babel)config.module.rule('js').test(/\.js$/).exclude.add(/node_modules/).end().use('esbuild-loader').loader('esbuild-loader').options({ loader: 'jsx', target: 'es2015' });},css: {loaderOptions: {less: {modifyVars: {/* less 變量覆蓋,用于自定義 ant design 主題 */'primary-color': '#1890FF','link-color': '#1890FF','border-radius-base': '4px'},javascriptEnabled: true}}},devServer: {port: 3000,client: {overlay: {runtimeErrors: false}},proxy: {target: 'http://localhost:8080',ws: false,changeOrigin: true}}}
}