// https://umijs.org/config/ import { defineConfig } from '@umijs/max'; import dayjs from 'dayjs'; import fs from 'fs'; import { glob } from 'glob'; import path, { join } from 'path'; import defaultSettings from './defaultSettings'; import proxy from './proxy'; import routes from './routes'; // 导入所有环境配置 const allConfigs = glob.sync('*/**.ts', { cwd: path.join(__dirname, 'env') }).reduce( (configs, file) => { const [productId, envFile] = file.split(path.sep); const envName = path.basename(envFile, '.ts'); if (!configs[productId]) { configs[productId] = {}; } configs[productId][envName] = require(`./env/${file}`).default; return configs; }, {} as Record>, ); const { REACT_APP_ENV = 'prod', PROD_ID = 'go-pmp' } = process.env; const envConfig = Object.assign( allConfigs[PROD_ID]?.['default'] ?? {}, allConfigs[PROD_ID]?.[REACT_APP_ENV as any] ?? {}, ); const buildTime = dayjs(); envConfig.REACT_APP_VERSION = `${envConfig.REACT_APP_VERSION} build:${buildTime .unix() .toString(36) .toUpperCase()}`; const defSettings = { ...defaultSettings, title: process.env.REACT_APP_NAME, }; // 路由过滤函数 function filterRoutesByProd(routes: any[], prodId: string): any[] { return routes .map((route) => { // 检查是否应该显示该路由 const shouldShow = (() => { // 如果定义了 includeProds,只在指定产品中显示 if (route.includeProds?.length > 0) { return route.includeProds.includes(prodId); } // 如果定义了 excludeProds,在非指定产品中显示 if (route.excludeProds?.length > 0) { return !route.excludeProds.includes(prodId); } // 默认显示 return true; })(); if (prodId !== 'go-pmp' && !shouldShow) { return null; } // 递归处理子路由 if (route.routes) { const filteredChildren = filterRoutesByProd(route.routes, prodId); // 如果子路由全部被过滤掉了,且当前路由没有 component,则过滤掉当前路由 if (filteredChildren.length === 0 && !route.component) { return null; } return { ...route, routes: filteredChildren, }; } return route; }) .filter(Boolean); // 过滤掉 null 值 } export default defineConfig({ /** * 注入环境变量 */ define: { 'process.env': { REACT_APP_ENV: REACT_APP_ENV, REACT_APP_ID: envConfig.REACT_APP_ID, REACT_APP_NAME: envConfig.REACT_APP_NAME, REACT_APP_VERSION: envConfig.REACT_APP_VERSION, REACT_APP_BUILD_TIME: buildTime.unix(), STORAGE_NAME_SPACE: envConfig.STORAGE_NAME_SPACE, ENABLE_REQUEST_ENCRYPTION: envConfig.ENABLE_REQUEST_ENCRYPTION, REQUEST_ENCRYPTION_KEY: envConfig.REQUEST_ENCRYPTION_KEY, ENABLE_STORAGE_ENCRYPTION: envConfig.ENABLE_STORAGE_ENCRYPTION, STORAGE_ENCRYPTION_KEY: envConfig.STORAGE_ENCRYPTION_KEY, REACT_APP_MANAGEMENT_KEY: envConfig.REACT_APP_MANAGEMENT_KEY, REQUEST_DATA_COMPRESSION: envConfig.REQUEST_DATA_COMPRESSION, REACT_APP_API_URL: envConfig.REACT_APP_API_URL, }, }, links: [ { rel: 'icon', href: `/favicon-${envConfig.REACT_APP_ID}.ico`, }, ], alias: { '@svgs': path.resolve(process.cwd(), 'svgs'), }, chainWebpack(memo) { const svgsDir = path.resolve(process.cwd(), 'svgs'); // 将 svgs/ 目录从所有能匹配 .svg 的规则中排除(包括 asset/resource 类型规则和 svgo-loader) Object.values(memo.module.rules.entries()).forEach((rule: any) => { const testRegex = rule.get?.('test'); if (testRegex instanceof RegExp && testRegex.test('file.svg')) { rule.exclude.add(svgsDir); return; } const uses = rule.uses?.entries?.(); if (!uses) return; Object.values(uses).forEach((use: any) => { if ((use.get?.('loader') ?? '').includes('svgo-loader')) { rule.exclude.add(svgsDir); } }); }); // SVG icon loader:自动处理 svgs/ 目录,无需手动运行 convert-svg-icon memo.module .rule('svgs-iconify') .test(/\.svg$/) .include.add(svgsDir) .end() .type('javascript/auto') .use('iconify-svg-loader') .loader(path.resolve(process.cwd(), 'tools/svgIconLoader.cjs')) .end(); // 在生产环境构建时生成 version.json 文件 if (REACT_APP_ENV === 'prod' || REACT_APP_ENV === 'test' || REACT_APP_ENV === 'dev') { memo.plugin('generate-version-file').use( class GenerateVersionFilePlugin { apply(compiler: any) { compiler.hooks.beforeCompile.tapAsync( 'GenerateVersionFilePlugin', (_params: any, callback: any) => { try { const publicDir = path.join(process.cwd(), 'public'); if (!fs.existsSync(publicDir)) { fs.mkdirSync(publicDir, { recursive: true }); } // 生成版本信息 const versionInfo = { productId: envConfig.REACT_APP_ID, version: envConfig.REACT_APP_VERSION, buildTime: buildTime.format('YYYY-MM-DD HH:mm:ss'), buildTimestamp: buildTime.unix(), environment: REACT_APP_ENV, }; // 写入 version.json 文件 const versionFilePath = path.join(publicDir, 'version.json'); fs.writeFileSync(versionFilePath, JSON.stringify(versionInfo, null, 2), 'utf8'); console.log(`✅ 已生成 version.json 文件: ${versionFilePath}`); console.log('版本信息:', versionInfo); } catch (error) { console.error('❌ 生成 version.json 文件失败:', error); } callback(); }, ); } }, ); } return memo; }, /** * @name 开启 hash 模式 * @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。 * @doc https://umijs.org/docs/api/config#hash */ hash: true, /** * @name 兼容性设置 * @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖 * @doc https://umijs.org/docs/api/config#targets */ // targets: { // ie: 11, // }, /** * @name 路由的配置,不在路由中引入的文件不会编译 * @description 只支持 path,component,routes,redirect,wrappers,title 的配置 * @doc https://umijs.org/docs/guides/routes */ // umi routes: https://umijs.org/docs/routing routes: filterRoutesByProd(routes, PROD_ID), /** * @name 主题的配置 * @description 虽然叫主题,但是其实只是 less 的变量设置 * @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn * @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme */ theme: { // 如果不想要 configProvide 动态设置主题需要把这个设置为 default // 只有设置为 variable, 才能使用 configProvide 动态设置主色调 'root-entry-name': 'variable', }, /** * @name moment 的国际化配置 * @description 如果对国际化没有要求,打开之后能减少js的包大小 * @doc https://umijs.org/docs/api/config#ignoremomentlocale */ ignoreMomentLocale: true, /** * @name 代理配置 * @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了 * @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。 * @doc 代理介绍 https://umijs.org/docs/guides/proxy * @doc 代理配置 https://umijs.org/docs/api/config#proxy */ proxy: proxy(envConfig) as any, /** * @name 快速热更新配置 * @description 一个不错的热更新组件,更新时可以保留 state */ fastRefresh: true, //============== 以下都是max的插件配置 =============== /** * @name 数据流插件 * @@doc https://umijs.org/docs/max/data-flow */ model: {}, /** * 一个全局的初始数据流,可以用它在插件之间共享数据 * @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。 * @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 */ initialState: {}, /** * @name layout 插件 * @doc https://umijs.org/docs/max/layout-menu */ title: 'Go PMP', layout: { locale: true, ...defSettings, }, /** * @name moment2dayjs 插件 * @description 将项目中的 moment 替换为 dayjs * @doc https://umijs.org/docs/max/moment2dayjs */ moment2dayjs: { preset: 'antd', plugins: ['duration'], }, /** * @name 国际化插件 * @doc https://umijs.org/docs/max/i18n */ locale: { // default zh-CN default: 'zh-CN', antd: true, // default true, when it is true, will use `navigator.language` overwrite default baseNavigator: true, }, /** * @name antd 插件 * @description 内置了 babel import 插件 * @doc https://umijs.org/docs/max/antd#antd */ antd: { configProvider: { theme: { cssVar: true } } }, /** * @name 网络请求配置 * @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 * @doc https://umijs.org/docs/max/request */ request: {}, /** * @name 权限插件 * @description 基于 initialState 的权限插件,必须先打开 initialState * @doc https://umijs.org/docs/max/access */ access: {}, /** * @name 中额外的 script * @description 配置 中额外的 script */ headScripts: [ // 解决首次加载时白屏的问题 { src: '/scripts/loading.js', async: true }, ], //================ pro 插件配置 ================= presets: ['umi-presets-pro'], /** * @name openAPI 插件的配置 * @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码 * @doc https://pro.ant.design/zh-cn/docs/openapi/ */ openAPI: [ { requestLibPath: "import { request } from '@umijs/max'", // 或者使用在线的版本 // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json" schemaPath: join(__dirname, 'oneapi.json'), mock: false, }, { requestLibPath: "import { request } from '@umijs/max'", schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json', projectName: 'swagger', }, ], mock: { include: ['mock/**/*', 'src/pages/**/_mock.ts'], }, mfsu: { strategy: 'normal', shared: { react: { singleton: true, eager: true, requiredVersion: false }, 'react-dom': { singleton: true, eager: true, requiredVersion: false }, 'lodash-es': { singleton: true, eager: true, requiredVersion: false }, ramda: { singleton: true, eager: true, requiredVersion: false }, }, }, esbuildMinifyIIFE: true, /** * @name Babel 插件配置 * @description 使用 Babel 插件移除 console 语句 */ extraBabelPlugins: [ REACT_APP_ENV === 'prod' ? 'babel-plugin-transform-remove-console' : '', ].filter(Boolean), requestRecord: {}, tailwindcss: { timeout: 30000, }, icons: { autoInstall: {} }, }) as any;