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; // WorkboxPlugin 用于生成Service Worker const { InjectManifest } = require('workbox-webpack-plugin'); // 引入 path 模块 const path = require('path') function resolve(dir) { return path.join(__dirname, dir) } // Vue CLI 会按 mode 加载 .env.*;这里再读一遍,给 vue.config.js 自身用 dotenv.config(); dotenv.config({ path: path.resolve(__dirname, `.env.${process.env.NODE_ENV || 'development'}`) }); // 定义CDN资源列表,确保Service Worker也能访问 const cdnResources = { css: [ 'https://unpkg.com/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://unpkg.com/vue@2.6.14/dist/vue.min.js', 'https://unpkg.com/vue-router@3.6.5/dist/vue-router.min.js', 'https://unpkg.com/vuex@3.6.2/dist/vuex.min.js', 'https://unpkg.com/element-ui@2.15.14/lib/index.js', 'https://unpkg.com/axios@0.27.2/dist/axios.min.js', 'https://unpkg.com/opus-decoder@0.7.7/dist/opus-decoder.min.js' ] }; // 判断是否使用CDN const useCDN = process.env.VUE_APP_USE_CDN === 'true'; module.exports = defineConfig({ productionSourceMap: process.env.NODE_ENV !== 'production', // 生产环境不生成 source map devServer: { port: 8001, // 指定端口为 8001 proxy: { '/xiaozhi': { target: 'http://127.0.0.1:8002', changeOrigin: true } }, client: { overlay: false, // 不显示 webpack 错误覆盖层 }, }, publicPath: process.env.VUE_APP_PUBLIC_PATH || "/", chainWebpack: config => { // 修改 HTML 插件配置,动态插入 CDN 链接 config.plugin('html') .tap(args => { // 根据配置决定是否使用CDN if (process.env.NODE_ENV === 'production' && useCDN) { args[0].cdn = cdnResources; } 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, }, } }); // webpack 5 开发模式默认也会开 usedExports,会把 Vue 2 SFC 的 style // default export treeshake 掉(大量 style0 warning)。minimize 还会破坏 // Vue 2 渲染函数,导致页面空白和 setAttribute 报错。这些只给生产构建。 if (process.env.NODE_ENV === 'production') { config.optimization.usedExports(true); config.optimization.concatenateModules(true); config.optimization.minimize(true); } else { config.optimization.usedExports(false); config.optimization.concatenateModules(false); config.optimization.minimize(false); } // Vue 2 + vue-style-loader 需要 css-loader 的 CommonJS 导出 const ruleTypes = ['css', 'postcss', 'scss', 'sass', 'less', 'stylus'] ruleTypes.forEach(ruleType => { const rule = config.module.rule(ruleType) if (!rule) return const oneOfs = rule.oneOfs && rule.oneOfs.store if (!oneOfs) return oneOfs.forEach(oneOf => { if (oneOf.uses.has('css-loader')) { oneOf.use('css-loader').tap(options => { options = options || {} options.esModule = false return options }) } }) }) }, 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 }) ); // 根据是否使用CDN来决定是否添加Service Worker config.plugins.push( new InjectManifest({ swSrc: path.resolve(__dirname, 'src/service-worker.js'), swDest: 'service-worker.js', exclude: [/\.map$/, /asset-manifest\.json$/], maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB // 自定义Service Worker注入点 injectionPoint: 'self.__WB_MANIFEST', // 添加额外信息传递给Service Worker additionalManifestEntries: useCDN ? [{ url: 'cdn-mode', revision: 'enabled' }] : [{ url: 'cdn-mode', revision: 'disabled' }] }) ); // 如果使用CDN,则配置externals排除依赖包 if (useCDN) { config.externals = { 'vue': 'Vue', 'vue-router': 'VueRouter', 'vuex': 'Vuex', 'element-ui': 'ELEMENT', 'axios': 'axios', 'opus-decoder': 'OpusDecoder' }; } else { // 确保不使用CDN时不设置externals,让webpack打包所有依赖 config.externals = {}; } 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] // 每次配置文件修改时缓存失效 } }; } }, // 将CDN资源信息暴露给service-worker.js pwa: { workboxOptions: { skipWaiting: true, clientsClaim: true } } });