generator-mico-cli 0.2.30 → 0.2.31
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 +8 -7
- package/generators/micro-react/README.md +34 -0
- package/generators/micro-react/index.js +2 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +21 -1
- package/generators/micro-react/templates/apps/layout/config/config.ts +0 -15
- package/generators/micro-react/templates/apps/layout/docs/arch-/346/227/245/345/277/227/344/270/216/345/270/270/351/207/217.md +16 -8
- package/generators/micro-react/templates/apps/layout/docs/feat-/346/236/204/345/273/272define/344/270/216/345/205/215/350/256/244/350/257/201/345/210/235/345/247/213/346/200/201.md +49 -3
- 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 +3 -1
- package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/346/235/203/351/231/220/346/227/245/345/277/227.md +4 -4
- package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -4
- package/generators/micro-react/templates/apps/layout/src/common/auth/auth-check-path.ts +14 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/common/logger.ts +51 -18
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +8 -5
- package/generators/micro-react/templates/package.json +1 -1
- package/generators/subapp-react/README.md +43 -0
- package/generators/subapp-react/index.js +2 -0
- package/generators/subapp-react/templates/homepage/README.md +5 -1
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +20 -0
- package/generators/subapp-react/templates/homepage/config/config.ts +0 -15
- package/generators/subapp-react/templates/homepage/src/common/logger.ts +50 -18
- package/generators/subapp-umd/README.md +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
| 文档 | 说明 |
|
|
8
8
|
|---|---|
|
|
9
|
-
| [docs/
|
|
10
|
-
| [docs/
|
|
11
|
-
| [docs/
|
|
12
|
-
| [docs/
|
|
13
|
-
| [docs/
|
|
14
|
-
| [
|
|
15
|
-
| [
|
|
9
|
+
| [docs/architecture.md](docs/architecture.md) | 项目架构、模块职责、关键约定、依赖关系 |
|
|
10
|
+
| [docs/generators.md](docs/generators.md) | 三个生成器的 prompt / 模板变量 / 联动修改细节 |
|
|
11
|
+
| [docs/configuration.md](docs/configuration.md) | `.micorc` 配置字段与加载顺序 |
|
|
12
|
+
| [docs/testing.md](docs/testing.md) | Vitest + yeoman-test 测试体系与 Mock 策略 |
|
|
13
|
+
| [docs/scripts.md](docs/scripts.md) | `sync:subapp-react-template` 与 `verify:micro-react` |
|
|
14
|
+
| [generators/micro-react/README.md](generators/micro-react/README.md) | `micro-react` 生成器就近说明 |
|
|
15
|
+
| [generators/subapp-react/README.md](generators/subapp-react/README.md) | `subapp-react` 生成器就近说明 |
|
|
16
|
+
| [generators/subapp-umd/README.md](generators/subapp-umd/README.md) | `subapp-umd` 生成器就近说明 |
|
|
16
17
|
|
|
17
18
|
## 要求
|
|
18
19
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# micro-react 生成器
|
|
2
|
+
|
|
3
|
+
创建完整的 qiankun 微前端 Monorepo。详细 prompt、模板变量、后置动作请见 [docs/generators.md#micro-react](../../docs/generators.md#micro-react)。
|
|
4
|
+
|
|
5
|
+
## 快速索引
|
|
6
|
+
|
|
7
|
+
| 文件 | 职责 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `index.js` | Yeoman 生成器类(`initializing` / `prompting` / `writing` / `install` / `end`) |
|
|
10
|
+
| `meta.json` | `mico list` 展示的名称、描述、features |
|
|
11
|
+
| `ignore-list.json` | `collectFiles` 按路径片段过滤的清单 |
|
|
12
|
+
| `templates/` | 模板根(本仓库文档审计不覆盖此目录) |
|
|
13
|
+
|
|
14
|
+
## 使用
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mkdir my-project && cd my-project
|
|
18
|
+
mico create micro-react
|
|
19
|
+
# 可选:--dry-run / --verbose / --force
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
要求当前目录**不**是已有 monorepo(没有 `pnpm-workspace.yaml`);`--force` 可绕过此检查。
|
|
23
|
+
|
|
24
|
+
## 模板变量速查
|
|
25
|
+
|
|
26
|
+
| 变量 | 类型 | 来源 |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `projectName` / `ProjectName` | string | prompt,`toKebab` / `toPascal` |
|
|
29
|
+
| `appId` | string | prompt |
|
|
30
|
+
| `packageScope` | string | prompt |
|
|
31
|
+
| `cdnPrefix` / `cdnPrefixPath` | string | prompt;`cdnPrefixPath` 为空或带尾斜杠 |
|
|
32
|
+
| `author` | string | prompt |
|
|
33
|
+
| `intlTag` | string | prompt |
|
|
34
|
+
| `micoUiVersion` / `themeVersion` | string | `npm view @mico-platform/{ui,theme} version`,前缀 `^`,失败回退 `^1.0.0` |
|
|
@@ -186,6 +186,8 @@ module.exports = class extends Generator {
|
|
|
186
186
|
const templateData = {
|
|
187
187
|
projectName: this.projectName,
|
|
188
188
|
ProjectName: this.ProjectName,
|
|
189
|
+
// 用于 MFSU mfName 等需要合法 JS 标识符的场景(不允许 `-`)
|
|
190
|
+
projectNameSnake: this.projectName.replace(/-/g, '_'),
|
|
189
191
|
appId: this.appId,
|
|
190
192
|
packageScope: this.packageScope,
|
|
191
193
|
author: this.author,
|
|
@@ -34,7 +34,7 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
34
34
|
// 关闭权限控制(调试用)
|
|
35
35
|
disableAuth: false,
|
|
36
36
|
// SSO 外跳地址(与 resolveExternalLoginPath 读取的 externalLoginPath 一致;生产由注入的 __MICO_CONFIG__ 提供)
|
|
37
|
-
externalLoginPath: 'https://micous-idp.cig.tencentcs.com/sso/tn-456d1d3feb5f4e09ad28ab35ee4d2e66/ai-
|
|
37
|
+
externalLoginPath: 'https://micous-idp.cig.tencentcs.com/sso/tn-456d1d3feb5f4e09ad28ab35ee4d2e66/ai-4919cf3d4883490b956b90376cfb86e7/cas',
|
|
38
38
|
// 多语言中台(可选):intl 与 common-intl 包 initIntl 合并;见 packages/common-intl/README.md
|
|
39
39
|
// 这里只是一个例子,需要根据业务在翻译中台的配置来替换
|
|
40
40
|
// intl: {
|
|
@@ -80,6 +80,26 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
80
80
|
'process.env.LOCALE_REQUEST_URL':
|
|
81
81
|
'https://api-test.micoplatform.com/lang_server/pull',
|
|
82
82
|
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @name MFSU 配置
|
|
86
|
+
* @description
|
|
87
|
+
* - mfName: 唯一名隔离微前端 runtime,避免多应用共存时 MFSU 容器冲突
|
|
88
|
+
* - exclude:
|
|
89
|
+
* - `@mico-platform/theme`:theme 子路径解析
|
|
90
|
+
* - `@mico-platform/ui`:尽量与主应用一起编译
|
|
91
|
+
* - `<%= packageScope %>/*`:workspace 包走 webpack 编译,开发态可在 DevTools 直接定位到 packages/* 源码
|
|
92
|
+
* - shared: React 单例,确保 MFSU 预打包里的组件(如 Layout)与主应用共用同一份 React,否则 useContext 报 null
|
|
93
|
+
* @doc https://umijs.org/docs/api/config#mfsu
|
|
94
|
+
*/
|
|
95
|
+
mfsu: {
|
|
96
|
+
mfName: 'mf_<%= projectNameSnake %>_layout',
|
|
97
|
+
exclude: ['@mico-platform/theme', '@mico-platform/ui', /^<%= packageScope %>\//],
|
|
98
|
+
shared: {
|
|
99
|
+
react: { singleton: true },
|
|
100
|
+
'react-dom': { singleton: true },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
83
103
|
};
|
|
84
104
|
|
|
85
105
|
export default defineConfig(config);
|
|
@@ -124,21 +124,6 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
124
124
|
*/
|
|
125
125
|
extraBabelPresets: ['@mico-platform/ui/babel-preset'],
|
|
126
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
|
-
|
|
142
127
|
/**
|
|
143
128
|
* @name qiankun 微前端配置
|
|
144
129
|
* @description 作为主应用,动态加载子应用
|
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
## Logger 工具
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
默认:**开发环境**全量输出;**生产环境**仅 `log` / `info` / `warn` 静默,`error` 仍带前缀输出。使用 `bind` 保持正确的调用栈位置。
|
|
8
|
+
|
|
9
|
+
### 生产临时全量日志(首屏 query)
|
|
10
|
+
|
|
11
|
+
首屏 URL 带 **`?debug=1`**(或 `debug=true` / `yes`,大小写不敏感)时,在**本模块首次加载**时解析一次,之后 `log` / `info` / `warn` 与开发环境行为一致;**不**随 SPA 内路由或 query 变更而动态开关(需整页刷新并带上参数)。
|
|
12
|
+
|
|
13
|
+
常量 **`DEBUG_LOGS_QUERY_KEY`**(当前为 `'debug'`)与 **`isLayoutDebugLogsEnabled`**(布尔)可从 `@/common/logger` 引用。
|
|
14
|
+
|
|
15
|
+
**安全与合规**:仅用于临时排障;控制台可能含业务或个人信息,**勿长期公开或转发**带 `debug` 的生产链接;接入日志采集时需自行脱敏。
|
|
8
16
|
|
|
9
17
|
### 使用
|
|
10
18
|
|
|
@@ -18,12 +26,12 @@ layoutLogger.error('error'); // 保持原始调用栈位置
|
|
|
18
26
|
### 实现原理
|
|
19
27
|
|
|
20
28
|
```typescript
|
|
21
|
-
// 使用 bind 而非 wrapper
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
29
|
+
// 使用 bind 而非 wrapper,保持控制台显示实际调用位置
|
|
30
|
+
// 生产且未开 debug:log/info/warn 为 noop,error 仍 bind
|
|
31
|
+
const createLogger = (prefix: string) =>
|
|
32
|
+
isLayoutDebugLogsEnabled
|
|
33
|
+
? createVerboseHandlers(`[${prefix}]`)
|
|
34
|
+
: createQuietHandlers(`[${prefix}]`);
|
|
27
35
|
```
|
|
28
36
|
|
|
29
37
|
### 预定义 Logger
|
|
@@ -100,6 +108,6 @@ export const STORAGE_KEYS = {
|
|
|
100
108
|
|
|
101
109
|
## 注意事项
|
|
102
110
|
|
|
103
|
-
-
|
|
111
|
+
- 生产默认仅 `error` 输出;首屏 `?debug=1` 等可打开全量日志(见上文),用完建议去掉参数或整页刷新无参 URL
|
|
104
112
|
- 使用 `bind` 实现,DevTools 显示实际调用位置而非 logger.ts
|
|
105
113
|
- 新增路由常量应添加到 `ROUTES` 对象
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# 构建 define 精简与免认证页初始态
|
|
2
2
|
|
|
3
|
-
> 创建时间:2026-03-24
|
|
3
|
+
> 创建时间:2026-03-24
|
|
4
|
+
> 更新:2026-05-13(补充 `defaultPath` 首屏与 `authCheckPath`、与手动跳转认证页的差异;补充从免认证页跳进认证页后 **`refresh` → `fetchUserInfo`** 的说明)
|
|
4
5
|
|
|
5
6
|
## 功能概述
|
|
6
7
|
|
|
7
8
|
1. **Umi `define`**:不再通过构建期注入 `process.env.EXTERNAL_LOGIN_PATH`(本模板从未注入 `APP_ID`);SSO 外跳等依赖运行时 `window.__MICO_CONFIG__`。
|
|
8
9
|
2. **`getInitialState`**:免认证路由下即使本地仍有 token,也不请求用户信息接口,减少无效调用。
|
|
10
|
+
3. **`/` + `defaultPath` 首屏**:若仅用 `location.pathname === '/'` 判断是否免认证,可能与「即将 `replace` 到的默认页」的认证要求不一致;见下文 **`authCheckPath`**。
|
|
9
11
|
|
|
10
12
|
## 技术方案
|
|
11
13
|
|
|
@@ -21,7 +23,46 @@
|
|
|
21
23
|
|
|
22
24
|
### 初始态
|
|
23
25
|
|
|
24
|
-
`src/app.tsx` 中仅在 `getStoredAuthToken() && !skipAuth` 时调用 `fetchUserInfoFn()`;`skipAuth
|
|
26
|
+
`src/app.tsx` 中仅在 `getStoredAuthToken() && !skipAuth` 时调用 `fetchUserInfoFn()`;`skipAuth` 由 **`isNoAuthRoute(authCheckPath) || isPageAuthFree(authCheckPath)`** 计算(见下节 **`authCheckPath`**,勿与仅看 `location.pathname` 混淆)。
|
|
27
|
+
|
|
28
|
+
### `defaultPath` 与 `authCheckPath`(首屏重定向 vs 手动进认证页)
|
|
29
|
+
|
|
30
|
+
#### 背景
|
|
31
|
+
|
|
32
|
+
访问 **`/`** 且配置了 **`window.__MICO_CONFIG__.defaultPath`**(且不为 `/`)时,`app.tsx` 的 **`onRouteChange`** 会 **`history.replace(defaultPath)`**(例如跳到工作台)。**`getInitialState` 与 `onRouteChange` 的执行顺序下,首屏拿到的 `history.location.pathname` 往往仍是 `/`**。
|
|
33
|
+
|
|
34
|
+
若 **`/`** 被配进 **`noAuthRouteList`**,或 **`/`** 对应页面 **`accessControlEnabled: false`**(页面级免认证),则仅按 **`pathname === '/'`** 会得到 **`skipAuth === true`**:不执行 **`ensureSsoSession`**、不按需认证页拉用户;随后用户马上被重定向到 **`defaultPath` 所指的需登录页**,形成「用免认证壳路径误判、绕开目标页认证」的窗口。为此在 **`getInitialState`**(约自 `app.tsx` 215 行起)对 **`defaultPath`** 做与 **`onRouteChange`** 一致的重定向预判。
|
|
35
|
+
|
|
36
|
+
#### `authCheckPath` 规则
|
|
37
|
+
|
|
38
|
+
- 当 **`pathname === '/'`** 且存在有效 **`defaultPath`**(与 `/` 不同)时,视为即将发生默认重定向,令 **`authCheckPath = defaultPath`**;
|
|
39
|
+
- 否则 **`authCheckPath = location.pathname`**。
|
|
40
|
+
|
|
41
|
+
**`isNoAuthRoute`、`isPageAuthFree` 以及由此得到的 `skipAuth`、`ensureSsoSession`、是否拉 `fetchUserInfo` 等,均基于 `authCheckPath`**,使首屏认证语义与 **重定向目标页** 一致,而不是与瞬时的 **`/`** 一致。
|
|
42
|
+
|
|
43
|
+
实现上 **`authCheckPath`** 与 **`handleAuthFailureRedirect`**(`src/common/request/sso.ts`)共用 **`resolveAuthCheckPath(pathname)`**(`src/common/auth/auth-check-path.ts`),避免无 token 时 **`getInitialState` 已按 `defaultPath` 判定需认证**,但 SSO 外跳仍按浏览器地址 **`/`** 误判为免认证而**不触发重定向**。
|
|
44
|
+
|
|
45
|
+
#### 与「从免认证页手动点到认证页」的区别
|
|
46
|
+
|
|
47
|
+
| 场景 | 典型 `location.pathname` | 为何不会误判 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| **`/` + `defaultPath` 首屏** | 首屏仍为 **`/`**,真实要去 **`defaultPath`** | 若只看 **`/`**,可能与 **`defaultPath`** 的认证要求不一致;必须用 **`authCheckPath`** 把判断「对齐到目标页」。 |
|
|
50
|
+
| **应用内从免认证页导航到认证页**(如 `/login` → `/app`) | 导航后 **`/app/...`** | 不会用旧页的免认证 pathname 算 **`skipAuth`**;且布局里 **`useRoutePermissionRefresh`** 在 pathname 变为**非** `isNoAuthRoute` 时会调用 **`@@initialState` 的 `refresh()`**,从而**再次执行 `getInitialState`**,在 **`getStoredAuthToken() && !skipAuth`** 成立时拉 **`fetchUserInfo`**(见下节)。整页刷新直达认证页时逻辑相同,仅触发时机为首屏而非路由变更。 |
|
|
51
|
+
| **外链直达或地址栏改为认证页** | 直接进入 **`/app/xxx`** | 首屏 `getInitialState` 即按认证页计算 **`skipAuth`**;有 token 则拉 **`fetchUserInfo`**。 |
|
|
52
|
+
|
|
53
|
+
#### 为何客户端从免认证跳进认证页后会拉取用户权限(`fetchUserInfo`)
|
|
54
|
+
|
|
55
|
+
1. **首屏停在免认证路径**:`getInitialState` 里 **`skipAuth === true`**,模板在 **`getStoredAuthToken() && !skipAuth`** 条件下**不会**为当前首屏去调用户信息接口,`currentUser` 可能仍为空(见 `app.tsx`)。
|
|
56
|
+
|
|
57
|
+
2. **`pathname` 变为需认证路径**:`layouts/index.tsx` 使用 **`useRoutePermissionRefresh`**(`src/hooks/useRoutePermissionRefresh.ts`)。在**跳过首次渲染**之后,每当 **`location.pathname` 变化**且新路径**不是** **`isNoAuthRoute`** 所指的静态免认证路由时,会防抖调用 Umi **`useModel('@@initialState').refresh()`**。
|
|
58
|
+
|
|
59
|
+
3. **`refresh()` 会重新执行 `getInitialState`**:此时用于计算 **`skipAuth`** 的路径已是认证页(并适用上文 **`authCheckPath`** 规则),一般 **`skipAuth === false`**;若本地仍有 **`getStoredAuthToken()`**,即进入 **`fetchUserInfoFn()`**,请求 **`GET /user/info/`** 等,更新 **`menu_perms`、`button_perms`** 等,供菜单、PermissionFilter、MicroAppLoader 注入子应用等使用。
|
|
60
|
+
|
|
61
|
+
4. **与「整页刷新且首屏已在认证页」**:走同一套 `getInitialState` 分支;差别是触发源为 **路由变更 + `refresh()`**,而非 **应用冷启动首屏**。
|
|
62
|
+
|
|
63
|
+
5. **实现细节**:`useRoutePermissionRefresh` 仅以 **`isNoAuthRoute(pathname)`** 判断是否跳过刷新;若某路由**仅**页面级 **`isPageAuthFree`** 而不在 `noAuthRouteList` 中,离开该页时 pathname 变化仍可能触发 **`refresh()`**——即「离开静态免认证名单就允许刷新权限」的策略。
|
|
64
|
+
|
|
65
|
+
**结论**:「绕过认证」风险来自 **「URL 暂时是免认证的 `/`,但实际业务要去需 SSO 的 `defaultPath`」** 这一种 **首屏 + 默认重定向** 组合;**`authCheckPath`** 专门纠偏这一种。**手动从免认证页进入认证页**时,浏览器地址已是认证路径,鉴权按认证页执行,**不依赖**与 `defaultPath` 相同的特殊分支;二者差异在于 **是否存在「pathname 与真实首屏业务路径不一致」的短暂错位**。在 **SPA** 下,进入认证页后还会经 **`useRoutePermissionRefresh` → `refresh()`** 在 **有 token** 时补拉 **`fetchUserInfo`**(见上节),避免 **`currentUser` 仍为空**、菜单与子应用权限数据落后。
|
|
25
66
|
|
|
26
67
|
## 文件清单
|
|
27
68
|
|
|
@@ -31,7 +72,11 @@
|
|
|
31
72
|
| `config/config.prod.ts` | 移除 `EXTERNAL_LOGIN_PATH` define |
|
|
32
73
|
| `config/config.prod.development.ts` | 同上 |
|
|
33
74
|
| `config/config.prod.testing.ts` | 同上 |
|
|
34
|
-
| `src/app.tsx` | `
|
|
75
|
+
| `src/app.tsx` | `getInitialState` 使用 `resolveAuthCheckPath` |
|
|
76
|
+
| `src/common/auth/auth-check-path.ts` | `resolveAuthCheckPath`:`/` + `defaultPath` 与真实鉴权路径对齐 |
|
|
77
|
+
| `src/common/request/sso.ts` | `handleAuthFailureRedirect` 使用 `resolveAuthCheckPath`,与 `getInitialState` 一致 |
|
|
78
|
+
| `src/hooks/useRoutePermissionRefresh.ts` | 非静态免认证路径的 pathname 变更时 `refresh()`,触发重跑 `getInitialState` 以拉权限 |
|
|
79
|
+
| `src/layouts/index.tsx` | 使用 `useRoutePermissionRefresh` |
|
|
35
80
|
|
|
36
81
|
## 部署注意
|
|
37
82
|
|
|
@@ -40,5 +85,6 @@
|
|
|
40
85
|
|
|
41
86
|
## 相关文档
|
|
42
87
|
|
|
88
|
+
- [菜单权限控制](./feature-菜单权限控制.md)(SSO 步骤与 pathname / `authCheckPath` 关系)
|
|
43
89
|
- [fix-SSO无限重定向](./fix-SSO无限重定向.md)
|
|
44
90
|
- [arch-请求模块](./arch-请求模块.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 菜单权限控制
|
|
2
2
|
|
|
3
|
-
> 创建时间:2026-01-24 更新时间:2026-03-27(同步 `getMenuPage`:pageId 优先,path
|
|
3
|
+
> 创建时间:2026-01-24 更新时间:2026-03-27(同步 `getMenuPage`:pageId 优先,path 兜底);2026-05-13(`getInitialState` 与 `authCheckPath` 说明链至 feat-构建define)
|
|
4
4
|
|
|
5
5
|
## 功能概述
|
|
6
6
|
|
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
└──────────────────────────────────────────────┘
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
**首屏 `getInitialState` 与步骤 1**:若 URL 为 **`/`** 且配置了 **`__MICO_CONFIG__.defaultPath`**(非 `/`),`onRouteChange` 会 **`replace`** 到默认路径,首屏瞬间 **`location.pathname` 可能仍为 `/`**。此时步骤 1 不能只看 **`pathname`**,需用 **`authCheckPath`**(见 [feat-构建define与免认证初始态](./feat-构建define与免认证初始态.md) 中「`defaultPath` 与 `authCheckPath`」)。图中步骤 1 的「pathname」在 **`getInitialState`** 语境下指 **`authCheckPath`**;从免认证页**手动**进入认证页时,地址已是认证路径,无「`/` 壳 vs `defaultPath` 目标」错位。**客户端 SPA** 下进入认证页后,还会由 **`useRoutePermissionRefresh` → `@@initialState.refresh()`** 重跑 `getInitialState`,在 **有 token** 时拉 **`fetchUserInfo`**(详见该文档「为何客户端从免认证跳进认证页后会拉取用户权限」)。
|
|
88
|
+
|
|
87
89
|
### 权限判断逻辑(详细)
|
|
88
90
|
|
|
89
91
|
```
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
1. **`routePermission`**:`BasicLayout` 中 **`isForbidden` 路由权限判定**(为何放行 / 为何 403)。
|
|
10
10
|
2. **`menuFilter`**:`filterMenuItems` 中**被隐藏的菜单项**及**隐藏原因**(侧栏不可见条目)。
|
|
11
11
|
|
|
12
|
-
日志均经 `layoutLogger`
|
|
12
|
+
日志均经 `layoutLogger` 输出;**默认仅开发环境**会在控制台看到 `log`;生产首屏带 `?debug=1`(等)时行为与开发一致,见 [日志与常量](./arch-日志与常量.md)。
|
|
13
13
|
|
|
14
14
|
## 技术方案
|
|
15
15
|
|
|
16
16
|
### 技术栈
|
|
17
17
|
|
|
18
18
|
- 框架:React 18 + @umijs/max
|
|
19
|
-
- 日志:`apps/layout/src/common/logger.ts` 的 `layoutLogger
|
|
19
|
+
- 日志:`apps/layout/src/common/logger.ts` 的 `layoutLogger`(开发环境或生产首屏 `debug` query 时输出 `log`)
|
|
20
20
|
|
|
21
21
|
### 核心实现
|
|
22
22
|
|
|
@@ -142,13 +142,13 @@ menuFilter
|
|
|
142
142
|
| 决策点 | 选择 | 理由 |
|
|
143
143
|
|--------|------|------|
|
|
144
144
|
| 日志前缀 | 固定 `'routePermission'` + 对象载荷 | 可过滤、可结构化,便于 AI/人工检索 |
|
|
145
|
-
| 生产环境 |
|
|
145
|
+
| 生产环境 | 默认静默 `log` | `layoutLogger` 在 production 下 `log`/`info`/`warn` 为 noop;首屏 `?debug=1` 等同开发 |
|
|
146
146
|
| 无 `currentRoute` 不打日志 | 跳过 | 静态路由访问频繁,避免噪音 |
|
|
147
147
|
| `menuFilter` 与超管/关权限 | 不打 | 无过滤效果时无隐藏项可记 |
|
|
148
148
|
|
|
149
149
|
## 已知限制与待改进
|
|
150
150
|
|
|
151
|
-
-
|
|
151
|
+
- **默认**仅开发环境会在控制台看到本功能的 `log`;生产首屏带 `?debug=1`(等)时亦会输出,见 [日志与常量](./arch-日志与常量.md)。线上问题仍主要依赖 Sentry 或其它监控。
|
|
152
152
|
- `renderContent` 另有 `renderContent:` 日志,与 `routePermission` 独立,排查时可同时参考。
|
|
153
153
|
|
|
154
154
|
## 注意事项
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type ILang,
|
|
23
23
|
} from '<%= packageScope %>/common-intl';
|
|
24
24
|
import * as CommonIntl from '<%= packageScope %>/common-intl';
|
|
25
|
+
import { resolveAuthCheckPath } from './common/auth/auth-check-path';
|
|
25
26
|
import { getStoredAuthToken } from './common/auth/auth-manager';
|
|
26
27
|
import type { IUserInfo } from './common/auth/type';
|
|
27
28
|
import { fetchUserInfo } from './services/user';
|
|
@@ -210,16 +211,21 @@ export async function getInitialState(): Promise<{
|
|
|
210
211
|
};
|
|
211
212
|
|
|
212
213
|
const { location } = history;
|
|
213
|
-
|
|
214
|
-
|
|
214
|
+
// 如果当前路径是 / 且配置了 defaultPath(onRouteChange 会立即重定向),
|
|
215
|
+
// 用目标路径判断是否需要认证,避免用免认证的兜底页绕过目标页的认证要求(与 resolveAuthCheckPath、sso handleAuthFailureRedirect 一致)
|
|
216
|
+
const authCheckPath = resolveAuthCheckPath(location.pathname);
|
|
217
|
+
|
|
218
|
+
const noAuthRoute = isNoAuthRoute(authCheckPath);
|
|
219
|
+
const skipAuth = noAuthRoute || isPageAuthFree(authCheckPath);
|
|
215
220
|
|
|
216
221
|
// 非免认证路由:走 SSO 流程
|
|
217
222
|
if (!skipAuth) {
|
|
218
223
|
await ensureSsoSession();
|
|
219
224
|
}
|
|
220
225
|
|
|
221
|
-
//
|
|
222
|
-
//
|
|
226
|
+
// 仅在「需认证」路径且本地已有 token 时拉取用户信息(登录态刷新等)。
|
|
227
|
+
// 免认证 / 页面级免认证路径不强制 SSO,本地未必有 token;不进入本分支则无 currentUser,
|
|
228
|
+
// PermissionFilter、MicroAppLoader 注入子应用的 button_perms 等按无登录用户(无权限)处理。
|
|
223
229
|
if (getStoredAuthToken() && !skipAuth) {
|
|
224
230
|
const userInfo = await fetchUserInfoFn();
|
|
225
231
|
if (userInfo) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 与 `app.tsx` 中 `getInitialState` 的鉴权路径语义一致:
|
|
3
|
+
* 访问 `/` 且配置了 `window.__MICO_CONFIG__.defaultPath`(非 `/`)时,`onRouteChange` 会 `replace` 到该路径,
|
|
4
|
+
* 首屏 `location.pathname` 可能仍为 `/`,但免认证 / SSO 失败重定向等判断应以**目标路径**为准。
|
|
5
|
+
*/
|
|
6
|
+
export function resolveAuthCheckPath(pathname: string): string {
|
|
7
|
+
if (typeof window === 'undefined') {
|
|
8
|
+
return pathname;
|
|
9
|
+
}
|
|
10
|
+
const defaultPath = window.__MICO_CONFIG__?.defaultPath;
|
|
11
|
+
const willRedirectToDefault =
|
|
12
|
+
pathname === '/' && !!defaultPath && defaultPath !== '/';
|
|
13
|
+
return willRedirectToDefault ? defaultPath : pathname;
|
|
14
|
+
}
|
|
@@ -1,37 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志工具
|
|
3
|
-
*
|
|
3
|
+
* - 开发环境:全量输出
|
|
4
|
+
* - 生产环境:默认仅 `error` 带前缀输出;可在**首屏 URL** 带 query 打开全量 `log` / `info` / `warn`(与 dev 一致)
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
+
* 生产开启方式:首次进入页面时地址栏带 `?debug=1`(或 `debug=true` / `yes`),
|
|
7
|
+
* 在**本 JS 模块加载时刻**解析一次并固定,后续路由或 query 变更**不会**再切换日志级别(需整页刷新带参 URL)。
|
|
8
|
+
*
|
|
9
|
+
* **安全与合规**:`debug` 仅用于临时排障;全量日志可能在控制台暴露业务或个人信息,**勿长期公开或转发**带 `debug` 的生产链接;若接入日志采集需自行脱敏。
|
|
10
|
+
*
|
|
11
|
+
* 使用 bind 保持原始调用位置,避免错误栈指向 logger 内部(在开启详细日志时)
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
const isDev = process.env.NODE_ENV === 'development';
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
const
|
|
16
|
+
/** Query 参数名;勿随意改名以免已分享的排障链接失效 */
|
|
17
|
+
export const DEBUG_LOGS_QUERY_KEY = 'debug';
|
|
12
18
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
/** 模块初始化时读取一次 `location.search`,之后不变 */
|
|
20
|
+
function readVerboseDebugQueryOnce(): boolean {
|
|
21
|
+
if (typeof window === 'undefined') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const v = new URLSearchParams(window.location.search).get(DEBUG_LOGS_QUERY_KEY);
|
|
26
|
+
if (v == null || v === '') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const normalized = v.toLowerCase();
|
|
30
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
27
33
|
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 开发环境,或生产环境首屏 URL 已带 `debug` 开关(模块加载时确定);供业务侧按需分支 */
|
|
37
|
+
export const isLayoutDebugLogsEnabled: boolean = isDev || readVerboseDebugQueryOnce();
|
|
38
|
+
|
|
39
|
+
const noop = (): void => {};
|
|
28
40
|
|
|
41
|
+
function createVerboseHandlers(formattedPrefix: string) {
|
|
29
42
|
return {
|
|
30
43
|
log: console.log.bind(console, formattedPrefix),
|
|
31
44
|
info: console.info.bind(console, formattedPrefix),
|
|
32
45
|
warn: console.warn.bind(console, formattedPrefix),
|
|
33
46
|
error: console.error.bind(console, formattedPrefix),
|
|
34
47
|
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createQuietHandlers(formattedPrefix: string) {
|
|
51
|
+
return {
|
|
52
|
+
log: noop,
|
|
53
|
+
info: noop,
|
|
54
|
+
warn: noop,
|
|
55
|
+
error: console.error.bind(console, formattedPrefix),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 创建带前缀的日志函数
|
|
61
|
+
* 使用 bind 绑定前缀,保持控制台显示正确的调用位置(在开启详细日志时)
|
|
62
|
+
*/
|
|
63
|
+
const createLogger = (prefix: string) => {
|
|
64
|
+
const formattedPrefix = `[${prefix}]`;
|
|
65
|
+
return isLayoutDebugLogsEnabled
|
|
66
|
+
? createVerboseHandlers(formattedPrefix)
|
|
67
|
+
: createQuietHandlers(formattedPrefix);
|
|
35
68
|
};
|
|
36
69
|
|
|
37
70
|
// 预定义的日志实例
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { request as rawRequest } from '@umijs/max';
|
|
6
|
+
import { resolveAuthCheckPath } from '@/common/auth/auth-check-path';
|
|
6
7
|
import {
|
|
7
8
|
maybePersistTokens,
|
|
8
9
|
setStoredAuthToken,
|
|
@@ -59,14 +60,16 @@ const performSsoRedirect = (): void => {
|
|
|
59
60
|
export const handleAuthFailureRedirect = (): void => {
|
|
60
61
|
if (typeof window === 'undefined') return;
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const authCheckPath = resolveAuthCheckPath(window.location.pathname);
|
|
64
|
+
|
|
65
|
+
// 与 getInitialState 一致:用 authCheckPath 判断,避免「/ + defaultPath」首屏仍按 / 误判为免认证
|
|
66
|
+
if (isNoAuthRoute(authCheckPath)) {
|
|
67
|
+
console.log('[SSO] 当前路由在免认证列表中,跳过 SSO 重定向', { authCheckPath });
|
|
65
68
|
return;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
if (isPageAuthFree(
|
|
69
|
-
console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向');
|
|
71
|
+
if (isPageAuthFree(authCheckPath)) {
|
|
72
|
+
console.log('[SSO] 页面 accessControlEnabled=false,跳过 SSO 重定向', { authCheckPath });
|
|
70
73
|
return;
|
|
71
74
|
}
|
|
72
75
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@commitlint/cli": "^19.5.0",
|
|
34
34
|
"@commitlint/config-conventional": "^19.5.0",
|
|
35
|
-
"@common-web/sentry": "0.0.
|
|
35
|
+
"@common-web/sentry": "0.0.8",
|
|
36
36
|
"@sentry/webpack-plugin": "^4.9.1",
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
38
38
|
"@typescript-eslint/parser": "^8.54.0",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# subapp-react 生成器
|
|
2
|
+
|
|
3
|
+
在已有 Monorepo 的 `apps/` 下创建 React 子应用。详细说明见 [docs/generators.md#subapp-react](../../docs/generators.md#subapp-react)。
|
|
4
|
+
|
|
5
|
+
## 快速索引
|
|
6
|
+
|
|
7
|
+
| 文件 | 职责 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `index.js` | 生成器主体;含三处对主应用的联动修改 |
|
|
10
|
+
| `meta.json` | `mico list` 展示信息 |
|
|
11
|
+
| `ignore-list.json` | 模板过滤清单 |
|
|
12
|
+
| `templates/homepage/` | 模板根,本生成器以 `homepage` 作为模板目录名 |
|
|
13
|
+
|
|
14
|
+
## 使用
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd <monorepo-root>
|
|
18
|
+
mico create subapp-react
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
要求 monorepo 根包含 `apps/` 目录。
|
|
22
|
+
|
|
23
|
+
## 联动修改
|
|
24
|
+
|
|
25
|
+
非 dry-run 时会尝试原位修改主应用以下文件(任一缺失静默跳过):
|
|
26
|
+
|
|
27
|
+
| 文件 | 修改 |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `dev.preset.json` | 向 `presets.full.apps` 追加 `appName` |
|
|
30
|
+
| `apps/layout/mock/pages.ts` | 在兜底路由前插入页面项(`routeKey = cs_web_menu_<snake>_page`) |
|
|
31
|
+
| `apps/layout/mock/api.mock.ts` | 将 `routeKey` 加入 `menu_perms` |
|
|
32
|
+
|
|
33
|
+
修改完成后 prettier 仅格式化这两个 mock 文件。
|
|
34
|
+
|
|
35
|
+
## 模板同步
|
|
36
|
+
|
|
37
|
+
从源仓库的 `apps/homepage` 同步模板:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm sync:subapp-react-template -- <source-project-root>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
详见 [docs/scripts.md](../../docs/scripts.md)。
|
|
@@ -168,6 +168,8 @@ module.exports = class extends Generator {
|
|
|
168
168
|
const templateData = {
|
|
169
169
|
appName: this.appName,
|
|
170
170
|
AppName: this.appNamePascal,
|
|
171
|
+
// 用于 MFSU mfName 等需要合法 JS 标识符的场景(不允许 `-`)
|
|
172
|
+
appNameSnake: this.appName.replace(/-/g, '_'),
|
|
171
173
|
packageScope: this.packageScope,
|
|
172
174
|
devPort: this.devPort,
|
|
173
175
|
micoUiVersion: `^${micoUiVer}`,
|
|
@@ -64,7 +64,7 @@ PORT=8001
|
|
|
64
64
|
│ ├── app.tsx # Umi 运行时配置
|
|
65
65
|
│ ├── assets/ # 静态资源
|
|
66
66
|
│ ├── common/ # 公共模块
|
|
67
|
-
│ │ ├── logger.ts #
|
|
67
|
+
│ │ ├── logger.ts # 日志工具(与 layout 一致:生产首屏 `?debug=1` 可开全量 log/info/warn)
|
|
68
68
|
│ │ ├── mainApp.ts # 主应用通信
|
|
69
69
|
│ │ └── request.ts # 请求封装
|
|
70
70
|
│ ├── pages/ # 页面组件
|
|
@@ -75,6 +75,10 @@ PORT=8001
|
|
|
75
75
|
└── tsconfig.json
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
## 日志(logger)
|
|
79
|
+
|
|
80
|
+
与 monorepo 内 **layout** 的 `src/common/logger.ts` 规则对齐:生产默认静默 `log`/`info`/`warn`,`error` 仍输出;首屏 URL 带 **`?debug=1`**(或 `debug=true` / `yes`)可在模块加载时打开全量日志。详见主应用文档 `apps/layout/docs/arch-日志与常量.md`(生成项目后路径以仓库为准)。
|
|
81
|
+
|
|
78
82
|
## 微前端配置
|
|
79
83
|
|
|
80
84
|
本应用作为 qiankun 子应用运行,关键配置:
|
|
@@ -61,6 +61,26 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
61
61
|
// @mico-platform/ui 基础样式(使用运行时解析的最新版本号,避免 latest 标签)
|
|
62
62
|
`https://cdn-portal.micoplatform.com/portal-center/mico-ui/${MICO_UI_VERSION}/ui/dist/css/mico-ui.min.css`,
|
|
63
63
|
],
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @name MFSU 配置
|
|
67
|
+
* @description
|
|
68
|
+
* - mfName: 唯一名隔离微前端 runtime,避免多应用共存时 MFSU 容器冲突
|
|
69
|
+
* - exclude:
|
|
70
|
+
* - `@mico-platform/theme`:theme 子路径解析
|
|
71
|
+
* - `@mico-platform/ui`:尽量与主应用一起编译
|
|
72
|
+
* - `<%= packageScope %>/*`:workspace 包走 webpack 编译,开发态可在 DevTools 直接定位到 packages/* 源码
|
|
73
|
+
* - shared: React 单例,确保 MFSU 预打包里的组件(如 UI 库)与应用共用同一份 React,否则 useContext 报 null
|
|
74
|
+
* @doc https://umijs.org/docs/api/config#mfsu
|
|
75
|
+
*/
|
|
76
|
+
mfsu: {
|
|
77
|
+
mfName: 'mf_<%= appNameSnake %>',
|
|
78
|
+
exclude: ['@mico-platform/theme', '@mico-platform/ui', /^<%= packageScope %>\//],
|
|
79
|
+
shared: {
|
|
80
|
+
react: { singleton: true },
|
|
81
|
+
'react-dom': { singleton: true },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
64
84
|
};
|
|
65
85
|
|
|
66
86
|
export default defineConfig(config);
|
|
@@ -118,21 +118,6 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
118
118
|
* @description 使用 @mico-platform/ui 的 babel preset,实现组件与图标的按需加载
|
|
119
119
|
*/
|
|
120
120
|
extraBabelPresets: ['@mico-platform/ui/babel-preset'],
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @name MFSU 配置
|
|
124
|
-
* @description
|
|
125
|
-
* - exclude: theme 子路径解析;ui 尽量与主应用一起编译
|
|
126
|
-
* - shared: React 单例,确保 MFSU 预打包里的组件(如 UI 库)与应用共用同一份 React,否则 useContext 报 null
|
|
127
|
-
* @doc https://umijs.org/docs/guides/mfsu
|
|
128
|
-
*/
|
|
129
|
-
mfsu: {
|
|
130
|
-
exclude: ['@mico-platform/theme', '@mico-platform/ui'],
|
|
131
|
-
shared: {
|
|
132
|
-
react: { singleton: true },
|
|
133
|
-
'react-dom': { singleton: true },
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
121
|
};
|
|
137
122
|
|
|
138
123
|
export default defineConfig(config);
|
|
@@ -1,38 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志工具
|
|
3
|
-
*
|
|
3
|
+
* - 开发环境:全量输出
|
|
4
|
+
* - 生产环境:默认仅 `error` 带前缀输出;可在**首屏 URL** 带 query 打开全量 `log` / `info` / `warn`(与 layout `apps/layout` 的 logger 行为一致)
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
+
* 生产开启方式:首次进入页面时地址栏带 `?debug=1`(或 `debug=true` / `yes`),
|
|
7
|
+
* 在**本 JS 模块加载时刻**解析一次并固定,后续路由或 query 变更**不会**再切换日志级别(需整页刷新带参 URL)。
|
|
8
|
+
*
|
|
9
|
+
* **安全与合规**:`debug` 仅用于临时排障;全量日志可能在控制台暴露业务或个人信息,**勿长期公开或转发**带 `debug` 的生产链接;若接入日志采集需自行脱敏。
|
|
10
|
+
*
|
|
11
|
+
* 使用 bind 保持原始调用位置,避免错误栈指向 logger 内部(在开启详细日志时)
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
const isDev = process.env.NODE_ENV === 'development';
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
const
|
|
16
|
+
/** Query 参数名;与主应用 layout 一致,勿随意改名以免已分享的排障链接失效 */
|
|
17
|
+
export const DEBUG_LOGS_QUERY_KEY = 'debug';
|
|
12
18
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
/** 模块初始化时读取一次 `location.search`,之后不变 */
|
|
20
|
+
function readVerboseDebugQueryOnce(): boolean {
|
|
21
|
+
if (typeof window === 'undefined') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const v = new URLSearchParams(window.location.search).get(DEBUG_LOGS_QUERY_KEY);
|
|
26
|
+
if (v == null || v === '') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const normalized = v.toLowerCase();
|
|
30
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
26
33
|
}
|
|
34
|
+
}
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
/** 开发环境,或生产环境首屏 URL 已带 `debug` 开关(模块加载时确定);供业务侧按需分支 */
|
|
37
|
+
export const isAppDebugLogsEnabled: boolean = isDev || readVerboseDebugQueryOnce();
|
|
38
|
+
|
|
39
|
+
const noop = (): void => {};
|
|
29
40
|
|
|
41
|
+
function createVerboseHandlers(formattedPrefix: string) {
|
|
30
42
|
return {
|
|
31
43
|
log: console.log.bind(console, formattedPrefix),
|
|
32
44
|
info: console.info.bind(console, formattedPrefix),
|
|
33
45
|
warn: console.warn.bind(console, formattedPrefix),
|
|
34
46
|
error: console.error.bind(console, formattedPrefix),
|
|
35
47
|
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createQuietHandlers(formattedPrefix: string) {
|
|
51
|
+
return {
|
|
52
|
+
log: noop,
|
|
53
|
+
info: noop,
|
|
54
|
+
warn: noop,
|
|
55
|
+
error: console.error.bind(console, formattedPrefix),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 创建带前缀的日志函数
|
|
61
|
+
* 使用 bind 绑定前缀,保持控制台显示正确的调用位置(在开启详细日志时)
|
|
62
|
+
*/
|
|
63
|
+
const createLogger = (prefix: string) => {
|
|
64
|
+
const formattedPrefix = `[${prefix}]`;
|
|
65
|
+
return isAppDebugLogsEnabled
|
|
66
|
+
? createVerboseHandlers(formattedPrefix)
|
|
67
|
+
: createQuietHandlers(formattedPrefix);
|
|
36
68
|
};
|
|
37
69
|
|
|
38
70
|
// 预定义的日志实例
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# subapp-umd 生成器
|
|
2
|
+
|
|
3
|
+
在已有 Monorepo 的 `packages/` 下创建 UMD 组件包(Webpack 构建)。详见 [docs/generators.md#subapp-umd](../../docs/generators.md#subapp-umd)。
|
|
4
|
+
|
|
5
|
+
## 快速索引
|
|
6
|
+
|
|
7
|
+
| 文件 | 职责 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `index.js` | 生成器主体;无跨模块联动修改 |
|
|
10
|
+
| `meta.json` | `mico list` 展示信息 |
|
|
11
|
+
| `ignore-list.json` | 模板过滤清单 |
|
|
12
|
+
| `templates/` | 模板根 |
|
|
13
|
+
|
|
14
|
+
## 使用
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd <monorepo-root>
|
|
18
|
+
mico create subapp-umd
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
要求 monorepo 根包含 `packages/` 目录。
|
|
22
|
+
|
|
23
|
+
## Prompt 默认值
|
|
24
|
+
|
|
25
|
+
| name | 默认 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `appName` | `.micorc.defaultUmdName` ?? `'my-widget'` |
|
|
28
|
+
| `umdGlobalName` | `toPascal(appName)` |
|
|
29
|
+
| `packageScope` | `.micorc.packageScope` ?? `detectPackageScope()` ?? `'@my-project'` |
|
|
30
|
+
| `devPort` | `.micorc.umdDevPort` ?? `'9100'` |
|
|
31
|
+
|
|
32
|
+
## 运行产物
|
|
33
|
+
|
|
34
|
+
| URL | 说明 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `http://localhost:<devPort>/` | HTML 预览页 |
|
|
37
|
+
| `http://localhost:<devPort>/<appName>.umd.js` | UMD JS,供主应用通过 `jsUrls` 接入 |
|