generator-mico-cli 0.2.20 → 0.2.21

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 (29) hide show
  1. package/README.md +29 -0
  2. package/bin/mico.js +124 -5
  3. package/generators/micro-react/index.js +76 -17
  4. package/generators/micro-react/templates/CLAUDE.md +11 -5
  5. package/generators/micro-react/templates/_gitignore +2 -0
  6. package/generators/micro-react/templates/apps/layout/config/config.ts +21 -0
  7. package/generators/micro-react/templates/apps/layout/docs/common-intl.md +8 -6
  8. 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 +60 -35
  9. 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 +7 -2
  10. package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +4 -2
  11. package/generators/micro-react/templates/apps/layout/mock/menus.ts +7 -2
  12. package/generators/micro-react/templates/apps/layout/package.json +3 -2
  13. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +3 -15
  14. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +4 -0
  15. package/generators/micro-react/templates/apps/layout/src/global.less +1 -2
  16. package/generators/micro-react/templates/package.json +2 -1
  17. package/generators/subapp-react/index.js +81 -13
  18. package/generators/subapp-react/templates/homepage/config/config.dev.ts +8 -1
  19. package/generators/subapp-react/templates/homepage/config/config.ts +21 -0
  20. package/generators/subapp-react/templates/homepage/config/routes.ts +1 -1
  21. package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
  22. package/generators/subapp-react/templates/homepage/package.json +3 -2
  23. package/generators/subapp-react/templates/homepage/src/app.tsx +1 -1
  24. package/generators/subapp-react/templates/homepage/src/common/request.ts +2 -2
  25. package/generators/subapp-react/templates/homepage/src/global.less +2 -1
  26. package/generators/subapp-react/templates/homepage/src/pages/index.less +1 -1
  27. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +27 -27
  28. package/lib/utils.js +200 -2
  29. package/package.json +1 -1
package/README.md CHANGED
@@ -48,12 +48,41 @@ mico list
48
48
  mico create micro-react --verbose
49
49
  ```
50
50
 
51
+ 预览模式(只显示将创建的文件,不实际创建):
52
+
53
+ ```bash
54
+ mico create micro-react --dry-run
55
+ ```
56
+
57
+ 检查环境依赖:
58
+
59
+ ```bash
60
+ mico doctor
61
+ ```
62
+
51
63
  将额外参数传递给生成器:
52
64
 
53
65
  ```bash
54
66
  mico create micro-react -- --help
