项目: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.log
if (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 map
configureWebpack: (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/webpack
cacheDirectory: 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 配置到 HTML
config.plugin('html').tap(args => {
args[0].cdn = assetsCDN
return 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 map
configureWebpack: (config) => {
if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer = [
new EsbuildPlugin ({
target: 'es2015', // 压缩为 ES6
drop: ['console'], // 移除 console
css: false
}),
new CssMinimizerPlugin()
]
// 配置外部依赖
config.externals = assetsCDN.externals;
}
// 缓存配置
config.cache = {
type: 'filesystem',
buildDependencies: {
config: [__filename], // vue.config.js 变更时失效
// dependencies: ['package.json'] // 依赖变更时失效(存在兼容性问题)
},
// 指定缓存位置,默认位置:node_modules/.cache/webpack
cacheDirectory: 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 配置到 HTML
config.plugin('html').tap(args => {
args[0].cdn = assetsCDN
return 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
}
}
}
}