webpack.config.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  1. const path = require('path')
  2. const fs = require('fs')
  3. const webpack = require('webpack')
  4. const HtmlWebpackPlugin = require('html-webpack-plugin')
  5. const MiniCssExtractPlugin = require('mini-css-extract-plugin')
  6. const { VueLoaderPlugin } = require('vue-loader')
  7. const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
  8. const TerserPlugin = require('terser-webpack-plugin')
  9. const CopyWebpackPlugin = require('copy-webpack-plugin')
  10. // 预加载插件用于资源预加载
  11. const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')
  12. // 资源压缩插件
  13. const CompressionWebpackPlugin = require('compression-webpack-plugin')
  14. // 读取环境变量文件
  15. function loadEnvFile(envPath) {
  16. if (fs.existsSync(envPath)) {
  17. const envContent = fs.readFileSync(envPath, 'utf8')
  18. const envVars = {}
  19. envContent.split('\n').forEach(line => {
  20. // 跳过注释和空行
  21. const trimmedLine = line.trim()
  22. if (!trimmedLine || trimmedLine.startsWith('#')) return
  23. const equalIndex = trimmedLine.indexOf('=')
  24. if (equalIndex > 0) {
  25. const key = trimmedLine.substring(0, equalIndex).trim()
  26. const value = trimmedLine.substring(equalIndex + 1).trim()
  27. // 移除首尾的引号(单引号或双引号)
  28. const cleanValue = value.replace(/^["']|["']$/g, '')
  29. envVars[key] = cleanValue
  30. }
  31. })
  32. return envVars
  33. }
  34. return {}
  35. }
  36. // 根据环境加载对应的 .env 文件
  37. // 确保 NODE_ENV 有正确的值
  38. const nodeEnv = process.env.NODE_ENV || 'development'
  39. const isDevelopment = process.env.NODE_ENV === 'development'
  40. const isProduction = process.env.NODE_ENV === 'production'
  41. let envVars = {}
  42. if (isProduction) {
  43. envVars = loadEnvFile('.env.production')
  44. } else if (isDevelopment) {
  45. envVars = loadEnvFile('.env.development')
  46. }
  47. // 合并到 process.env
  48. Object.assign(process.env, envVars)
  49. // 获取 public path
  50. const getPublicPath = () => {
  51. const routerBase = process.env.VUE_ROUTER_BASE || process.env.VUE_APP_ROUTER_BASE || '/'
  52. // 确保路径以斜杠结尾
  53. const normalizedPath = routerBase.endsWith('/') ? routerBase : routerBase + '/'
  54. const publicPath = isProduction ? normalizedPath : '/'
  55. console.log('==========',process.env.NODE_ENV)
  56. console.log(`🔧 构建环境: ${process.env.NODE_ENV || 'development'}`)
  57. console.log(`📁 Public Path: ${publicPath}`)
  58. if (isProduction && (process.env.VUE_ROUTER_BASE || process.env.VUE_APP_ROUTER_BASE)) {
  59. console.log(`📖 从 .env.production 读取路由基础路径: ${routerBase}`)
  60. }
  61. return publicPath
  62. }
  63. // 验证并设置正确的 mode 值
  64. const getMode = () => {
  65. const validModes = ['development', 'production', 'none']
  66. if (validModes.includes(nodeEnv)) {
  67. return nodeEnv
  68. }
  69. // 如果 NODE_ENV 不是有效值,根据情况设置默认值
  70. console.warn(`⚠️ NODE_ENV "${nodeEnv}" 不是有效的 webpack mode,默认使用 development`)
  71. return 'development'
  72. }
  73. module.exports = {
  74. mode: getMode(),
  75. // 添加这个配置来抑制特定警告
  76. stats: {
  77. warnings: isDevelopment ? false : true, // 开发环境完全关闭警告,生产环境显示
  78. warningsFilter: [
  79. /export .* was not found in/,
  80. /Deprecation Warning/,
  81. /sass-lang\.com\/d\/import/,
  82. /Global built-in functions are deprecated/,
  83. /@import rules are deprecated/,
  84. /Invalid deprecation/,
  85. /Module Warning \(from \.\/node_modules\/sass-loader/,
  86. /Sass @import rules are deprecated/,
  87. /More info and automated migrator/,
  88. /downloadByUrl.*was not found/,
  89. /possible exports:/,
  90. /WARNING in/,
  91. /Module not found/,
  92. /Can't resolve/,
  93. /\[BABEL\] Note:/,
  94. /code generator has deoptimised/,
  95. /exceeds the max of/
  96. ]
  97. },
  98. // 缓存配置 - 大幅提升重新构建速度
  99. cache: {
  100. type: 'filesystem',
  101. buildDependencies: {
  102. config: [__filename],
  103. },
  104. cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack'),
  105. compression: 'gzip',
  106. profile: false,
  107. maxMemoryGenerations: isDevelopment ? 5 : Infinity,
  108. maxAge: 1000 * 60 * 60 * 24 * 7, // 7天
  109. allowCollectingMemory: isDevelopment,
  110. },
  111. entry: {
  112. main: path.resolve(__dirname, 'src/main.js')
  113. },
  114. output: {
  115. path: path.resolve(__dirname, 'dist'),
  116. filename: isDevelopment ? 'js/[name].js' : 'js/[name].[contenthash:8].js',
  117. chunkFilename: isDevelopment ? 'js/[name].chunk.js' : 'js/[name].[contenthash:8].chunk.js',
  118. publicPath: getPublicPath(),
  119. clean: true,
  120. assetModuleFilename: 'assets/[name].[hash:8][ext]'
  121. },
  122. resolve: {
  123. alias: {
  124. '@': path.resolve(__dirname, 'src'),
  125. '@components': path.resolve(__dirname, 'src/components'),
  126. '@views': path.resolve(__dirname, 'src/views'),
  127. '@utils': path.resolve(__dirname, 'src/utils'),
  128. '@lib': path.resolve(__dirname, 'src/components/lib'),
  129. '@assets': path.resolve(__dirname, 'src/assets'),
  130. '@api': path.resolve(__dirname, 'src/api'),
  131. '#': path.resolve(__dirname, 'public'),
  132. 'vue$': 'vue/dist/vue.esm.js',
  133. 'element-ui/lib/el-tooltip': 'element-ui/lib/tooltip',
  134. // Component Gallery 别名
  135. '@component-gallery/assets': path.join(__dirname, 'src/components/common-assets'),
  136. '@component-gallery/utils': path.join(__dirname, 'src/components/common-inner-utils'),
  137. '@component-gallery/theme-chalk': path.join(__dirname, 'src/components/theme-chalk'),
  138. '~@component-gallery/theme-chalk': path.join(__dirname, 'src/components/theme-chalk'),
  139. "@component-gallery/build-event-bus-path": path.join(__dirname, 'src/components/build-event-bus-path/lib/main'),
  140. "@component-gallery/base-components": path.join(__dirname, 'src/components/common-base-components'),
  141. "@component-gallery/base-art-player": path.join(__dirname, 'src/components/common-base-art-player'),
  142. "@component-gallery/base-video-player": path.join(__dirname, 'src/components/common-base-component-video-player'),
  143. "@component-gallery/video-map-camera-position": path.join(__dirname, 'src/components/common-comp-video-map-camera-position/src/entry/CommonVideoMapCameraPosition.vue'),
  144. "@component-gallery/video-map-view": path.join(__dirname, 'src/components/common-comp-video-map-view/src/entry/CommonVideoMapView.vue'),
  145. "@component-gallery/horn-shout": path.join(__dirname, 'src/components/common-comp-horn-shout/src/entry/CommonHornShout.vue'),
  146. "@component-gallery/lib": path.join(__dirname, 'src/components/lib'),
  147. "@component-gallery/one-key-alarm": path.join(__dirname, 'src/components/common-comp-one-key-alarm/src/entry/CommonOneKeyAlarm.vue'),
  148. "@component-gallery/alarm-list": path.join(__dirname, 'src/components/common-comp-alarm-list'),
  149. "@component-gallery/alarm-detail": path.join(__dirname, 'src/components/common-comp-alarm-detail/src/entry/CommonAlarmDetail.vue'),
  150. "@component-gallery/horn-tree": path.join(__dirname, 'src/components/common-comp-horn-tree/src/entry/CommonHornTree.vue'),
  151. "@component-gallery/horn-broadcast": path.join(__dirname, 'src/components/common-comp-horn-broadcast/src/entry/CommonHornBroadcast.vue'),
  152. "@component-gallery/map": path.join(__dirname, 'src/components/common-comp-map/src/entry/CommonMap.vue'),
  153. "@component-gallery/tool-box": path.join(__dirname, 'src/components/common-comp-tool-box/src/entry/CommonToolBox.vue'),
  154. "@component-gallery/kanzheli": path.join(__dirname, 'src/components/common-comp-kanzheli/src/entry/CommonKanzheli.vue'),
  155. "@component-gallery/alarm-filte": path.join(__dirname, 'src/components/common-comp-alarm-filte/src/entry/CommonAlarmFilte.vue'),
  156. "@component-gallery/camera-choose": path.join(__dirname, 'src/components/common-comp-camera-choose/src/CommonCameraChoose.vue'),
  157. "@component-gallery/coordinate-picking": path.join(__dirname, 'src/components/common-comp-coordinate-picking/src/entry/CommonCoordinatePicking.vue'),
  158. "@component-gallery/coordinate-positioning": path.join(__dirname, 'src/components/common-comp-coordinate-positioning/src/entry/CommonCoordinatePositioning.vue'),
  159. "@component-gallery/layers-control": path.join(__dirname, 'src/components/common-comp-layers-control/src/entry/CommonLayersControl.vue'),
  160. "@component-gallery/slope-aspect-analysis": path.join(__dirname, 'src/components/common-comp-slope-aspect-analysis/src/CommonSlopeAspectAnalysis.vue'),
  161. "@component-gallery/spot-detail": path.join(__dirname, 'src/components/common-comp-spot-detail/src/entry/SpotDetail.vue'),
  162. "@component-gallery/tool-camera-filte": path.join(__dirname, 'src/components/common-comp-tool-camera-filte/src/CommonCameraFilte.vue'),
  163. "@component-gallery/tool-config-item": path.join(__dirname, 'src/components/common-comp-tool-config-item/src/entry/CommonToolConfigItem.vue'),
  164. "@component-gallery/tools-plot": path.join(__dirname, 'src/components/common-comp-tools-plot/src/CommonToolsPlot.vue'),
  165. "@component-gallery/viewshed-analysis": path.join(__dirname, 'src/components/common-comp-viewshed-analysis/src/CommonViewshedAnalysis.vue'),
  166. "@component-gallery/tool-measure": path.join(__dirname, 'src/components/common-comp-tool-measure/src/entry/CommonCompToolMeasure.vue'),
  167. "@component-gallery/tool-section-analyse": path.join(__dirname, 'src/components/common-comp-tool-section-analyse/src/entry/CommonCompToolSectionAnalyse.vue'),
  168. "@component-gallery/layer-style-config": path.join(__dirname, 'src/components/common-comp-layer-style-config/src/entry/CommonLayerStyleConfig.vue'),
  169. "@component-gallery/azimuthal-measurement": path.join(__dirname, 'src/components/common-comp-azimuthal-measurement/src/entry/AzimuthalMeasurement.vue'),
  170. "@component-gallery/terrain-dig": path.join(__dirname, 'src/components/common-comp-terrain-dig/src/entry/TerrainDig.vue'),
  171. "@component-gallery/lnglat-select-point": path.join(__dirname, 'src/components/common-comp-lnglat-select-point/src/entry/CommonLnglatSelectPoint.vue'),
  172. "@component-gallery/tool-spot": path.join(__dirname, 'src/components/common-comp-tool-spot/src/entry/SpotSearch.vue'),
  173. "@component-gallery/dialog-tool": path.join(__dirname, 'src/components/common-comp-dialog-tool/src/entry/CommonDialogTool.vue'),
  174. "@component-gallery/layers-tool": path.join(__dirname, 'src/components/common-comp-layers-tool/src/entry/CommonLayersTool.vue'),
  175. "@component-gallery/horn-circle-selection": path.join(__dirname, 'src/components/common-comp-horn-circle-selection/src/entry/CommonHornCircleSelection.vue'),
  176. "@component-gallery/around-analysis": path.join(__dirname, 'src/components/common-comp-around-analysis/src/CommonAroundAnalysis.vue'),
  177. "@component-gallery/video-inspection-polygon": path.join(__dirname, 'src/components/common-comp-video-inspection-polygon/src/entry/CommonVideoInspectionPolygon.vue'),
  178. "@component-gallery/footer": path.join(__dirname, 'src/components/common-comp-footer/src/entry/CommonFooter.vue'),
  179. "@component-gallery/multimodal-assistant": path.join(__dirname, 'src/components/common-comp-multimodal-assistant/src/entry/CommonMultimodalAssistant.vue'),
  180. "@component-gallery/alarm-detail-large": path.join(__dirname, 'src/components/common-comp-alarm-detail-large/src/entry/AlarmDetailLarge.vue'),
  181. "@component-gallery/alarm-transfer-event": path.join(__dirname, 'src/components/common-comp-alarm-transfer-event/src/entry/AlarmTransferEvent.vue'),
  182. "@component-gallery/tree": path.join(__dirname, 'src/components/common-comp-tree/src/entry/CommonTree.vue'),
  183. "@component-gallery/iot-tree": path.join(__dirname, 'src/components/common-comp-iot-tree/src/entry/CommonIotTree.vue'),
  184. "@component-gallery/radar-tree": path.join(__dirname, 'src/components/common-comp-radar-tree/src/entry/CommonRadarTree.vue'),
  185. "@component-gallery/uav-tree": path.join(__dirname, 'src/components/common-comp-uav-tree/src/entry/CommonUavTree.vue'),
  186. "@component-gallery/source-tree": path.join(__dirname, 'src/components/common-comp-source-tree/src/entry/CommonSourceTree.vue'),
  187. "@component-gallery/grid-tree": path.join(__dirname, 'src/components/common-comp-grid-tree/src/entry/CommonGridTree.vue'),
  188. "@component-gallery/grid-operator-tree": path.join(__dirname, 'src/components/common-comp-grid-operator-tree/src/entry/CommonGridOperatorTree.vue'),
  189. "@component-gallery/track-popup": path.join(__dirname, 'src/components/common-comp-track-popup/src/entry/TrackPopup.vue'),
  190. "@component-gallery/tree-recorder": path.join(__dirname, 'src/components/common-comp-tree-recorder/src/entry/CommonTreeRecorder.vue'),
  191. "@component-gallery/multiple-device-search": path.join(__dirname, 'src/components/common-comp-multiple-device-search/src/entry/CommonMultipleDeviceSearch.vue'),
  192. "@component-gallery/camera-circle-selection": path.join(__dirname, 'src/components/common-comp-camera-circle-selection/src/CommonCameraCircleSelection.vue'),
  193. "@component-gallery/algorithm-configuration": path.join(__dirname, 'src/components/common-comp-algorithm-configuration/src/entry/AlgorithmConfiguration.vue'),
  194. "@component-gallery/algorithm-configuration-dialog": path.join(__dirname, 'src/components/common-comp-algorithm-configuration-dialog/src/entry/index.vue'),
  195. "@component-gallery/satellite-sense": path.join(__dirname, 'src/components/common-comp-tool-satellite-sense/src/entry/CommonSatelliteSense.vue'),
  196. "@component-gallery/tool-space": path.join(__dirname, 'src/components/common-comp-tool-space/src/entry/SpotSpace.vue'),
  197. "@component-gallery/tool-compound": path.join(__dirname, 'src/components/common-comp-tool-compound/src/entry/SpotCompound.vue'),
  198. "@component-gallery/tool-complex": path.join(__dirname, 'src/components/common-comp-tool-complex/src/entry/ToolComplex.vue'),
  199. "@component-gallery/tool-swiper": path.join(__dirname, 'src/components/common-comp-tool-swiper/src/entry/ToolSwiper.vue'),
  200. "@component-gallery/sense-time-line": path.join(__dirname, 'src/components/common-comp-sense-time-line/src/entry/SenseTimeLine.vue'),
  201. "@component-gallery/weather-small": path.join(__dirname, 'src/components/common-comp-weather-small/src/entry/CommonWeatherSmall.vue'),
  202. "@component-gallery/search-map": path.join(__dirname, 'src/components/common-comp-search-map/src/entry/CommonSearchMap.vue'),
  203. "@component-gallery/inspection-task-list": path.join(__dirname, 'src/components/common-comp-inspection-task-list/src/entry/CommonInspectionTaskList.vue'),
  204. },
  205. extensions: ['.js', '.vue', '.json', '.ts', '.tsx'],
  206. // 解决 webpack 5 严格模块解析问题
  207. fullySpecified: false,
  208. fallback: {
  209. path: require.resolve('path-browserify'),
  210. crypto: false,
  211. stream: false,
  212. fs: false,
  213. net: false,
  214. tls: false,
  215. buffer: require.resolve('buffer'),
  216. util: require.resolve('util')
  217. }
  218. },
  219. module: {
  220. rules: [
  221. // 处理第三方库的严格模块解析问题
  222. {
  223. test: /\.m?js$/,
  224. resolve: {
  225. fullySpecified: false
  226. }
  227. },
  228. // 处理 @ct 相关的 ES6 语法问题
  229. {
  230. test: /\.m?js$/,
  231. include: [
  232. // 指定需要转译的 node_modules 包
  233. /node_modules\/@ct/,
  234. /node_modules\/marked/,
  235. /node_modules\/@ct\/ais-js-kit/
  236. ],
  237. use: {
  238. loader: 'babel-loader',
  239. options: {
  240. presets: [['@babel/preset-env', { modules: false }]],
  241. plugins: [
  242. '@babel/plugin-proposal-nullish-coalescing-operator',
  243. '@babel/plugin-proposal-optional-chaining',
  244. ],
  245. },
  246. },
  247. },
  248. // Vue 文件处理
  249. {
  250. test: /\.vue$/,
  251. loader: 'vue-loader',
  252. options: {
  253. compilerOptions: {
  254. preserveWhitespace: false
  255. }
  256. }
  257. },
  258. // JavaScript 文件处理
  259. {
  260. test: /\.js$/,
  261. exclude: /node_modules(?!\/(@ct|marked))/,
  262. use: {
  263. loader: 'babel-loader',
  264. options: {
  265. presets: ['@babel/preset-env'],
  266. plugins: [
  267. "@babel/plugin-proposal-optional-chaining",
  268. "@babel/plugin-proposal-nullish-coalescing-operator",
  269. "@babel/plugin-proposal-class-properties"
  270. ],
  271. // 启用缓存
  272. cacheDirectory: true,
  273. cacheCompression: false
  274. // plugins: [
  275. // [
  276. // 'babel-plugin-component',
  277. // {
  278. // libraryName: 'element-ui',
  279. // libraryDirectory: 'lib',
  280. // styleLibraryName: 'theme-chalk'
  281. // },
  282. // 'element-ui'
  283. // ]
  284. // ]
  285. }
  286. }
  287. },
  288. // CSS 文件处理
  289. {
  290. test: /\.css$/,
  291. use: [
  292. isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
  293. {
  294. loader: 'css-loader',
  295. options: {
  296. sourceMap: true,
  297. // 处理CSS中的URL引用
  298. url: {
  299. filter: (url, resourcePath) => {
  300. // 对于第三方包中的静态资源路径,返回false让webpack跳过处理
  301. // 我们将通过CopyPlugin来复制这些资源
  302. if (resourcePath.includes('node_modules/@ct/component-gallery-theme-chalk') &&
  303. url.startsWith('./static/')) {
  304. return false;
  305. }
  306. return true;
  307. },
  308. },
  309. }
  310. },
  311. {
  312. loader: 'postcss-loader',
  313. options: {
  314. postcssOptions: {
  315. plugins: [
  316. require('postcss-pxtorem')({
  317. rootValue: 100,
  318. propList: ['*'],
  319. // exclude: /node_modules/i
  320. exclude(file) {
  321. if (file.indexOf("component-gallery-theme-chalk") !== -1 || file.indexOf("common-base-component-video-player-listener") !== -1) {
  322. return true;
  323. } else {
  324. return false;
  325. }
  326. }
  327. })
  328. ]
  329. }
  330. }
  331. }
  332. ]
  333. },
  334. // SCSS 文件处理
  335. {
  336. test: /\.scss$/,
  337. use: [
  338. isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
  339. {
  340. loader: 'css-loader',
  341. options: {
  342. sourceMap: true,
  343. }
  344. },
  345. // {
  346. // loader: 'postcss-loader',
  347. // options: {
  348. // sourceMap: true,
  349. // postcssOptions: {
  350. // plugins: [
  351. // require('postcss-pxtorem')({
  352. // rootValue: 16,
  353. // propList: ['*'],
  354. // exclude: /node_modules/i
  355. // })
  356. // ]
  357. // }
  358. // }
  359. // },
  360. {
  361. loader: 'resolve-url-loader',
  362. options: {
  363. sourceMap: true,
  364. }
  365. },
  366. {
  367. loader: 'sass-loader',
  368. options: {
  369. sourceMap: true,
  370. sassOptions: {
  371. // 抑制 Sass 弃用警告
  372. quietDeps: true,
  373. verbose: false,
  374. // 使用通配符抑制所有弃用警告
  375. silenceDeprecations: ['*']
  376. }
  377. },
  378. },
  379. ]
  380. },
  381. // 图片文件处理
  382. {
  383. test: /\.(png|jpe?g|gif|svg|webp|tiff?)$/i,
  384. type: 'asset',
  385. parser: {
  386. dataUrlCondition: {
  387. maxSize: 4 * 1024 // 4KB以下的图片转为base64,减少小图片请求
  388. }
  389. },
  390. generator: {
  391. filename: 'img/[name].[hash:8][ext]'
  392. }
  393. },
  394. // 字体文件处理
  395. {
  396. test: /\.(woff|woff2|eot|ttf|otf)$/i,
  397. type: 'asset',
  398. parser: {
  399. dataUrlCondition: {
  400. maxSize: 8 * 1024
  401. }
  402. },
  403. generator: {
  404. filename: 'font/[name].[hash:8][ext]'
  405. }
  406. },
  407. // 媒体文件处理
  408. {
  409. test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)$/i,
  410. type: 'asset',
  411. generator: {
  412. filename: 'media/[name].[hash:8][ext]'
  413. }
  414. }
  415. ]
  416. },
  417. plugins: [
  418. new VueLoaderPlugin(),
  419. new HtmlWebpackPlugin({
  420. template: path.resolve(__dirname, 'public/index.html'),
  421. filename: 'index.html',
  422. inject: true,
  423. templateParameters: {
  424. BASE_URL: getPublicPath(),
  425. VUE_APP_ROUTER_BASE: getPublicPath()
  426. },
  427. publicPath: getPublicPath(),
  428. // 优化资源加载优先级
  429. scriptLoading: 'defer',
  430. minify: isProduction ? {
  431. removeComments: true,
  432. collapseWhitespace: true,
  433. removeRedundantAttributes: true,
  434. useShortDoctype: true,
  435. removeEmptyAttributes: true,
  436. removeStyleLinkTypeAttributes: true,
  437. keepClosingSlash: true,
  438. minifyJS: true,
  439. minifyCSS: true,
  440. minifyURLs: true
  441. } : false
  442. }),
  443. new webpack.DefinePlugin({
  444. // 创建一个干净的环境变量对象,避免冲突
  445. 'process.env': JSON.stringify({
  446. NODE_ENV: process.env.NODE_ENV || 'development',
  447. VUE_APP_ROUTER_BASE: getPublicPath(),
  448. VUE_ROUTER_BASE: process.env.VUE_ROUTER_BASE || process.env.VUE_APP_ROUTER_BASE || '/', // 确保VUE_ROUTER_BASE被传递
  449. // 只包含需要的环境变量,避免泄露敏感信息
  450. ...Object.keys(process.env)
  451. .filter(key => key.startsWith('VUE_APP_') || key === 'NODE_ENV' || key === 'VUE_ROUTER_BASE')
  452. .reduce((env, key) => {
  453. env[key] = process.env[key];
  454. return env;
  455. }, {})
  456. }),
  457. 'process.browser': true,
  458. 'process.version': JSON.stringify(process.version),
  459. 'process.platform': JSON.stringify(process.platform),
  460. 'process.type': JSON.stringify('renderer')
  461. }),
  462. new CopyWebpackPlugin({
  463. patterns: [
  464. {
  465. from: path.resolve(__dirname, 'public'),
  466. to: path.resolve(__dirname, 'dist'),
  467. globOptions: {
  468. ignore: ['**/index.html']
  469. }
  470. },
  471. // 复制本地assets到static目录,以匹配第三方CSS包中的相对路径引用
  472. {
  473. from: path.resolve(__dirname, 'src/components/common-assets/image'),
  474. to: path.resolve(__dirname, 'dist/static/image'),
  475. globOptions: {
  476. ignore: ['**/.DS_Store']
  477. }
  478. }
  479. ]
  480. })
  481. ].concat(isProduction ? [
  482. new MiniCssExtractPlugin({
  483. filename: 'css/[name].[contenthash:8].css',
  484. chunkFilename: 'css/[name].[contenthash:8].css',
  485. ignoreOrder: true // 忽略 CSS 顺序警告
  486. }),
  487. // 预加载关键资源 - 只在生产环境使用,排除图片资源
  488. new PreloadWebpackPlugin({
  489. rel: 'preload',
  490. as(entry) {
  491. if (/\.css$/.test(entry)) return 'style';
  492. if (/\.woff2?$/.test(entry)) return 'font';
  493. // 移除图片的 preload,让它们按需加载
  494. // if (/\.(png|jpg|jpeg|gif|svg)$/.test(entry)) return 'image';
  495. return 'script';
  496. },
  497. include: ['vue-vendor', 'element-ui', 'main'], // 预加载关键chunk
  498. fileBlacklist: [
  499. /\.map$/,
  500. /hot-update\.js$/,
  501. /\.(png|jpg|jpeg|gif|svg|webp|ico)$/ // 排除所有图片文件
  502. ]
  503. }),
  504. // 预获取其他资源 - 只在生产环境使用,排除图片资源
  505. new PreloadWebpackPlugin({
  506. rel: 'prefetch',
  507. include: 'asyncChunks', // 预获取异步chunk
  508. fileBlacklist: [
  509. /\.map$/,
  510. /hot-update\.js$/,
  511. /\.(png|jpg|jpeg|gif|svg|webp|ico)$/ // 排除所有图片文件
  512. ]
  513. }),
  514. // Gzip压缩 - 提高加载速度,减少带宽消耗
  515. new CompressionWebpackPlugin({
  516. algorithm: 'gzip',
  517. test: /\.(js|css|html|svg)$/,
  518. threshold: 8192, // 8KB以上的文件才压缩
  519. minRatio: 0.8,
  520. deleteOriginalAssets: false // 保留原文件
  521. }),
  522. // Brotli压缩 - 比Gzip更高的压缩率
  523. new CompressionWebpackPlugin({
  524. algorithm: 'brotliCompress',
  525. test: /\.(js|css|html|svg)$/,
  526. compressionOptions: {
  527. level: 11,
  528. },
  529. threshold: 8192,
  530. minRatio: 0.8,
  531. filename: '[path][base].br',
  532. deleteOriginalAssets: false
  533. })
  534. ] : []),
  535. optimization: {
  536. minimize: isProduction,
  537. minimizer: [
  538. new TerserPlugin({
  539. terserOptions: {
  540. compress: {
  541. drop_console: isProduction,
  542. drop_debugger: isProduction
  543. }
  544. },
  545. parallel: 2 // 减少并行处理数量,降低内存使用
  546. }),
  547. // 添加CSS压缩器
  548. new CssMinimizerPlugin({
  549. parallel: 2,
  550. minimizerOptions: {
  551. preset: [
  552. 'default',
  553. {
  554. discardComments: { removeAll: true },
  555. },
  556. ],
  557. },
  558. })
  559. ],
  560. splitChunks: {
  561. chunks: 'all',
  562. maxInitialRequests: 6, // 减少初始请求数量
  563. maxAsyncRequests: 8, // 减少异步请求数量
  564. minSize: 100000, // 100KB 最小包大小,避免过小的包
  565. maxSize: 8000000, // 8MB 最大包大小,允许更大的包
  566. cacheGroups: {
  567. // Vue 生态系统合并打包
  568. vue: {
  569. test: /[\\/]node_modules[\\/](vue|vue-router|vuex|@vue)[\\/]/,
  570. name: 'vue-vendor',
  571. chunks: 'all',
  572. priority: 30,
  573. enforce: true // 强制打包,不受大小限制
  574. },
  575. // Element UI 完整生态
  576. elementUI: {
  577. test: /[\\/]node_modules[\\/](element-ui|element-theme-|@element)[\\/]/,
  578. name: 'element-ui',
  579. chunks: 'all',
  580. priority: 25,
  581. enforce: true
  582. },
  583. // 大型第三方库合并
  584. largeLibs: {
  585. test: /[\\/]node_modules[\\/](echarts|artplayer|hls\.js|mpegts\.js|fabric|@ct\/ct_map_ol|@ct\/icons-v2)[\\/]/,
  586. name: 'large-libs',
  587. chunks: 'all',
  588. priority: 23,
  589. enforce: true
  590. },
  591. // 工具库合并
  592. utils: {
  593. test: /[\\/]node_modules[\\/](lodash|moment|dayjs|axios|crypto-js|uuid|classnames|immer)[\\/]/,
  594. name: 'utils',
  595. chunks: 'all',
  596. priority: 21,
  597. enforce: true
  598. },
  599. // CT组件库合并
  600. ctComponents: {
  601. test: /[\\/]node_modules[\\/]@ct[\\/]/,
  602. name: 'ct-components',
  603. chunks: 'all',
  604. priority: 20,
  605. enforce: true
  606. },
  607. // 其他第三方库
  608. vendor: {
  609. test: /[\\/]node_modules[\\/]/,
  610. name: 'vendors',
  611. chunks: 'all',
  612. priority: 10,
  613. minSize: 200000, // 200KB 最小大小
  614. enforce: true
  615. },
  616. // 公共代码 - 更宽松的条件
  617. common: {
  618. name: 'common',
  619. minChunks: 2,
  620. chunks: 'all',
  621. priority: 5,
  622. reuseExistingChunk: true,
  623. minSize: 50000, // 50KB
  624. maxSize: 3000000 // 3MB
  625. }
  626. }
  627. }
  628. },
  629. devServer: {
  630. host: '0.0.0.0',
  631. historyApiFallback: true,
  632. static: [
  633. {
  634. directory: path.join(__dirname, 'public'),
  635. publicPath: getPublicPath()
  636. },
  637. // 添加静态资源服务,支持第三方CSS包的相对路径访问
  638. {
  639. directory: path.join(__dirname, 'src/components/common-assets/image'),
  640. publicPath: '/static/image'
  641. }
  642. ],
  643. // open: true,
  644. compress: true,
  645. hot: true, // 只使用HMR,不用liveReload
  646. port: 3000,
  647. allowedHosts: 'all',
  648. // 开发服务器性能优化
  649. devMiddleware: {
  650. writeToDisk: false, // 不写入磁盘,提高速度
  651. stats: 'errors-only', // 只显示错误
  652. },
  653. client: {
  654. logging: 'error', // 只显示错误级别的日志
  655. overlay: {
  656. errors: true,
  657. warnings: false, // 不显示警告
  658. runtimeErrors: false // 减少运行时错误覆盖
  659. },
  660. progress: false, // 关闭进度条减少内存
  661. webSocketTransport: 'ws' // 使用更高效的WebSocket传输
  662. },
  663. proxy: [
  664. {
  665. context: ['/api'],
  666. target: 'http://localhost:3000',
  667. changeOrigin: true,
  668. pathRewrite: { '^/api': '' }
  669. }
  670. ]
  671. },
  672. // 文件监听优化 - 减少内存使用
  673. watchOptions: {
  674. ignored: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/.webpack/**', '**/.cache/**'], // 使用glob模式
  675. aggregateTimeout: 500, // 增加延迟,减少编译频率
  676. poll: false, // 使用原生文件监听,更高效
  677. followSymlinks: false // 不跟随符号链接
  678. },
  679. devtool: isDevelopment ? 'eval-cheap-module-source-map' : false,
  680. performance: {
  681. hints: false,
  682. maxAssetSize: 2000000,
  683. maxEntrypointSize: 2000000
  684. },
  685. // 内存优化
  686. cache: {
  687. type: 'filesystem',
  688. cacheDirectory: path.resolve(__dirname, '.webpack'),
  689. compression: 'gzip',
  690. // 限制缓存大小,防止内存溢出
  691. maxMemoryGenerations: isDevelopment ? 1 : 0,
  692. // 内存管理配置
  693. memoryCacheUnaffected: true,
  694. buildDependencies: {
  695. config: [__filename]
  696. }
  697. }
  698. }