55
67
  ```
56
68
 
69
+ ## 配置文件
70
+
71
+ 可以在项目目录或用户主目录创建 `.micorc` 或 `.micorc.json` 文件来预设默认值:
72
+
73
+ ```json
74
+ {
75
+ "packageScope": "@my-company",
76
+ "cdnPrefix": "portal",
77
+ "author": "Team <team@example.com>",
78
+ "defaultSubappName": "subapp"
79
+ }
80
+ ```
81
+
82
+ 配置查找顺序:
83
+ 1. 当前目录的 `.micorc` 或 `.micorc.json`
84
+ 2. 用户主目录的 `.micorc` 或 `.micorc.json`
85
+
57
86
  ## Monorepo 项目生成器 (micro-react)
58
87
 
59
88
  创建基于 qiankun 微前端架构的完整 Monorepo 项目:
package/bin/mico.js CHANGED
@@ -22,18 +22,32 @@ Commands:
22
22
  create <generator> Run a generator (e.g., mico create subapp-react)
23
23
  list List all available generators
24
24
  update Update mico-cli to the latest version
25
+ doctor Check environment dependencies
25
26
 
26
27
  Options:
27
28
  --help, -h Show this help message
28
29
  --version, -v Show version number
29
30
  --verbose Show detailed output during generation
31
+ --dry-run Preview files without creating them
30
32
  --no-update-check Skip update check
31
33
 
32
34
  Examples:
33
35
  mico create subapp-react
34
36
  mico create micro-react --verbose
37
+ mico create micro-react --dry-run
35
38
  mico list
36
39
  mico update
40
+ mico doctor
41
+
42
+ Configuration:
43
+ Create a .micorc or .micorc.json file in your project or home directory
44
+ to set default values for prompts:
45
+
46
+ {
47
+ "packageScope": "@my-company",
48
+ "cdnPrefix": "portal",
49
+ "author": "Team <team@example.com>"
50
+ }
37
51
 
38
52
  Notes:
39
53
  Requires Yeoman CLI (yo) installed globally.
@@ -118,6 +132,85 @@ function printVersion() {
118
132
  console.log(`mico-cli v${pkg.version}`);
119
133
  }
120
134
 
135
+ /**
136
+ * 运行 doctor 检查
137
+ */
138
+ async function runDoctor() {
139
+ const { runDoctorChecks, loadMicorc } = require('../lib/utils');
140
+
141
+ console.log('');
142
+ console.log(` \x1b[1mMico CLI Environment Check\x1b[0m (v${pkg.version})`);
143
+ console.log('');
144
+
145
+ const results = await runDoctorChecks();
146
+
147
+ // Node.js
148
+ const nodeIcon = results.node.satisfied ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
149
+ const nodeStatus = results.node.satisfied ? 'OK' : `Required: >= ${results.node.required}`;
150
+ console.log(` ${nodeIcon} Node.js ${results.node.current} (${nodeStatus})`);
151
+
152
+ // pnpm
153
+ const pnpmIcon = results.pnpm.available ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
154
+ if (results.pnpm.available) {
155
+ console.log(` ${pnpmIcon} pnpm ${results.pnpm.version}`);
156
+ } else {
157
+ console.log(` ${pnpmIcon} pnpm not found`);
158
+ console.log(' Install: npm install -g pnpm');
159
+ }
160
+
161
+ // Yeoman
162
+ const yoIcon = results.yo.available ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
163
+ if (results.yo.available) {
164
+ console.log(` ${yoIcon} yo ${results.yo.version}`);
165
+ } else {
166
+ console.log(` ${yoIcon} yo not found`);
167
+ console.log(' Install: npm install -g yo');
168
+ }
169
+
170
+ // Git
171
+ const gitIcon = results.git.available ? '\x1b[32m✓\x1b[0m' : '\x1b[33m⚠\x1b[0m';
172
+ if (results.git.available) {
173
+ console.log(` ${gitIcon} git ${results.git.version}`);
174
+ } else {
175
+ console.log(` ${gitIcon} git not found (optional)`);
176
+ }
177
+
178
+ // npm registry
179
+ const npmIcon = results.npm.reachable ? '\x1b[32m✓\x1b[0m' : '\x1b[33m⚠\x1b[0m';
180
+ if (results.npm.reachable) {
181
+ console.log(` ${npmIcon} npm registry reachable`);
182
+ } else {
183
+ console.log(` ${npmIcon} npm registry unreachable (${results.npm.error})`);
184
+ }
185
+
186
+ // .micorc 配置
187
+ console.log('');
188
+ const { config, configPath } = loadMicorc();
189
+ if (configPath) {
190
+ console.log(` \x1b[32m✓\x1b[0m Config loaded from: ${configPath}`);
191
+ const keys = Object.keys(config);
192
+ if (keys.length > 0) {
193
+ console.log(` Keys: ${keys.join(', ')}`);
194
+ }
195
+ } else {
196
+ console.log(' \x1b[2m○\x1b[0m No .micorc config found (optional)');
197
+ }
198
+
199
+ console.log('');
200
+
201
+ // 总结
202
+ const allGood = results.node.satisfied && results.pnpm.available && results.yo.available;
203
+ if (allGood) {
204
+ console.log(' \x1b[32m✅ All required dependencies are installed!\x1b[0m');
205
+ } else {
206
+ console.log(' \x1b[31m❌ Some required dependencies are missing.\x1b[0m');
207
+ console.log(' Please install them before using mico-cli.');
208
+ }
209
+ console.log('');
210
+
211
+ return allGood;
212
+ }
213
+
121
214
  /**
122
215
  * 检查是否有新版本
123
216
  * @returns {Promise<{current: string, latest: string, hasUpdate: boolean} | null>}
@@ -256,6 +349,7 @@ function performUpdate(latestVersion) {
256
349
  * @param {string[]} passthroughArgs - 透传参数
257
350
  * @param {object} options - 选项
258
351
  * @param {boolean} options.verbose - 是否启用详细输出
352
+ * @param {boolean} options.dryRun - 是否启用 dry-run 模式
259
353
  */
260
354
  function runGenerator(generator, rest, passthroughArgs, options = {}) {
261
355
  const localGeneratorEntry = path.join(
@@ -275,17 +369,31 @@ function runGenerator(generator, rest, passthroughArgs, options = {}) {
275
369
 
276
370
  const yoArgs = [yoGenerator, ...rest];
277
371
 
372
+ // 添加 dry-run 参数给 yeoman
373
+ if (options.dryRun) {
374
+ yoArgs.push('--dry-run');
375
+ }
376
+
278
377
  if (passthroughArgs.length > 0) {
279
378
  yoArgs.push('--', ...passthroughArgs);
280
379
  }
281
380
 
282
- // 通过环境变量传递 verbose 标志给生成器
381
+ // 通过环境变量传递标志给生成器
283
382
  const env = { ...process.env };
284
383
  if (options.verbose) {
285
384
  env.MICO_VERBOSE = '1';
385
+ }
386
+ if (options.dryRun) {
387
+ env.MICO_DRY_RUN = '1';
388
+ }
389
+
390
+ if (options.verbose) {
286
391
  console.log('');
287
392
  console.log(' \x1b[2m[verbose] Running generator:', yoGenerator, '\x1b[0m');
288
393
  console.log(' \x1b[2m[verbose] Arguments:', yoArgs.join(' '), '\x1b[0m');
394
+ if (options.dryRun) {
395
+ console.log(' \x1b[2m[verbose] Dry-run mode enabled\x1b[0m');
396
+ }
289
397
  console.log('');
290
398
  }
291
399
 
@@ -323,6 +431,7 @@ async function main() {
323
431
  const hasVersion = mainArgs.includes('--version') || mainArgs.includes('-v');
324
432
  const skipUpdateCheck = mainArgs.includes('--no-update-check');
325
433
  const isVerbose = mainArgs.includes('--verbose');
434
+ const isDryRun = mainArgs.includes('--dry-run');
326
435
 
327
436
  // 过滤掉标志参数
328
437
  const filteredArgs = mainArgs.filter(
@@ -332,7 +441,8 @@ async function main() {
332
441
  arg !== '--version' &&
333
442
  arg !== '-v' &&
334
443
  arg !== '--no-update-check' &&
335
- arg !== '--verbose'
444
+ arg !== '--verbose' &&
445
+ arg !== '--dry-run'
336
446
  );
337
447
 
338
448
  // 处理 --version
@@ -355,6 +465,12 @@ async function main() {
355
465
  process.exit(0);
356
466
  }
357
467
 
468
+ // 处理 doctor 命令
469
+ if (command === 'doctor') {
470
+ const success = await runDoctor();
471
+ process.exit(success ? 0 : 1);
472
+ }
473
+
358
474
  // 处理 update 命令
359
475
  if (command === 'update') {
360
476
  const updateInfo = await checkForUpdate();
@@ -376,8 +492,8 @@ async function main() {
376
492
  process.exit(1);
377
493
  }
378
494
 
379
- // 检查更新(除非跳过)
380
- if (!skipUpdateCheck) {
495
+ // 检查更新(除非跳过或 dry-run)
496
+ if (!skipUpdateCheck && !isDryRun) {
381
497
  const updateInfo = await checkForUpdate();
382
498
  if (updateInfo && updateInfo.hasUpdate) {
383
499
  const shouldUpdate = await askForUpdate(
@@ -396,7 +512,10 @@ async function main() {
396
512
  }
397
513
 
398
514
  // 运行生成器
399
- runGenerator(generator, rest.slice(1), passthroughArgs, { verbose: isVerbose });
515
+ runGenerator(generator, rest.slice(1), passthroughArgs, {
516
+ verbose: isVerbose,
517
+ dryRun: isDryRun
518
+ });
400
519
  return;
401
520
  }
402
521
 
@@ -9,10 +9,10 @@ const {
9
9
  collectFiles,
10
10
  transformDestPath,
11
11
  isTemplateFile,
12
- getLatestNpmVersion,
12
+ getPackageVersionsParallel,
13
13
  setupErrorHandlers,
14
14
  createLogger,
15
- // isVerbose,
15
+ loadMicorc,
16
16
  } = require('../../lib/utils');
17
17
 
18
18
  const IGNORE_LIST = require('./ignore-list.json');
@@ -25,6 +25,17 @@ module.exports = class extends Generator {
25
25
  this.projectRoot = process.cwd();
26
26
  this.logger = createLogger(this);
27
27
 
28
+ // 检查 dry-run 模式
29
+ this.isDryRun = this.options.dryRun || process.env.MICO_DRY_RUN === '1';
30
+
31
+ // 加载 .micorc 配置
32
+ const { config: rcConfig, configPath } = loadMicorc(this.projectRoot);
33
+ this.rcConfig = rcConfig;
34
+ if (configPath) {
35
+ this.logger.verbose('Loaded config from:', configPath);
36
+ this.logger.verbose('Config:', JSON.stringify(rcConfig, null, 2));
37
+ }
38
+
28
39
  // 检查当前目录是否已是一个 monorepo
29
40
  const workspaceFile = path.join(this.projectRoot, 'pnpm-workspace.yaml');
30
41
  if (fs.existsSync(workspaceFile)) {
@@ -46,12 +57,15 @@ module.exports = class extends Generator {
46
57
 
47
58
  async prompting() {
48
59
  try {
60
+ // 使用 .micorc 中的值作为默认值
61
+ const rc = this.rcConfig || {};
62
+
49
63
  this.answers = await this.prompt([
50
64
  {
51
65
  type: 'input',
52
66
  name: 'projectName',
53
67
  message: 'Project name',
54
- default: path.basename(this.projectRoot),
68
+ default: rc.projectName || path.basename(this.projectRoot),
55
69
  filter: (input) => toKebab(input),
56
70
  validate: (input) => {
57
71
  const value = toKebab(input);
@@ -63,7 +77,7 @@ module.exports = class extends Generator {
63
77
  type: 'input',
64
78
  name: 'packageScope',
65
79
  message: 'Package scope (e.g., @my-project)',
66
- default: (answers) => `@${toKebab(answers.projectName)}`,
80
+ default: (answers) => rc.packageScope || `@${toKebab(answers.projectName)}`,
67
81
  validate: (input) => {
68
82
  if (!input) return 'Package scope is required';
69
83
  if (!input.startsWith('@'))
@@ -80,7 +94,7 @@ module.exports = class extends Generator {
80
94
  - 输入 "portal": https://cdn.example.com/portal/my-project/1.0.0/
81
95
  - 输入 "admin/v2": https://cdn.example.com/admin/v2/my-project/1.0.0/
82
96
  Prefix`,
83
- default: '',
97
+ default: rc.cdnPrefix || '',
84
98
  filter: (input) => {
85
99
  // 移除首尾斜杠,规范化路径
86
100
  return input.trim().replace(/^\/+|\/+$/g, '');
@@ -90,7 +104,7 @@ module.exports = class extends Generator {
90
104
  type: 'input',
91
105
  name: 'author',
92
106
  message: 'Author',
93
- default: 'Your Name <email@example.com>',
107
+ default: rc.author || 'Your Name <email@example.com>',
94
108
  },
95
109
  ]);
96
110
 
@@ -110,7 +124,7 @@ module.exports = class extends Generator {
110
124
  }
111
125
  }
112
126
 
113
- writing() {
127
+ async writing() {
114
128
  try {
115
129
  if (!fs.existsSync(this.templateDir)) {
116
130
  console.error('');
@@ -125,21 +139,21 @@ module.exports = class extends Generator {
125
139
 
126
140
  // 在 mico_cli 根目录执行 npm view,以使用该目录 .npmrc 中的 Nexus 认证
127
141
  const cliRoot = path.resolve(__dirname, '../..');
128
- this.logger.verbose('Fetching latest package versions...');
142
+ this.logger.verbose('Fetching latest package versions (parallel)...');
129
143
 
130
- const micoUiVer = getLatestNpmVersion(
131
- '@mico-platform/ui',
132
- '1.0.0',
133
- 8000,
134
- cliRoot,
135
- );
136
- const themeVer = getLatestNpmVersion(
137
- '@mico-platform/theme',
138
- '1.0.0',
144
+ // 并行获取版本
145
+ const versions = await getPackageVersionsParallel(
146
+ [
147
+ { name: '@mico-platform/ui', fallback: '1.0.0' },
148
+ { name: '@mico-platform/theme', fallback: '1.0.0' },
149
+ ],
139
150
  8000,
140
151
  cliRoot,
141
152
  );
142
153
 
154
+ const micoUiVer = versions['@mico-platform/ui'];
155
+ const themeVer = versions['@mico-platform/theme'];
156
+
143
157
  this.logger.verbose('@mico-platform/ui version:', micoUiVer);
144
158
  this.logger.verbose('@mico-platform/theme version:', themeVer);
145
159
 
@@ -173,6 +187,45 @@ module.exports = class extends Generator {
173
187
  process.exit(1);
174
188
  }
175
189
 
190
+ // Dry-run 模式:只列出文件,不实际创建
191
+ if (this.isDryRun) {
192
+ this.log('');
193
+ this.log('\x1b[33m📋 Dry run mode - no files will be created\x1b[0m');
194
+ this.log('');
195
+ this.log(` Project: ${this.projectName}`);
196
+ this.log(` Scope: ${this.packageScope}`);
197
+ this.log(` Destination: ${this.destDir}`);
198
+ this.log('');
199
+ this.log(' Would create the following files:');
200
+ this.log('');
201
+
202
+ let templateCount = 0;
203
+ let copyCount = 0;
204
+
205
+ for (const relPath of files) {
206
+ const destRelPath = transformDestPath(relPath);
207
+ const isTemplate = isTemplateFile(relPath);
208
+ const tag = isTemplate ? '\x1b[32m[tpl]\x1b[0m' : '\x1b[36m[cpy]\x1b[0m';
209
+ this.log(` ${tag} ${destRelPath}`);
210
+
211
+ if (isTemplate) {
212
+ templateCount++;
213
+ } else {
214
+ copyCount++;
215
+ }
216
+ }
217
+
218
+ this.log('');
219
+ this.log(` Total: ${files.length} files (${templateCount} templates, ${copyCount} static)`);
220
+ this.log('');
221
+ this.log(' Run without --dry-run to actually create these files.');
222
+ this.log('');
223
+
224
+ // 设置标记以跳过后续阶段
225
+ this._skipInstall = true;
226
+ return;
227
+ }
228
+
176
229
  this.log('');
177
230
  this.log(`📦 Creating project: ${this.projectName}`);
178
231
  this.log(` Scope: ${this.packageScope}`);
@@ -210,6 +263,9 @@ module.exports = class extends Generator {
210
263
  }
211
264
 
212
265
  install() {
266
+ // 跳过 dry-run 模式
267
+ if (this._skipInstall) return;
268
+
213
269
  // 检查并初始化 git
214
270
  const gitDir = path.join(this.destDir, '.git');
215
271
  if (!fs.existsSync(gitDir)) {
@@ -228,6 +284,9 @@ module.exports = class extends Generator {
228
284
  }
229
285
 
230
286
  end() {
287
+ // 跳过 dry-run 模式
288
+ if (this._skipInstall) return;
289
+
231
290
  this.log('');
232
291
  this.log('✅ 项目创建成功!');
233
292
  this.log('');
@@ -53,15 +53,21 @@ apps/ # Workspace 应用目录(微前端子应用)
53
53
  │ │ ├── app.tsx # Umi 运行时配置
54
54
  │ │ ├── layouts/ # 布局组件
55
55
  │ │ ├── pages/ # 路由页面
56
- │ │ ├── components/ # 公共组件
56
+ │ │ ├── components/ # 公共组件(MicroAppLoader 等)
57
57
  │ │ ├── common/ # 工具模块(auth、request、upload、menu)
58
+ │ │ ├── constants/ # 常量定义(路由、主题、时区等)
58
59
  │ │ ├── hooks/ # 自定义 Hooks
59
60
  │ │ ├── services/ # API 服务层
60
61
  │ │ ├── models/ # Umi model(全局状态)
61
- │ │ ├── locales/ # 国际化文件(zh-CN、en-US)
62
- │ │ └── styles/ # 主题变量与样式覆盖
62
+ │ │ └── locales/ # 国际化文件(zh-CN、en-US)
63
63
  │ └── config/ # Umi 配置
64
- packages/ # 共享包
64
+ ├── basis/ # 基础数据子应用(货币管理等)
65
+ │ ├── src/
66
+ │ │ ├── pages/ # 路由页面
67
+ │ │ ├── locales/ # 国际化文件
68
+ │ │ └── common/ # 工具模块
69
+ │ └── config/ # Umi 配置
70
+ packages/ # 共享包(common-intl 等)
65
71
  scripts/ # 构建与开发脚本
66
72
  CICD/ # 部署资源
67
73
  ```
@@ -97,7 +103,7 @@ refactor(auth): 重构认证服务
97
103
  API 请求配置在 `src/common/request/`(模块化拆分为 6 个文件),错误处理在 `src/requestErrorConfig.ts`。详见 [请求模块架构](./apps/layout/docs/arch-请求模块.md)。
98
104
 
99
105
  ### 日志与常量
100
- Logger 工具在 `src/common/logger.ts`,常量定义在 `src/common/constants.ts`。详见 [日志与常量](./apps/layout/docs/arch-日志与常量.md)。
106
+ Logger 工具在 `src/common/logger.ts`,常量定义在 `src/constants/index.ts`。详见 [日志与常量](./apps/layout/docs/arch-日志与常量.md)。
101
107
 
102
108
  ### 认证模块
103
109
  认证逻辑在 `src/common/auth/`,核心文件 `auth-manager.ts` 管理 Token 存储和用户信息。SSO 登录在 `getInitialState()` 中完成,确保 UI 渲染前认证已就绪。
@@ -55,3 +55,5 @@ build
55
55
  /openspec
56
56
 
57
57
  **/tasks/*
58
+
59
+ .pnpm-store/**
@@ -118,6 +118,27 @@ const config: ReturnType<typeof defineConfig> = {
118
118
  */
119
119
  tailwindcss: {},
120
120
 
121
+ /**
122
+ * @name 额外 Babel Presets
123
+ * @description 使用 @mico-platform/ui 的 babel preset,实现组件与图标的按需加载
124
+ */
125
+ extraBabelPresets: ['@mico-platform/ui/babel-preset'],
126
+
127
+ /**
128
+ * @name MFSU 配置
129
+ * @description
130
+ * - exclude: theme 子路径解析;ui 尽量与主应用一起编译
131
+ * - shared: React 单例,确保 MFSU 预打包里的组件(如 Layout)与主应用共用同一份 React,否则 useContext 报 null
132
+ * @doc https://umijs.org/docs/guides/mfsu
133
+ */
134
+ mfsu: {
135
+ exclude: ['@mico-platform/theme', '@mico-platform/ui'],
136
+ shared: {
137
+ react: { singleton: true },
138
+ 'react-dom': { singleton: true },
139
+ },
140
+ },
141
+
121
142
  /**
122
143
  * @name qiankun 微前端配置
123
144
  * @description 作为主应用,动态加载子应用
@@ -10,6 +10,8 @@
10
10
  - 需要从多语言中台动态获取翻译文案
11
11
  - 希望统一管理国际化逻辑,减少重复代码
12
12
 
13
+ > **注意**:本文档描述的是 `@payment-portal/common-intl` 包的完整使用方式,涵盖了所有使用该包的应用(包括本仓库外的应用如 conversation-v2、session、workorder 等)。当前仓库仅包含 `layout` 和 `basis` 两个应用。
14
+
13
15
  ## 核心特性
14
16
 
15
17
  | 特性 | 说明 |
@@ -311,12 +313,12 @@ console.log(SUPPORTED_LOCALES); // ['zh_CN', 'en', 'ar', 'tr']
311
313
 
312
314
  目前支持以下语言:
313
315
 
314
- | 语言常量 | 值 | 说明 |
315
- | ------------ | ------- | -------- |
316
- | `LANG.ZH_CN` | `zh_CN` | 简体中文 |
317
- | `LANG.EN` | `en` | 英语 |
318
- | `LANG.AR` | `ar` | 阿拉伯语 |
319
- | `LANG.TR` | `tr` | 土耳其语 |
316
+ | 语言常量 | 值 | 说明 |
317
+ | ------------- | ------- | -------- |
318
+ | `LANG.ZH_CN` | `zh_CN` | 简体中文 |
319
+ | `LANG.EN_US` | `en` | 英语 |
320
+ | `LANG.AR_SA` | `ar` | 阿拉伯语 |
321
+ | `LANG.TR_TR` | `tr` | 土耳其语 |
320
322
 
321
323
  ### Q: 如何在代码中获取当前语言?
322
324
 
@@ -28,16 +28,16 @@
28
28
 
29
29
  ### 核心文件
30
30
 
31
- | 文件路径 | 说明 |
32
- | --------------------------------------------------- | -------------------------------- |
33
- | `src/components/MicroAppLoader/index.tsx` | qiankun 微应用加载器组件 |
34
- | `src/components/MicroAppLoader/container-manager.ts`| 容器生命周期管理、会话 ID 机制 |
35
- | `src/components/MicroAppLoader/index.less` | 加载器样式 |
36
- | `src/common/menu/parser.ts` | 菜单解析,包含加载类型判断 |
37
- | `src/common/menu/types.ts` | 类型定义 |
38
- | `src/layouts/index.tsx` | 主布局,集成微应用渲染 |
39
- | `src/app.tsx` | qiankun 全局错误处理 |
40
- | `config/config.ts` | qiankun master 配置 |
31
+ | 文件路径 | 说明 |
32
+ | --- | --- |
33
+ | `src/components/MicroAppLoader/index.tsx` | qiankun 微应用加载器组件 |
34
+ | `src/components/MicroAppLoader/micro-app-manager.ts` | 微应用管理器(单例),稳定容器 + 实例缓存 + 操作序列号 |
35
+ | `src/components/MicroAppLoader/index.less` | 加载器样式 |
36
+ | `src/common/menu/parser.ts` | 菜单解析,包含加载类型判断 |
37
+ | `src/common/menu/types.ts` | 类型定义 |
38
+ | `src/layouts/index.tsx` | 主布局,集成微应用渲染 |
39
+ | `src/app.tsx` | qiankun 全局错误处理 |
40
+ | `config/config.ts` | qiankun master 配置 |
41
41
 
42
42
  ## API / 组件接口
43
43
 
@@ -462,52 +462,77 @@ addGlobalUncaughtErrorHandler((event: Event | string) => {
462
462
  });
463
463
  ```
464
464
 
465
- ### container-manager API
465
+ ### MicroAppManager API (micro-app-manager.ts)
466
466
 
467
- 容器管理器提供会话 ID 机制防止并发加载冲突:
467
+ `MicroAppManager` 是单例类,管理微应用的加载、缓存、切换和卸载:
468
468
 
469
469
  ```typescript
470
- /** 开始加载会话,返回唯一会话 ID */
471
- function startLoadSession(appName: string): number;
470
+ interface MicroAppConfig {
471
+ name: string;
472
+ entry: string;
473
+ target: HTMLElement; // 目标挂载位置
474
+ props: Record<string, unknown>;
475
+ }
476
+
477
+ interface MicroAppState {
478
+ loading: boolean;
479
+ error: string | null;
480
+ mounted: boolean;
481
+ }
482
+
483
+ const manager = microAppManager; // 单例,导出即用
472
484
 
473
- /** 检查会话是否仍然有效(被新加载覆盖时返回 false) */
474
- function isLoadSessionValid(appName: string, sessionId: number): boolean;
485
+ /** 切换到指定微应用(已挂载则仅更新 props) */
486
+ manager.switchTo(config: MicroAppConfig): void;
475
487
 
476
- /** 标记加载完成 */
477
- function markLoadComplete(appName: string, sessionId: number): boolean;
488
+ /** 更新当前已挂载微应用的 props */
489
+ manager.updateProps(props: Record<string, unknown>): void;
478
490
 
479
- /** 等待当前卸载操作完成(如果有) */
480
- async function waitForUnmount(appName: string): Promise<void>;
491
+ /** 取消待处理的请求 */
492
+ manager.cancel(): void;
481
493
 
482
- /** 获取当前加载状态 */
483
- function getLoadingStatus(appName: string): 'idle' | 'loading' | 'mounted' | 'unmounting';
494
+ /** 清除所有缓存实例 */
495
+ manager.clearCache(): Promise<void>;
496
+
497
+ /** 获取调试信息 */
498
+ manager.getDebugInfo(): object;
499
+
500
+ /** 设置状态变化回调 */
501
+ manager.setStateCallback(callback: StateChangeCallback | null): void;
484
502
  ```
485
503
 
504
+ ### 核心设计
505
+
506
+ 1. **稳定容器**:容器在 `document.body` 中创建,激活时移到目标元素内,停用时移回 body 隐藏,不受 React 生命周期影响
507
+ 2. **实例缓存**:每个 entry 只 `loadMicroApp` 一次,后续切换复用已有实例(mount/unmount)
508
+ 3. **操作序列号**:通过递增的 `operationSeq` 检测过期操作,替代旧的会话 ID 机制
509
+ 4. **自动路由守卫**:由独立的 `route-guard.ts` 自动检测用户意图,业务代码无需感知
510
+
486
511
  ### 快速切换时序
487
512
 
488
513
  ```
489
514
  场景:A → B → A 快速切换
490
515
 
491
- A1 开始加载 (sessionId=1)
492
-
493
- 切换到 B,A1 cleanup 在 queueMicrotask 中执行
516
+ switchTo(A):operationSeq=1,开始 loadMicroApp
494
517
 
495
- 切换回 A,A2 开始加载 (sessionId=2)
518
+ switchTo(B):operationSeq=2,A pendingRequest 被替换
496
519
 
497
- A2 调用 waitForUnmount(),等待 A1 完全卸载
520
+ A processRequest 在异步步骤中检测 shouldAbort(mySeq=1) true
498
521
 
499
- A1 卸载完成(带 10 秒超时保护)
522
+ A 等待 mountPromise 完成后执行 safeUnmount,容器移回 body
500
523
 
501
- A2 继续加载,每个异步步骤检查 isLoadSessionValid(sessionId)
524
+ B 开始 processRequest,检查缓存 → 无缓存 → loadMicroApp
502
525
 
503
- 加载完成后立即同步最新 Props(locale/timezone 等)
526
+ B 加载完成后同步最新 Props(locale/timezone/routePath 等)
504
527
  ```
505
528
 
506
529
  ### 设计决策
507
530
 
508
531
  | 决策点 | 选择 | 理由 |
509
- |--------|------|------|
510
- | 卸载时机 | `queueMicrotask` | 在当前事件循环末尾执行,比 `requestAnimationFrame` 更快更可靠 |
511
- | 并发控制 | 会话 ID 机制 | 简单高效,无需复杂的锁机制 |
512
- | 超时时间 | 10 | 平衡等待时间与异常检测速度 |
513
- | Props 同步 | 加载后立即同步 | 确保加载期间的变化不丢失 |
532
+ | --- | --- | --- |
533
+ | 容器策略 | 稳定容器(body 中创建,移动挂载) | 不受 React 生命周期影响,避免容器被意外销毁 |
534
+ | 并发控制 | 操作序列号 + pendingRequest 队列 | 简单高效,新请求自动覆盖旧请求 |
535
+ | 实例缓存 | loadPromise 完成后立即缓存 | 即使被 abort 也可复用,避免重复加载 |
536
+ | unmount 安全 | 等待 mountPromise 完成后再 unmount | 避免 single-spa error #32 |
537
+ | 超时保护 | load 30s / mount 30s / unmount 10s | 平衡等待时间与异常检测速度 |
538
+ | Props 同步 | mount 完成后立即 safeUpdate | 确保加载期间的变化不丢失 |