webpack.config.js 29 KB

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