webpack.config.js 29 KB

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