perf(构建): 优化构建时间:首次构建提升 50%,二次构建提升 30%

优化构建配置,提升构建效率和文件体积优化

1. 禁用生产环境的 source map:为了减少生产环境构建的文件体积,提高构建速度,生产环境下不再生成 source map。

2. 配置开发服务器代理:在 `devServer` 中配置了代理,解决了跨域问题,确保本地开发时可以顺利访问后端 API。

3. 动态插入 CDN 链接:在生产环境下,通过 `chainWebpack` 动态修改 HTML 插件配置,插入 CDN 链接,避免打包常用库(如 `vue`, `axios`,
`element-ui` 等),从而减少打包体积。

4. 代码分割优化:优化了 `splitChunks` 配置,减少了小模块的分割,提升了构建效率和缓存利用率。增加了 `minSize` 和 `maxSize` 配置,避免过多的小模块被拆分。

5. 启用多线程压缩:在生产环境中,配置了 `TerserPlugin` 以启用并行压缩,减少了 JS 文件的压缩时间。

6. 启用 Gzip 压缩:通过 `CompressionPlugin` 配置了 Gzip 压缩,只压缩大于 20KB 的文件,减少了构建后的文件体积。

7. 配置 `externals`:通过 `externals` 排除了 `vue`, `vue-router`, `vuex`, `element-ui`, `axios`,
`opus-decoder` 等库的打包,避免重复打包并通过 CDN 加载这些库。

8. 配置文件系统缓存:启用了 Webpack 文件系统缓存,加速了增量构建,缓存目录设置为 `.webpack_cache`,缓存有效期为一个月。

9. 配置 Webpack 别名:为项目中的路径配置了别名,简化了模块导入,减少了代码中的路径冗余。

