vue.config.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. const CompressionWebpackPlugin = require("compression-webpack-plugin");
  2. const { transformElementScss } = require("ele-admin/lib/utils/dynamic-theme");
  3. const path = require("path");
  4. const { name } = require("./package.json");
  5. function resolve(dir) {
  6. return path.join(__dirname, dir);
  7. }
  8. // 与 EOM(/eos)、WT(/wt) 一致:静态资源部署在短路径 /hr,乾坤路由前缀才是 /page-hr
  9. const publicPath = process.env.VUE_APP_PUBLIC_PATH || "/hr";
  10. /**
  11. * 本地 /api 代理。
  12. * - 直连后端(18086 等):接口在根路径,需要剥掉 /api
  13. * - 网关/nginx(51010 等):/api 才是接口,剥掉会打到前端 HTML(ele-admin-template)
  14. */
  15. function resolveApiProxyTarget(rawTarget) {
  16. const target = String(rawTarget || "")
  17. .trim()
  18. .replace(/\/$/, "");
  19. const alreadyHasApi = /\/api$/i.test(target);
  20. const isDirectBackend = /:(18086|18087|18186|18187)(\/|$)/.test(target);
  21. return {
  22. target: alreadyHasApi ? target.replace(/\/api$/i, "") : target,
  23. keepApiPrefix: alreadyHasApi || !isDirectBackend,
  24. };
  25. }
  26. function stripProxyCacheHeaders(proxyRes) {
  27. proxyRes.headers["cache-control"] = "no-store, no-cache, must-revalidate";
  28. proxyRes.headers.pragma = "no-cache";
  29. proxyRes.headers.expires = "0";
  30. delete proxyRes.headers.etag;
  31. delete proxyRes.headers["last-modified"];
  32. const contentType = String(proxyRes.headers["content-type"] || "");
  33. if (proxyRes.statusCode === 200 && contentType.includes("text/html")) {
  34. proxyRes.statusCode = 502;
  35. }
  36. }
  37. const apiProxy = resolveApiProxyTarget(
  38. process.env.VUE_APP_PROXY_TARGET ||
  39. // "http://192.168.1.147:18086",
  40. "http://192.168.1.113:18086",
  41. // "http://aiot.zoomwin.com.cn:51001",
  42. );
  43. // element-ui / ele-admin 仍使用 Sass @import,静音 Dart Sass 弃用警告
  44. const sassOptions = {
  45. outputStyle: "expanded",
  46. importer: transformElementScss(),
  47. quietDeps: true,
  48. silenceDeprecations: [
  49. "import",
  50. "global-builtin",
  51. "color-functions",
  52. "slash-div",
  53. "legacy-js-api",
  54. ],
  55. };
  56. module.exports = {
  57. publicPath,
  58. lintOnSave: false,
  59. outputDir: "dist",
  60. productionSourceMap: false,
  61. configureWebpack: {
  62. performance: {
  63. maxAssetSize: 2000000,
  64. maxEntrypointSize: 2000000,
  65. },
  66. output: {
  67. // 与 EOM/WT 一致:子应用打包成 umd 库格式
  68. library: {
  69. type: "umd",
  70. name: `${name}`,
  71. },
  72. chunkLoadingGlobal: `webpackJsonp_${name}`,
  73. },
  74. // Vue2 + vue-style-loader:样式靠副作用注入,不导出 default;
  75. // Webpack5 会对 `import style0 from ...` 报 missing export,可安全忽略
  76. ignoreWarnings: [
  77. {
  78. message:
  79. /export ['"]default['"] \(imported as ['"]style\d+['"]\) was not found/,
  80. },
  81. ],
  82. },
  83. devServer: {
  84. port: Number(process.env.VUE_APP_PORT || process.env.PORT || 8091),
  85. open: [publicPath.endsWith("/") ? publicPath : `${publicPath}/`],
  86. historyApiFallback: {
  87. index: path.posix.join(
  88. publicPath.endsWith("/") ? publicPath : `${publicPath}/`,
  89. "index.html",
  90. ),
  91. htmlAcceptHeaders: ["text/html"],
  92. rewrites: [
  93. {
  94. from: /^\/(api|kkfile)(\/|$)/,
  95. to: (context) => context.parsedUrl.pathname,
  96. },
  97. ],
  98. },
  99. setupMiddlewares(middlewares) {
  100. middlewares.unshift({
  101. name: "redirect-to-app",
  102. middleware(req, res, next) {
  103. const url = (req.url || "").split("?")[0];
  104. const appIndex = publicPath.endsWith("/")
  105. ? publicPath
  106. : `${publicPath}/`;
  107. if (
  108. url === "/" ||
  109. url === "/page-hr" ||
  110. url === "/page-hr/" ||
  111. url === "/hr-pc" ||
  112. url === "/hr-pc/"
  113. ) {
  114. res.redirect(appIndex);
  115. return;
  116. }
  117. next();
  118. },
  119. });
  120. return middlewares;
  121. },
  122. proxy: {
  123. "/api": {
  124. target: apiProxy.target,
  125. changeOrigin: true,
  126. pathRewrite: apiProxy.keepApiPrefix ? undefined : { "^/api": "" },
  127. onProxyRes: stripProxyCacheHeaders,
  128. },
  129. "/kkfile": {
  130. target: "http://aiot.zoomwin.com.cn:51010",
  131. changeOrigin: true,
  132. pathRewrite: { "^/kkfile": "/kkfile" },
  133. },
  134. },
  135. headers: {
  136. "Access-Control-Allow-Origin": "*",
  137. },
  138. },
  139. chainWebpack(config) {
  140. config.plugins.delete("prefetch");
  141. config.module.rule("svg").exclude.add(resolve("src/icons")).end();
  142. config.module
  143. .rule("icons")
  144. .test(/\.svg$/)
  145. .include.add(resolve("src/icons"))
  146. .end()
  147. .use("svg-sprite-loader")
  148. .loader("svg-sprite-loader")
  149. .options({
  150. symbolId: "icon-[name]",
  151. })
  152. .end();
  153. if (process.env.NODE_ENV !== "development") {
  154. config.plugin("compressionPlugin").use(
  155. new CompressionWebpackPlugin({
  156. test: /\.(js|css|html)$/,
  157. threshold: 10240,
  158. }),
  159. );
  160. }
  161. },
  162. css: {
  163. loaderOptions: {
  164. css: {
  165. esModule: false,
  166. },
  167. sass: { sassOptions },
  168. scss: { sassOptions },
  169. },
  170. },
  171. };