wangzhibo
2026-08-31 17ac2ce0ed52874616c79c910667e41e34604826
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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
    }
  }
});