generator-mico-cli 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +33 -0
  2. package/bin/mico.js +15 -2
  3. package/generators/micro-react/index.js +44 -6
  4. package/generators/micro-react/meta.json +2 -1
  5. package/generators/micro-react/templates/CICD/start_dev.sh +12 -2
  6. package/generators/micro-react/templates/CICD/start_prod.sh +11 -1
  7. package/generators/micro-react/templates/CICD/start_test.sh +12 -1
  8. package/generators/micro-react/templates/_gitignore +3 -1
  9. package/generators/micro-react/templates/_npmrc +1 -0
  10. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +16 -0
  11. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +16 -0
  12. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
  13. package/generators/micro-react/templates/apps/layout/docs/feature-/345/276/256/345/211/215/347/253/257/346/250/241/345/274/217.md +0 -1
  14. package/generators/micro-react/templates/apps/layout/docs/feature-/350/217/234/345/215/225/346/235/203/351/231/220/346/216/247/345/210/266.md +9 -8
  15. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
  16. package/generators/micro-react/templates/apps/layout/mock/pages.ts +3 -3
  17. package/generators/micro-react/templates/apps/layout/src/app.tsx +5 -2
  18. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +7 -2
  19. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +29 -1
  20. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +1 -5
  21. package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +12 -16
  22. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +2 -1
  23. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +7 -8
  24. package/generators/micro-react/templates/apps/layout/src/services/user.ts +5 -1
  25. package/generators/micro-react/templates/package.json +2 -0
  26. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
  27. package/generators/micro-react/templates/scripts/collect-dist.js +10 -0
  28. package/generators/micro-react/templates/turbo.json +1 -1
  29. package/generators/subapp-react/index.js +46 -4
  30. package/generators/subapp-react/templates/homepage/config/config.dev.ts +0 -1
  31. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +9 -2
  32. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +9 -2
  33. package/generators/subapp-react/templates/homepage/config/config.prod.ts +9 -2
  34. package/generators/subapp-react/templates/homepage/package.json +1 -0
  35. package/generators/subapp-react/templates/homepage/src/app.tsx +6 -0
  36. package/generators/subapp-umd/ignore-list.json +5 -0
  37. package/generators/subapp-umd/index.js +325 -0
  38. package/generators/subapp-umd/meta.json +11 -0
  39. package/generators/subapp-umd/templates/README.md +94 -0
  40. package/generators/subapp-umd/templates/package.json +35 -0
  41. package/generators/subapp-umd/templates/public/index.html +34 -0
  42. package/generators/subapp-umd/templates/src/App.less +15 -0
  43. package/generators/subapp-umd/templates/src/App.tsx +13 -0
  44. package/generators/subapp-umd/templates/src/index.ts +2 -0
  45. package/generators/subapp-umd/templates/tsconfig.json +27 -0
  46. package/generators/subapp-umd/templates/webpack.config.js +68 -0
  47. package/lib/utils.js +2 -1
  48. package/package.json +1 -1
@@ -341,6 +341,10 @@ export const parseMenuItems = (items: MenuItem[]): ParsedMenuItem[] => {
341
341
  });
342
342
  };
343
343
 
344
+ /** 去除尾部斜杠(根路径 "/" 保持不变) */
345
+ const stripTrailingSlash = (path: string): string =>
346
+ path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;
347
+
344
348
  /**
345
349
  * 根据路径查找对应的路由配置
346
350
  */
