config.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. // https://umijs.org/config/
  2. import { defineConfig } from '@umijs/max';
  3. import dayjs from 'dayjs';
  4. import fs from 'fs';
  5. import { glob } from 'glob';
  6. import path, { join } from 'path';
  7. import defaultSettings from './defaultSettings';
  8. import proxy from './proxy';
  9. import routes from './routes';
  10. // 导入所有环境配置
  11. const allConfigs = glob.sync('*/**.ts', { cwd: path.join(__dirname, 'env') }).reduce(
  12. (configs, file) => {
  13. const [productId, envFile] = file.split(path.sep);
  14. const envName = path.basename(envFile, '.ts');
  15. if (!configs[productId]) {
  16. configs[productId] = {};
  17. }
  18. configs[productId][envName] = require(`./env/${file}`).default;
  19. return configs;
  20. },
  21. {} as Record<string, Record<string, NodeJS.ProcessEnv>>,
  22. );
  23. const { REACT_APP_ENV = 'prod', PROD_ID = 'go-pmp' } = process.env;
  24. const envConfig = Object.assign(
  25. allConfigs[PROD_ID]?.['default'] ?? {},
  26. allConfigs[PROD_ID]?.[REACT_APP_ENV as any] ?? {},
  27. );
  28. const buildTime = dayjs();
  29. envConfig.REACT_APP_VERSION = `${envConfig.REACT_APP_VERSION} build:${buildTime
  30. .unix()
  31. .toString(36)
  32. .toUpperCase()}`;
  33. const defSettings = {
  34. ...defaultSettings,
  35. title: process.env.REACT_APP_NAME,
  36. };
  37. // 路由过滤函数
  38. function filterRoutesByProd(routes: any[], prodId: string): any[] {
  39. return routes
  40. .map((route) => {
  41. // 检查是否应该显示该路由
  42. const shouldShow = (() => {
  43. // 如果定义了 includeProds,只在指定产品中显示
  44. if (route.includeProds?.length > 0) {
  45. return route.includeProds.includes(prodId);
  46. }
  47. // 如果定义了 excludeProds,在非指定产品中显示
  48. if (route.excludeProds?.length > 0) {
  49. return !route.excludeProds.includes(prodId);
  50. }
  51. // 默认显示
  52. return true;
  53. })();
  54. if (prodId !== 'go-pmp' && !shouldShow) {
  55. return null;
  56. }
  57. // 递归处理子路由
  58. if (route.routes) {
  59. const filteredChildren = filterRoutesByProd(route.routes, prodId);
  60. // 如果子路由全部被过滤掉了,且当前路由没有 component,则过滤掉当前路由
  61. if (filteredChildren.length === 0 && !route.component) {
  62. return null;
  63. }
  64. return {
  65. ...route,
  66. routes: filteredChildren,
  67. };
  68. }
  69. return route;
  70. })
  71. .filter(Boolean); // 过滤掉 null 值
  72. }
  73. export default defineConfig({
  74. /**
  75. * 注入环境变量
  76. */
  77. define: {
  78. 'process.env': {
  79. REACT_APP_ENV: REACT_APP_ENV,
  80. REACT_APP_ID: envConfig.REACT_APP_ID,
  81. REACT_APP_NAME: envConfig.REACT_APP_NAME,
  82. REACT_APP_VERSION: envConfig.REACT_APP_VERSION,
  83. REACT_APP_BUILD_TIME: buildTime.unix(),
  84. STORAGE_NAME_SPACE: envConfig.STORAGE_NAME_SPACE,
  85. ENABLE_REQUEST_ENCRYPTION: envConfig.ENABLE_REQUEST_ENCRYPTION,
  86. REQUEST_ENCRYPTION_KEY: envConfig.REQUEST_ENCRYPTION_KEY,
  87. ENABLE_STORAGE_ENCRYPTION: envConfig.ENABLE_STORAGE_ENCRYPTION,
  88. STORAGE_ENCRYPTION_KEY: envConfig.STORAGE_ENCRYPTION_KEY,
  89. REACT_APP_MANAGEMENT_KEY: envConfig.REACT_APP_MANAGEMENT_KEY,
  90. REQUEST_DATA_COMPRESSION: envConfig.REQUEST_DATA_COMPRESSION,
  91. REACT_APP_API_URL: envConfig.REACT_APP_API_URL,
  92. },
  93. },
  94. links: [
  95. {
  96. rel: 'icon',
  97. href: `/favicon-${envConfig.REACT_APP_ID}.ico`,
  98. },
  99. ],
  100. alias: {
  101. '@svgs': path.resolve(process.cwd(), 'svgs'),
  102. },
  103. chainWebpack(memo) {
  104. const svgsDir = path.resolve(process.cwd(), 'svgs');
  105. // 将 svgs/ 目录从所有能匹配 .svg 的规则中排除(包括 asset/resource 类型规则和 svgo-loader)
  106. Object.values(memo.module.rules.entries()).forEach((rule: any) => {
  107. const testRegex = rule.get?.('test');
  108. if (testRegex instanceof RegExp && testRegex.test('file.svg')) {
  109. rule.exclude.add(svgsDir);
  110. return;
  111. }
  112. const uses = rule.uses?.entries?.();
  113. if (!uses) return;
  114. Object.values(uses).forEach((use: any) => {
  115. if ((use.get?.('loader') ?? '').includes('svgo-loader')) {
  116. rule.exclude.add(svgsDir);
  117. }
  118. });
  119. });
  120. // SVG icon loader:自动处理 svgs/ 目录,无需手动运行 convert-svg-icon
  121. memo.module
  122. .rule('svgs-iconify')
  123. .test(/\.svg$/)
  124. .include.add(svgsDir)
  125. .end()
  126. .type('javascript/auto')
  127. .use('iconify-svg-loader')
  128. .loader(path.resolve(process.cwd(), 'tools/svgIconLoader.cjs'))
  129. .end();
  130. // 在生产环境构建时生成 version.json 文件
  131. if (REACT_APP_ENV === 'prod' || REACT_APP_ENV === 'test' || REACT_APP_ENV === 'dev') {
  132. memo.plugin('generate-version-file').use(
  133. class GenerateVersionFilePlugin {
  134. apply(compiler: any) {
  135. compiler.hooks.beforeCompile.tapAsync(
  136. 'GenerateVersionFilePlugin',
  137. (_params: any, callback: any) => {
  138. try {
  139. const publicDir = path.join(process.cwd(), 'public');
  140. if (!fs.existsSync(publicDir)) {
  141. fs.mkdirSync(publicDir, { recursive: true });
  142. }
  143. // 生成版本信息
  144. const versionInfo = {
  145. productId: envConfig.REACT_APP_ID,
  146. version: envConfig.REACT_APP_VERSION,
  147. buildTime: buildTime.format('YYYY-MM-DD HH:mm:ss'),
  148. buildTimestamp: buildTime.unix(),
  149. environment: REACT_APP_ENV,
  150. };
  151. // 写入 version.json 文件
  152. const versionFilePath = path.join(publicDir, 'version.json');
  153. fs.writeFileSync(versionFilePath, JSON.stringify(versionInfo, null, 2), 'utf8');
  154. console.log(`✅ 已生成 version.json 文件: ${versionFilePath}`);
  155. console.log('版本信息:', versionInfo);
  156. } catch (error) {
  157. console.error('❌ 生成 version.json 文件失败:', error);
  158. }
  159. callback();
  160. },
  161. );
  162. }
  163. },
  164. );
  165. }
  166. return memo;
  167. },
  168. /**
  169. * @name 开启 hash 模式
  170. * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
  171. * @doc https://umijs.org/docs/api/config#hash
  172. */
  173. hash: true,
  174. /**
  175. * @name 兼容性设置
  176. * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖
  177. * @doc https://umijs.org/docs/api/config#targets
  178. */
  179. // targets: {
  180. // ie: 11,
  181. // },
  182. /**
  183. * @name 路由的配置,不在路由中引入的文件不会编译
  184. * @description 只支持 path,component,routes,redirect,wrappers,title 的配置
  185. * @doc https://umijs.org/docs/guides/routes
  186. */
  187. // umi routes: https://umijs.org/docs/routing
  188. routes: filterRoutesByProd(routes, PROD_ID),
  189. /**
  190. * @name 主题的配置
  191. * @description 虽然叫主题,但是其实只是 less 的变量设置
  192. * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn
  193. * @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme
  194. */
  195. theme: {
  196. // 如果不想要 configProvide 动态设置主题需要把这个设置为 default
  197. // 只有设置为 variable, 才能使用 configProvide 动态设置主色调
  198. 'root-entry-name': 'variable',
  199. },
  200. /**
  201. * @name moment 的国际化配置
  202. * @description 如果对国际化没有要求,打开之后能减少js的包大小
  203. * @doc https://umijs.org/docs/api/config#ignoremomentlocale
  204. */
  205. ignoreMomentLocale: true,
  206. /**
  207. * @name 代理配置
  208. * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了
  209. * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。
  210. * @doc 代理介绍 https://umijs.org/docs/guides/proxy
  211. * @doc 代理配置 https://umijs.org/docs/api/config#proxy
  212. */
  213. proxy: proxy(envConfig) as any,
  214. /**
  215. * @name 快速热更新配置
  216. * @description 一个不错的热更新组件,更新时可以保留 state
  217. */
  218. fastRefresh: true,
  219. //============== 以下都是max的插件配置 ===============
  220. /**
  221. * @name 数据流插件
  222. * @@doc https://umijs.org/docs/max/data-flow
  223. */
  224. model: {},
  225. /**
  226. * 一个全局的初始数据流,可以用它在插件之间共享数据
  227. * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。
  228. * @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81
  229. */
  230. initialState: {},
  231. /**
  232. * @name layout 插件
  233. * @doc https://umijs.org/docs/max/layout-menu
  234. */
  235. title: 'Go PMP',
  236. layout: {
  237. locale: true,
  238. ...defSettings,
  239. },
  240. /**
  241. * @name moment2dayjs 插件
  242. * @description 将项目中的 moment 替换为 dayjs
  243. * @doc https://umijs.org/docs/max/moment2dayjs
  244. */
  245. moment2dayjs: {
  246. preset: 'antd',
  247. plugins: ['duration'],
  248. },
  249. /**
  250. * @name 国际化插件
  251. * @doc https://umijs.org/docs/max/i18n
  252. */
  253. locale: {
  254. // default zh-CN
  255. default: 'zh-CN',
  256. antd: true,
  257. // default true, when it is true, will use `navigator.language` overwrite default
  258. baseNavigator: true,
  259. },
  260. /**
  261. * @name antd 插件
  262. * @description 内置了 babel import 插件
  263. * @doc https://umijs.org/docs/max/antd#antd
  264. */
  265. antd: { configProvider: { theme: { cssVar: true } } },
  266. /**
  267. * @name 网络请求配置
  268. * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
  269. * @doc https://umijs.org/docs/max/request
  270. */
  271. request: {},
  272. /**
  273. * @name 权限插件
  274. * @description 基于 initialState 的权限插件,必须先打开 initialState
  275. * @doc https://umijs.org/docs/max/access
  276. */
  277. access: {},
  278. /**
  279. * @name <head> 中额外的 script
  280. * @description 配置 <head> 中额外的 script
  281. */
  282. headScripts: [
  283. // 解决首次加载时白屏的问题
  284. { src: '/scripts/loading.js', async: true },
  285. ],
  286. //================ pro 插件配置 =================
  287. presets: ['umi-presets-pro'],
  288. /**
  289. * @name openAPI 插件的配置
  290. * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码
  291. * @doc https://pro.ant.design/zh-cn/docs/openapi/
  292. */
  293. openAPI: [
  294. {
  295. requestLibPath: "import { request } from '@umijs/max'",
  296. // 或者使用在线的版本
  297. // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
  298. schemaPath: join(__dirname, 'oneapi.json'),
  299. mock: false,
  300. },
  301. {
  302. requestLibPath: "import { request } from '@umijs/max'",
  303. schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
  304. projectName: 'swagger',
  305. },
  306. ],
  307. mock: {
  308. include: ['mock/**/*', 'src/pages/**/_mock.ts'],
  309. },
  310. mfsu: {
  311. strategy: 'normal',
  312. shared: {
  313. react: { singleton: true, eager: true, requiredVersion: false },
  314. 'react-dom': { singleton: true, eager: true, requiredVersion: false },
  315. 'lodash-es': { singleton: true, eager: true, requiredVersion: false },
  316. ramda: { singleton: true, eager: true, requiredVersion: false },
  317. },
  318. },
  319. esbuildMinifyIIFE: true,
  320. /**
  321. * @name Babel 插件配置
  322. * @description 使用 Babel 插件移除 console 语句
  323. */
  324. extraBabelPlugins: [
  325. REACT_APP_ENV === 'prod' ? 'babel-plugin-transform-remove-console' : '',
  326. ].filter(Boolean),
  327. requestRecord: {},
  328. tailwindcss: {
  329. timeout: 30000,
  330. },
  331. icons: { autoInstall: {} },
  332. }) as any;