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.
- package/README.md +29 -0
- package/bin/mico.js +124 -5
- package/generators/micro-react/index.js +76 -17
- package/generators/micro-react/templates/CLAUDE.md +11 -5
- package/generators/micro-react/templates/_gitignore +2 -0
- package/generators/micro-react/templates/apps/layout/config/config.ts +21 -0
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +8 -6
- 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
- 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
- package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +4 -2
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +7 -2
- package/generators/micro-react/templates/apps/layout/package.json +3 -2
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +3 -15
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +4 -0
- package/generators/micro-react/templates/apps/layout/src/global.less +1 -2
- package/generators/micro-react/templates/package.json +2 -1
- package/generators/subapp-react/index.js +81 -13
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +8 -1
- package/generators/subapp-react/templates/homepage/config/config.ts +21 -0
- package/generators/subapp-react/templates/homepage/config/routes.ts +1 -1
- package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
- package/generators/subapp-react/templates/homepage/package.json +3 -2
- package/generators/subapp-react/templates/homepage/src/app.tsx +1 -1
- package/generators/subapp-react/templates/homepage/src/common/request.ts +2 -2
- package/generators/subapp-react/templates/homepage/src/global.less +2 -1
- package/generators/subapp-react/templates/homepage/src/pages/index.less +1 -1
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +27 -27
- package/lib/utils.js +200 -2
- 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
|
-
//
|
|
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, {
|
|
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
|
-
|
|
12
|
+
getPackageVersionsParallel,
|
|
13
13
|
setupErrorHandlers,
|
|
14
14
|
createLogger,
|
|
15
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
│ │
|
|
62
|
-
│ │ └── styles/ # 主题变量与样式覆盖
|
|
62
|
+
│ │ └── locales/ # 国际化文件(zh-CN、en-US)
|
|
63
63
|
│ └── config/ # Umi 配置
|
|
64
|
-
|
|
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/
|
|
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 渲染前认证已就绪。
|
|
@@ -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`
|
|
317
|
-
| `LANG.
|
|
318
|
-
| `LANG.
|
|
319
|
-
| `LANG.
|
|
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`
|
|
34
|
-
| `src/components/MicroAppLoader/
|
|
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`
|
|
40
|
-
| `config/config.ts`
|
|
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
|
-
###
|
|
465
|
+
### MicroAppManager API (micro-app-manager.ts)
|
|
466
466
|
|
|
467
|
-
|
|
467
|
+
`MicroAppManager` 是单例类,管理微应用的加载、缓存、切换和卸载:
|
|
468
468
|
|
|
469
469
|
```typescript
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
/**
|
|
474
|
-
|
|
485
|
+
/** 切换到指定微应用(已挂载则仅更新 props) */
|
|
486
|
+
manager.switchTo(config: MicroAppConfig): void;
|
|
475
487
|
|
|
476
|
-
/**
|
|
477
|
-
|
|
488
|
+
/** 更新当前已挂载微应用的 props */
|
|
489
|
+
manager.updateProps(props: Record<string, unknown>): void;
|
|
478
490
|
|
|
479
|
-
/**
|
|
480
|
-
|
|
491
|
+
/** 取消待处理的请求 */
|
|
492
|
+
manager.cancel(): void;
|
|
481
493
|
|
|
482
|
-
/**
|
|
483
|
-
|
|
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
|
-
|
|
492
|
-
↓
|
|
493
|
-
切换到 B,A1 cleanup 在 queueMicrotask 中执行
|
|
516
|
+
switchTo(A):operationSeq=1,开始 loadMicroApp
|
|
494
517
|
↓
|
|
495
|
-
|
|
518
|
+
switchTo(B):operationSeq=2,A 的 pendingRequest 被替换
|
|
496
519
|
↓
|
|
497
|
-
|
|
520
|
+
A 的 processRequest 在异步步骤中检测 shouldAbort(mySeq=1) → true
|
|
498
521
|
↓
|
|
499
|
-
|
|
522
|
+
A 等待 mountPromise 完成后执行 safeUnmount,容器移回 body
|
|
500
523
|
↓
|
|
501
|
-
|
|
524
|
+
B 开始 processRequest,检查缓存 → 无缓存 → loadMicroApp
|
|
502
525
|
↓
|
|
503
|
-
|
|
526
|
+
B 加载完成后同步最新 Props(locale/timezone/routePath 等)
|
|
504
527
|
```
|
|
505
528
|
|
|
506
529
|
### 设计决策
|
|
507
530
|
|
|
508
531
|
| 决策点 | 选择 | 理由 |
|
|
509
|
-
|
|
510
|
-
|
|
|
511
|
-
| 并发控制 |
|
|
512
|
-
|
|
|
513
|
-
|
|
|
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 | 确保加载期间的变化不丢失 |
|