config.ts 12 KB

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