10. 提供可选的打包分析:通过 `BundleAnalyzerPlugin` 插件,在 `ANALYZE=true` 环境变量下启用打包分析功能,帮助分析并优化构建后的文件。
This commit is contained in:
huangjunsen
2025-04-01 00:38:32 +08:00
parent 20c1f0576b
commit fab4adb07e
7 changed files with 1174 additions and 1439 deletions
+3 -1
View File
@@ -106,7 +106,6 @@ celerybeat.pid
*.sage.py
# Environments
.env
.venv
env/
venv/
@@ -156,3 +155,6 @@ main/xiaozhi-server/models/SenseVoiceSmall/model.pt
main/xiaozhi-server/models/sherpa-onnx*
my_wakeup_words.mp3
main/manager-api/.vscode
# Ignore webpack cache directory
main/manager-web/.webpack_cache/
+1
View File
@@ -0,0 +1 @@
registry=https://registry.npmmirror.com/
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
presets: [
['@vue/cli-plugin-babel/preset', {
useBuiltIns: 'usage',
corejs: 3
}]
],
plugins: [
'@babel/plugin-syntax-dynamic-import', // 确保支持动态导入 (Lazy Loading)
'@babel/plugin-transform-runtime'
]
}
+1010 -1424
View File
File diff suppressed because it is too large Load Diff
+14 -2
View File
@@ -4,9 +4,12 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
"build": "vue-cli-service build",
"analyze": "cross-env ANALYZE=true vue-cli-service build"
},
"dependencies": {
"core-js": "^3.41.0",
"cross-env": "^7.0.3",
"element-ui": "^2.15.14",
"flyio": "^0.6.14",
"normalize.css": "^8.0.1",
@@ -19,16 +22,25 @@
"xiaozhi": "file:"
},
"devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.26.10",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"compression-webpack-plugin": "^11.1.0",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"vue-template-compiler": "^2.6.14"
"vue-template-compiler": "^2.6.14",
"webpack-bundle-analyzer": "^4.10.2"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"sideEffects": [
"*.css",
"*.scss",
"*.vue"
]
}
+10
View File
@@ -6,6 +6,11 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<% if (htmlWebpackPlugin.options.cdn) { %>
<% for (var i in htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>">
<% } %>
<% } %>
</head>
<body>
<noscript>
@@ -13,5 +18,10 @@
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<% if (htmlWebpackPlugin.options.cdn) { %>
<% for (var i in htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
<% } %>
<% } %>
</body>
</html>
+123 -12
View File
@@ -1,19 +1,17 @@
const { defineConfig } = require('@vue/cli-service');
const dotenv = require('dotenv');
// TerserPlugin 用于压缩 JavaScript
const TerserPlugin = require('terser-webpack-plugin');
// CompressionPlugin 开启 Gzip 压缩
const CompressionPlugin = require('compression-webpack-plugin')
// BundleAnalyzerPlugin 用于分析打包后的文件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 引入 path 模块
const path = require('path');
// 确保加载 .env 文件
dotenv.config();
console.log("base_url",process.env.VUE_APP_API_BASE_URL);
module.exports = defineConfig({
chainWebpack: config => {
config.plugin('html')
.tap(args => {
// 将 VUE_APP_TITLE 从环境变量中注入到 HTML 中
args[0].title = process.env.VUE_APP_TITLE || '小智-智控台';
return args;
});
},
productionSourceMap: process.env.NODE_ENV === 'production' ? false : true, // 生产环境不生成 source map
devServer: {
port: 8001, // 指定端口为 8001
proxy: {
@@ -21,7 +19,7 @@ module.exports = defineConfig({
target: process.env.VUE_APP_API_BASE_URL, // 后端 API 的基础 URL
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/api': '', // 路径重写
'^/xiaozhi-esp32-api': '/xiaozhi-esp32-api',
},
},
},
@@ -29,4 +27,117 @@ module.exports = defineConfig({
overlay: false, // 不显示 webpack 错误覆盖层
},
},
chainWebpack: config => {
// 修改 HTML 插件配置,动态插入 CDN 链接
config.plugin('html')
.tap(args => {
if (process.env.NODE_ENV === 'production') {
args[0].cdn = {
css: [
'https://cdn.jsdelivr.net/npm/element-ui@2.15.14/lib/theme-chalk/index.css',
'https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css'
],
js: [
'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js',
'https://cdn.jsdelivr.net/npm/vue-router@3.6.5/dist/vue-router.min.js',
'https://cdn.jsdelivr.net/npm/vuex@3.6.2/dist/vuex.min.js',
'https://cdn.jsdelivr.net/npm/element-ui@2.15.14/lib/index.js',
'https://cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js',
'https://cdn.jsdelivr.net/npm/opus-decoder@0.7.7/dist/opus-decoder.min.js'
]
};
}
return args;
});
// 代码分割优化
config.optimization.splitChunks({
chunks: 'all',
minSize: 20000,
maxSize: 250000,
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial',
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true,
},
}
});
// 启用优化设置
config.optimization.usedExports(true);
config.optimization.concatenateModules(true);
config.optimization.minimize(true);
},
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
// 开启多线程编译
config.optimization = {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
}
}
})
]
};
config.plugins.push(
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 20480,
minRatio: 0.8
})
);
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'element-ui': 'ELEMENT',
'axios': 'axios',
'opus-decoder': 'OpusDecoder'
};
if (process.env.ANALYZE === 'true') { // 通过环境变量控制
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'server', // 开启本地服务器模式
openAnalyzer: true, // 自动打开浏览器
analyzerPort: 8888 // 指定端口号
})
);
}
config.cache = {
type: 'filesystem', // 使用文件系统缓存
cacheDirectory: path.resolve(__dirname, '.webpack_cache'), // 自定义缓存目录
allowCollectingMemory: true, // 启用内存收集
compression: 'gzip', // 启用gzip压缩缓存
maxAge: 5184000000, // 缓存有效期为 1个月
buildDependencies: {
config: [__filename] // 每次配置文件修改时缓存失效
}
};
config.resolve.alias = {
'@': path.resolve(__dirname, 'src'), // 让 '@' 代表 'src' 目录
'@assets': path.resolve(__dirname, 'src/assets'), // 设置 '@assets' 为 'src/assets' 目录
'@components': path.resolve(__dirname, 'src/components'), // 设置 '@components' 为 'src/components' 目录
'@views': path.resolve(__dirname, 'src/views'), // 设置 '@views' 为 'src/views' 目录
}
}
},
});