@@ -348,18 +352,19 @@ export const findRouteByPath = (
348
352
  routes: ParsedRoute[],
349
353
  pathname: string,
350
354
  ): ParsedRoute | undefined => {
355
+ const normalizedPathname = stripTrailingSlash(pathname);
351
356
  let exact: ParsedRoute | undefined;
352
357
  let bestWildcard: { route: ParsedRoute; basePath: string } | undefined;
353
358
 
354
359
  for (const route of routes) {
355
- if (route.path === pathname) {
360
+ if (stripTrailingSlash(route.path) === normalizedPathname) {
356
361
  exact = route;
357
362
  continue;
358
363
  }
359
364
 
360
365
  if (route.path.endsWith('/*')) {
361
366
  const basePath = route.path.slice(0, -2);
362
- if (pathname === basePath || pathname.startsWith(`${basePath}/`)) {
367
+ if (normalizedPathname === basePath || normalizedPathname.startsWith(`${basePath}/`)) {
363
368
  if (!bestWildcard || basePath.length > bestWildcard.basePath.length) {
364
369
  bestWildcard = { route, basePath };
365
370
  }
@@ -164,7 +164,7 @@ export const initDefaultInterceptors = (
164
164
  return data;
165
165
  });
166
166
 
167
- // 处理业务错误码
167
+ // 处理业务错误码({ result: { code, desc } } 格式)
168
168
  addResponseInterceptor(async (data: unknown) => {
169
169
  if (data && typeof data === 'object' && 'result' in data) {
170
170
  const typedData = data as { result?: { code?: number; desc?: string } };
@@ -183,4 +183,32 @@ export const initDefaultInterceptors = (
183
183
  }
184
184
  return data;
185
185
  });
186
+
187
+
188
+ // 处理业务错误码({ code, msg, data } 格式)
189
+ addResponseInterceptor(async (data: unknown) => {
190
+ if (
191
+ data &&
192
+ typeof data === 'object' &&
193
+ 'code' in data &&
194
+ !('result' in data) &&
195
+ !('success' in data)
196
+ ) {
197
+ const typedData = data as { code?: number; msg?: string };
198
+ if (typeof typedData.code === 'number' && typedData.code !== 200) {
199
+ const error = new Error(
200
+ typedData.msg || `请求失败 (${typedData.code})`,
201
+ ) as Error & {
202
+ code: number;
203
+ msg: string;
204
+ response: unknown;
205
+ };
206
+ error.code = typedData.code;
207
+ error.msg = typedData.msg || '';
208
+ error.response = data;
209
+ throw error;
210
+ }
211
+ }
212
+ return data;
213
+ });
186
214
  };
@@ -11,14 +11,10 @@
11
11
  }
12
12
 
13
13
  .micro-app-loading {
14
- position: absolute;
15
- top: 0;
16
- left: 0;
17
- right: 0;
18
- bottom: 0;
19
14
  display: flex;
20
15
  align-items: center;
21
16
  justify-content: center;
17
+ min-height: 600px;
22
18
  background: var(--color-bg-1);
23
19
  z-index: 10;
24
20
  }
@@ -1,4 +1,5 @@
1
1
  import { logout, STORAGE_KEYS } from '@/common/auth';
2
+ import type { IUserInfo } from '@/common/auth/type';
2
3
  import {
3
4
  getCurrentTimezone,
4
5
  getTimezoneRegion,
@@ -39,13 +40,10 @@ interface IMenuItem {
39
40
  children?: IMenuItem[];
40
41
  }
41
42
 
42
- const DEFAULT_AVATAR =
43
- 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
44
-
45
43
  // TODO: 临时假数据,后续接入真实登录态
46
- const MOCK_USER = {
44
+ const MOCK_USER: Partial<IUserInfo> = {
47
45
  user_name: 'Test User',
48
- avatar: DEFAULT_AVATAR,
46
+ username: 'test@micous.com',
49
47
  };
50
48
 
51
49
  export const AvatarName: React.FC<{ userName?: string }> = ({ userName }) => {
@@ -100,11 +98,11 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
100
98
 
101
99
  // 管理头像 URL
102
100
  const [avatarSrc, setAvatarSrc] = useState<string>(
103
- currentUser?.avatar || DEFAULT_AVATAR,
101
+ currentUser?.avatar || '',
104
102
  );
105
103
 
106
104
  useEffect(() => {
107
- setAvatarSrc(currentUser?.avatar || DEFAULT_AVATAR);
105
+ setAvatarSrc(currentUser?.avatar || '');
108
106
  }, [currentUser?.avatar]);
109
107
 
110
108
  // 加载时区列表
@@ -142,10 +140,6 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
142
140
  };
143
141
  }, []);
144
142
 
145
- const handleAvatarError = () => {
146
- setAvatarSrc(DEFAULT_AVATAR);
147
- };
148
-
149
143
  const loginOut = async () => {
150
144
  logout();
151
145
  const { search, pathname } = window.location;
@@ -370,11 +364,13 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
370
364
  }
371
365
  >
372
366
  <div className="flex items-center avatar-dropdown-trigger">
373
- <div className="flex items-center">
374
- <Avatar size={24} shape="circle">
375
- <img src={avatarSrc} alt="avatar" onError={handleAvatarError} />
376
- </Avatar>
377
- </div>
367
+ {avatarSrc && (
368
+ <div className="flex items-center">
369
+ <Avatar size={24} shape="circle">
370
+ <img src={avatarSrc} alt="avatar" />
371
+ </Avatar>
372
+ </div>
373
+ )}
378
374
  <AvatarName userName={currentUser?.user_name} />
379
375
  <IconFont
380
376
  type="webcs-outline_down1"
@@ -135,11 +135,12 @@
135
135
  }
136
136
 
137
137
  // Sider styles
138
- .<%= projectName %>-sider {
138
+ .layout-web-sider {
139
139
  box-shadow: none;
140
140
  background-color: @color-text-5 !important;
141
141
  position: fixed !important;
142
142
  height: calc(100vh - @header-height) !important;
143
+ z-index: 999;
143
144
 
144
145
  .arco-layout-sider-trigger {
145
146
  display: flex;
@@ -2,12 +2,12 @@ import type { ParsedMenuItem } from '@/common/menu';
2
2
  import { filterMenuItems, parseMenuItems } from '@/common/menu';
3
3
  import { getMenus } from '@/common/portal-data';
4
4
  import IconFont from '@/components/IconFont';
5
- import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
5
+ import { isAuthDisabled } from '@/constants';
6
6
  import { useMenuState } from '@/hooks/useMenuState';
7
7
  import { useTheme } from '@/hooks/useTheme';
8
8
  import { Layout, Menu } from '@mico-platform/ui';
9
9
  import * as Icons from '@mico-platform/ui/icon';
10
- import { useLocation, useModel } from '@umijs/max';
10
+ import { useModel } from '@umijs/max';
11
11
  import React, { useEffect, useMemo, useRef } from 'react';
12
12
  import './index.less';
13
13
 
@@ -115,16 +115,15 @@ interface LayoutMenuProps {
115
115
  const LayoutMenu: React.FC<LayoutMenuProps> = () => {
116
116
  const siderRef = useRef<HTMLDivElement>(null);
117
117
  const { isDark } = useTheme();
118
- const location = useLocation();
119
-
120
118
  const { initialState } = useModel('@@initialState');
121
119
  const currentUser = initialState?.currentUser;
122
120
 
123
121
  // Parse menu data
124
- // disableAuth 或免权限校验路由时不过滤,显示全部菜单
122
+ // isMenuAllowed 内部已对每个菜单项单独检查 isNoPermissionRoute,
123
+ // 无需在此按当前页面路径全局跳过过滤,避免菜单可见性随页面变化
125
124
  const menuItems = useMemo(() => {
126
125
  const menus = getMenus();
127
- if (isAuthDisabled() || isNoPermissionRoute(location.pathname)) {
126
+ if (isAuthDisabled()) {
128
127
  return parseMenuItems(menus);
129
128
  }
130
129
  const filteredMenus = filterMenuItems(menus, {
@@ -132,7 +131,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
132
131
  sideMenus: (currentUser?.side_menus || []) as string[],
133
132
  });
134
133
  return parseMenuItems(filteredMenus);
135
- }, [currentUser?.is_superuser, currentUser?.side_menus, location.pathname]);
134
+ }, [currentUser?.is_superuser, currentUser?.side_menus]);
136
135
 
137
136
  // 使用菜单状态 Hook
138
137
  const {
@@ -177,7 +176,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
177
176
  onCollapse={handleCollapsed}
178
177
  collapsible
179
178
  breakpoint="xl"
180
- className="common-web-sider"
179
+ className="layout-web-sider"
181
180
  trigger={clickTriggerBtn}
182
181
  theme={isDark ? 'dark' : 'light'}
183
182
  >
@@ -42,7 +42,11 @@ export async function fetchUserInfo(): Promise<IUserInfo> {
42
42
  skipProxy: true,
43
43
  });
44
44
  if (response.code === 200 && response.data) {
45
- return response.data;
45
+ const data = response.data;
46
+ if (!data.user_name) {
47
+ data.user_name = data.username || '';
48
+ }
49
+ return data;
46
50
  }
47
51
  throw new Error(response.msg || '获取用户信息失败');
48
52
  }
@@ -32,6 +32,8 @@
32
32
  "devDependencies": {
33
33
  "@commitlint/cli": "^19.5.0",
34
34
  "@commitlint/config-conventional": "^19.5.0",
35
+ "@common-web/sentry": "^0.0.4",
36
+ "@sentry/webpack-plugin": "^4.9.1",
35
37
  "@typescript-eslint/eslint-plugin": "^8.54.0",
36
38
  "@typescript-eslint/parser": "^8.54.0",
37
39
  "dotenv-cli": "^7.4.1",
@@ -0,0 +1,45 @@
1
+ interface ApplySentryPluginOptions {
2
+ /** webpack-chain 实例(Umi chainWebpack 的参数) */
3
+ memo: any;
4
+ /** 应用名称,用于 urlPrefix 路径 */
5
+ appName: string;
6
+ /** Sentry project 名称,默认 '<%= projectName %>' */
7
+ project?: string;
8
+ }
9
+
10
+ /**
11
+ * 往 webpack-chain 配置上挂载 Sentry sourcemap 上传插件
12
+ */
13
+ export function applySentryPlugin({
14
+ memo,
15
+ appName,
16
+ project = '<%= projectName %>',
17
+ }: ApplySentryPluginOptions) {
18
+ const version = require('../package.json').version;
19
+
20
+ // 使用 Legacy Upload 方式,自托管 Sentry 不支持 Debug ID / Artifact Bundle
21
+ const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
22
+ memo.plugin('sentry').use(
23
+ class {
24
+ apply(compiler: any) {
25
+ sentryWebpackPlugin({
26
+ url: 'https://sentry.micoworld.net',
27
+ org: 'micocenter',
28
+ project,
29
+ /** @see https://micoworld.feishu.cn/wiki/AmXQwmnEairvQ9k2m9KcgJNcndv#share-OupCdqeeUoESO4x6YKPcrs4nn3c */
30
+ authToken: process.env.SENTRY_AUTH_TOKEN,
31
+ release: {
32
+ name: version,
33
+ uploadLegacySourcemaps: {
34
+ paths: ['./dist'],
35
+ urlPrefix: `~/<%= cdnPrefixPath %><%= projectName %>/${version}/${appName}`,
36
+ },
37
+ },
38
+ sourcemaps: {
39
+ disable: true,
40
+ },
41
+ }).apply(compiler);
42
+ }
43
+ },
44
+ );
45
+ }
@@ -48,6 +48,16 @@ async function collectDist() {
48
48
 
49
49
  // 移动 dist 目录到目标位置
50
50
  await fs.rename(appDistPath, targetDistPath);
51
+
52
+ // 删除 .map 文件(sourcemap 已由 webpack 插件上传到 Sentry,无需部署到 CDN)
53
+ const files = await fs.readdir(targetDistPath);
54
+ for (const file of files) {
55
+ if (file.endsWith('.map')) {
56
+ await fs.unlink(path.join(targetDistPath, file));
57
+ console.log(` 🗑 已删除 sourcemap: ${appName}/${file}`);
58
+ }
59
+ }
60
+
51
61
  console.log(`✓ ${appName}: apps/${appName}/dist -> dist/${appName}/`);
52
62
  } catch (error) {
53
63
  if (error.code === 'ENOENT') {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://turbo.build/schema.json",
3
- "globalEnv": ["CDN_PUBLIC_PATH"],
3
+ "globalEnv": ["CDN_PUBLIC_PATH", "SENTRY_AUTH_TOKEN"],
4
4
  "tasks": {
5
5
  "build": {
6
6
  "dependsOn": ["^build"],
@@ -428,9 +428,38 @@ module.exports = class extends Generator {
428
428
 
429
429
  this.log('');
430
430
  this.log('📦 正在安装依赖...');
431
- this.spawnCommandSync('pnpm', ['install'], {
432
- cwd: this.monorepoRoot
433
- });
431
+ try {
432
+ this.spawnCommandSync('pnpm', ['install'], {
433
+ cwd: this.monorepoRoot
434
+ });
435
+ } catch (e) {
436
+ this._installFailed = true;
437
+ this.log('');
438
+ this.log('⚠️ 依赖安装失败,但子应用文件已全部生成。');
439
+ this.logger.verbose('Install error:', e.message);
440
+ return;
441
+ }
442
+
443
+ // 依赖安装成功后,格式化被修改的 layout 文件
444
+ const layoutDir = path.join(this.monorepoRoot, 'apps/layout');
445
+ if (fs.existsSync(path.join(layoutDir, '.prettierrc'))) {
446
+ const filesToFormat = [
447
+ path.join(layoutDir, 'mock/pages.ts'),
448
+ path.join(layoutDir, 'config/config.dev.ts'),
449
+ ].filter((f) => fs.existsSync(f));
450
+
451
+ if (filesToFormat.length > 0) {
452
+ try {
453
+ this.spawnCommandSync(
454
+ 'pnpm',
455
+ ['-C', 'apps/layout', 'exec', 'prettier', '--write', ...filesToFormat],
456
+ { cwd: this.monorepoRoot },
457
+ );
458
+ } catch (e) {
459
+ this.logger.verbose('Formatting failed (non-critical):', e.message);
460
+ }
461
+ }
462
+ }
434
463
  }
435
464
 
436
465
  end() {
@@ -438,7 +467,20 @@ module.exports = class extends Generator {
438
467
  if (this._skipInstall) return;
439
468
 
440
469
  this.log('');
441
- this.log('✅ 子应用创建成功!');
470
+
471
+ if (this._installFailed) {
472
+ this.log('⚠️ 子应用文件已创建,但依赖安装未完成。');
473
+ this.log('');
474
+ this.log(' 请手动安装依赖:');
475
+ this.log('');
476
+ this.log(' pnpm install');
477
+ this.log('');
478
+ this.log(' 如果安装仍然失败,可尝试:');
479
+ this.log(' pnpm install --network-concurrency 4');
480
+ } else {
481
+ this.log('✅ 子应用创建成功!');
482
+ }
483
+
442
484
  this.log('');
443
485
  this.log(' 后续步骤:');
444
486
  this.log('');
@@ -50,7 +50,6 @@ const config: ReturnType<typeof defineConfig> = {
50
50
  // externals: {
51
51
  // react: 'window.React',
52
52
  // 'react-dom': 'window.ReactDOM',
53
- // 'react-dom/client': 'window.ReactDOMClient',
54
53
  // '@mico-platform/ui': 'window.micoUI',
55
54
  // },
56
55
  /**
@@ -1,8 +1,13 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
+ // 开发环境,不上传 sourcemap。调试时如有需要再解开
5
+ // import { applySentryPlugin } from "../../../scripts/apply-sentry-plugin";
4
6
 
5
7
  const config: ReturnType<typeof defineConfig> = {
8
+ // 开发环境,不上传 sourcemap。调试时如有需要再解开
9
+ // devtool: 'hidden-source-map',
10
+
6
11
  // 测试环境:将所有代码打包到一个文件
7
12
  extraBabelPlugins: ['babel-plugin-dynamic-import-node'],
8
13
 
@@ -12,6 +17,8 @@ const config: ReturnType<typeof defineConfig> = {
12
17
  memo.optimization.splitChunks(false);
13
18
  // 禁用 runtimeChunk
14
19
  memo.optimization.runtimeChunk(false);
20
+ // 开发环境,不上传 sourcemap。调试时如有需要再解开
21
+ // applySentryPlugin({ memo, appName: '<%= appName %>' });
15
22
  return memo;
16
23
  },
17
24
 
@@ -21,14 +28,14 @@ const config: ReturnType<typeof defineConfig> = {
21
28
  * @doc https://umijs.org/docs/api/config#externals
22
29
  *
23
30
  * 作为 qiankun 子应用时,这些库由主应用提供:
24
- * - react / react-dom / react-dom/client: 主应用已加载,避免多实例问题
31
+ * - react / react-dom: 主应用已加载,避免多实例问题
25
32
  * - @mico-platform/ui: 主应用已加载,复用组件和样式
26
33
  */
27
34
  externals: {
28
35
  react: 'React',
29
36
  'react-dom': 'ReactDOM',
30
- 'react-dom/client': 'ReactDOMClient',
31
37
  '@mico-platform/ui': 'micoUI',
38
+ '@common-web/sentry': 'CommonWebSentry',
32
39
  },
33
40
  };
34
41
 
@@ -1,8 +1,13 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
+ // 测试环境,不上传 sourcemap。调试时如有需要再解开
5
+ // import { applySentryPlugin } from "../../../scripts/apply-sentry-plugin";
4
6
 
5
7
  const config: ReturnType<typeof defineConfig> = {
8
+ // 测试环境,不上传 sourcemap。调试时如有需要再解开
9
+ // devtool: 'hidden-source-map',
10
+
6
11
  // 测试环境:将所有代码打包到一个文件
7
12
  extraBabelPlugins: ['babel-plugin-dynamic-import-node'],
8
13
 
@@ -12,6 +17,8 @@ const config: ReturnType<typeof defineConfig> = {
12
17
  memo.optimization.splitChunks(false);
13
18
  // 禁用 runtimeChunk
14
19
  memo.optimization.runtimeChunk(false);
20
+ // 测试环境,不上传 sourcemap。调试时如有需要再解开
21
+ // applySentryPlugin({ memo, appName: '<%= appName %>' });
15
22
  return memo;
16
23
  },
17
24
 
@@ -21,14 +28,14 @@ const config: ReturnType<typeof defineConfig> = {
21
28
  * @doc https://umijs.org/docs/api/config#externals
22
29
  *
23
30
  * 作为 qiankun 子应用时,这些库由主应用提供:
24
- * - react / react-dom / react-dom/client: 主应用已加载,避免多实例问题
31
+ * - react / react-dom: 主应用已加载,避免多实例问题
25
32
  * - @mico-platform/ui: 主应用已加载,复用组件和样式
26
33
  */
27
34
  externals: {
28
35
  react: 'React',
29
36
  'react-dom': 'ReactDOM',
30
- 'react-dom/client': 'ReactDOMClient',
31
37
  '@mico-platform/ui': 'micoUI',
38
+ '@common-web/sentry': 'CommonWebSentry',
32
39
  },
33
40
  };
34
41
 
@@ -1,6 +1,7 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
+ import { applySentryPlugin } from '../../../scripts/apply-sentry-plugin';
4
5
  const { CDN_PUBLIC_PATH } = process.env;
5
6
 
6
7
  const PUBLIC_PATH: string = CDN_PUBLIC_PATH
@@ -8,6 +9,11 @@ const PUBLIC_PATH: string = CDN_PUBLIC_PATH
8
9
  : '/<%= appName %>/';
9
10
 
10
11
  const config: ReturnType<typeof defineConfig> = {
12
+ /**
13
+ * 启用 Source Map(用于 Sentry 错误追踪)
14
+ * hidden-source-map 会生成 .map 文件但不在 JS 中添加 sourcemap 注释
15
+ */
16
+ devtool: 'hidden-source-map',
11
17
  // 生产环境:将所有代码打包到一个文件
12
18
  extraBabelPlugins: ['babel-plugin-dynamic-import-node'],
13
19
 
@@ -17,6 +23,7 @@ const config: ReturnType<typeof defineConfig> = {
17
23
  memo.optimization.splitChunks(false);
18
24
  // 禁用 runtimeChunk
19
25
  memo.optimization.runtimeChunk(false);
26
+ applySentryPlugin({ memo, appName: '<%= appName %>' });
20
27
  return memo;
21
28
  },
22
29
  publicPath: PUBLIC_PATH,
@@ -27,14 +34,14 @@ const config: ReturnType<typeof defineConfig> = {
27
34
  * @doc https://umijs.org/docs/api/config#externals
28
35
  *
29
36
  * 作为 qiankun 子应用时,这些库由主应用提供:
30
- * - react / react-dom / react-dom/client: 主应用已加载,避免多实例问题
37
+ * - react / react-dom: 主应用已加载,避免多实例问题
31
38
  * - @mico-platform/ui: 主应用已加载,复用组件和样式
32
39
  */
33
40
  externals: {
34
41
  react: 'React',
35
42
  'react-dom': 'ReactDOM',
36
- 'react-dom/client': 'ReactDOMClient',
37
43
  '@mico-platform/ui': 'micoUI',
44
+ '@common-web/sentry': 'CommonWebSentry',
38
45
  },
39
46
  };
40
47
 
@@ -15,6 +15,7 @@
15
15
  "start": "npm run dev"
16
16
  },
17
17
  "dependencies": {
18
+ "@mico-platform/ui": "<%= micoUiVersion %>",
18
19
  "@mico-platform/theme": "<%= themeVersion %>",
19
20
  "@umijs/max": "^4.4.8",
20
21
  "react": "^18.2.0",
@@ -7,7 +7,9 @@
7
7
  * 自定义函数请放在 @/common/mainApp.ts 中
8
8
  */
9
9
 
10
+ import React from 'react';
10
11
  import { history } from '@umijs/max';
12
+ import { SentryErrorBoundary } from '@common-web/sentry';
11
13
  import { appLogger } from './common/logger';
12
14
  import { type IMicroAppProps, setMainAppProps } from './common/mainApp';
13
15
 
@@ -95,3 +97,7 @@ export async function getInitialState() {
95
97
  name: '<%= appName %>',
96
98
  };
97
99
  }
100
+
101
+ export function rootContainer(container: React.ReactNode) {
102
+ return <SentryErrorBoundary>{container}</SentryErrorBoundary>;
103
+ }
@@ -0,0 +1,5 @@
1
+ [
2
+ "node_modules",
3
+ "dist",
4
+ ".turbo"
5
+ ]