@morningfast/create-ui 0.0.7 → 0.0.8

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 CHANGED
@@ -157,7 +157,7 @@ pnpm dev
157
157
  - `main-app` 的 nginx 静态资源目录会按项目名替换。
158
158
  - `main-app` 可选替换 nginx 的 `proxy_pass`。
159
159
  - 默认模板是 `sub-app`,单独主应用需要显式传 `--template main-app`。
160
- - `--no-demo` 会删除 `src/pages/demo` 和对应示例路由,`main-app` / `sub-app` 都适用。
160
+ - `--no-demo` 会删除 `src/pages/demo`、对应示例路由和示例子应用配置,`main-app` / `sub-app` 都适用。
161
161
  - `--update` 只能在 workspace 根目录执行,支持 `all`、`main`、`sub-apps` 三种更新目标。
162
162
 
163
163
  ## 不处理的内容
package/dist/index.js CHANGED
@@ -808,8 +808,11 @@ async function customizeAppTemplate(appRoot, variables) {
808
808
  }
809
809
  if (!variables.keepDemo) {
810
810
  await rm(path.join(appRoot, "src", "pages", "demo"), { recursive: true, force: true });
811
- await removeDemoRoute(path.join(appRoot, "src", "router", "business-routes.ts"));
811
+ await rm(path.join(appRoot, "src", "micro-apps", "demo-apps.ts"), { force: true });
812
+ await removeDemoRoute(path.join(appRoot, "src", "router", "mock-routes.ts"));
813
+ await removeSubAppDemoMockRoute(path.join(appRoot, "src", "router", "mock-routes.ts"));
812
814
  await removeSubAppDemoRoute(path.join(appRoot, "src", "router", "index.ts"));
815
+ await removeMainAppDemoSetup(path.join(appRoot, "src", "setup.ts"));
813
816
  }
814
817
  }
815
818
  function getTemplatePackageNameForAppRoot(appRoot, variables) {
@@ -824,14 +827,17 @@ async function updateWorkspaceScripts(targetDir, variables) {
824
827
  packageJson.scripts = {
825
828
  ...packageJson.scripts ?? {},
826
829
  "dev:main": `pnpm --filter ${variables.mainAppDir} dev`,
827
- "dev:sub": `pnpm --filter ${variables.subAppDir} dev`,
828
- [`dev:${variables.subAppDir}`]: `pnpm --filter ${variables.subAppDir} dev`,
830
+ "dev:sub": `node scripts/run-sub-app.mjs dev ${variables.subAppDir}`,
831
+ "dev:sub-app": `node scripts/run-sub-app.mjs dev ${variables.subAppDir}`,
832
+ [`dev:${variables.subAppDir}`]: `node scripts/run-sub-app.mjs dev ${variables.subAppDir}`,
829
833
  "check:main": `pnpm --filter ${variables.mainAppDir} check`,
830
- "check:sub": `pnpm --filter ${variables.subAppDir} check`,
831
- [`check:${variables.subAppDir}`]: `pnpm --filter ${variables.subAppDir} check`,
834
+ "check:sub": `node scripts/run-sub-app.mjs check ${variables.subAppDir}`,
835
+ "check:sub-app": `node scripts/run-sub-app.mjs check ${variables.subAppDir}`,
836
+ [`check:${variables.subAppDir}`]: `node scripts/run-sub-app.mjs check ${variables.subAppDir}`,
832
837
  "build:main": `pnpm --filter ${variables.mainAppDir} build`,
833
- "build:sub": `pnpm --filter ${variables.subAppDir} build`,
834
- [`build:${variables.subAppDir}`]: `pnpm --filter ${variables.subAppDir} build`,
838
+ "build:sub": `node scripts/run-sub-app.mjs build ${variables.subAppDir}`,
839
+ "build:sub-app": `node scripts/run-sub-app.mjs build ${variables.subAppDir}`,
840
+ [`build:${variables.subAppDir}`]: `node scripts/run-sub-app.mjs build ${variables.subAppDir}`,
835
841
  "build:all": "pnpm build:main && pnpm build:sub"
836
842
  };
837
843
  await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
@@ -876,16 +882,56 @@ async function removeDemoRoute(filePath) {
876
882
  return;
877
883
  }
878
884
  const source = await readFile(filePath, "utf8");
879
- const demoStart = source.indexOf(" {\n path: 'demo',");
880
- const demoEnd = source.indexOf("\n];", demoStart);
881
- if (demoStart < 0 || demoEnd < 0 || demoEnd <= demoStart) {
885
+ let updated = removeRouteObject(source, "path: 'demo'");
886
+ updated = removeRouteObject(updated, "path: 'micro'");
887
+ updated = updated.replace(/\n\s+'demo-user-management': \(\) => import\('@\/pages\/demo\/user-management\/index\.vue'\),/g, "").replace(/\n\s+'demo-mapping-config': \(\) => import\('@\/pages\/demo\/mapping-config\/index\.vue'\),/g, "").replace(/\n\s+'demo-table-page': \(\) => import\('@\/pages\/demo\/table-page-demo\/index\.vue'\),/g, "").replace(/\n\s+'demo-upload': \(\) => import\('@\/pages\/demo\/upload-demo\/index\.vue'\),/g, "");
888
+ if (updated !== source) {
889
+ await writeFile(filePath, updated, "utf8");
890
+ }
891
+ }
892
+ async function removeMainAppDemoSetup(filePath) {
893
+ if (!existsSync(filePath)) {
882
894
  return;
883
895
  }
884
- const updated = `${source.slice(0, demoStart).replace(/,\\s*$/, "")}${source.slice(demoEnd)}`;
896
+ const source = await readFile(filePath, "utf8");
897
+ const updated = source.replace(/\nimport \{ setDefaultAppConfigs \} from '@morningfast\/platform\/main\/micro-apps\/config';\n/, "\n").replace(/\nimport \{ demoAppConfigs \} from '\.\/micro-apps\/demo-apps';\n/, "\n").replace(/\n\s+setDefaultAppConfigs\(demoAppConfigs\);\n/, "\n").replace(/\n{3,}/g, "\n\n").replace(/\{\n\n\s+\/\//g, "{\n //");
885
898
  if (updated !== source) {
886
899
  await writeFile(filePath, updated, "utf8");
887
900
  }
888
901
  }
902
+ function removeRouteObject(source, marker) {
903
+ const markerIndex = source.indexOf(marker);
904
+ if (markerIndex < 0) {
905
+ return source;
906
+ }
907
+ const objectStart = source.lastIndexOf(" {", markerIndex);
908
+ if (objectStart < 0) {
909
+ return source;
910
+ }
911
+ let index = objectStart;
912
+ let depth = 0;
913
+ let objectEnd = -1;
914
+ for (; index < source.length; index += 1) {
915
+ const char = source[index];
916
+ if (char === "{") {
917
+ depth += 1;
918
+ } else if (char === "}") {
919
+ depth -= 1;
920
+ if (depth === 0) {
921
+ objectEnd = index + 1;
922
+ break;
923
+ }
924
+ }
925
+ }
926
+ if (objectEnd < 0) {
927
+ return source;
928
+ }
929
+ const trailingCommaEnd = source.slice(objectEnd).match(/^,\n?/)?.[0].length ?? 0;
930
+ const leadingCommaStart = trailingCommaEnd ? objectStart : source.lastIndexOf(",", objectStart);
931
+ const removeStart = leadingCommaStart >= 0 && source.slice(leadingCommaStart, objectStart).trim() === "" ? leadingCommaStart : objectStart;
932
+ const removeEnd = objectEnd + trailingCommaEnd;
933
+ return source.slice(0, removeStart) + source.slice(removeEnd);
934
+ }
889
935
  async function removeSubAppDemoRoute(filePath) {
890
936
  if (!existsSync(filePath)) {
891
937
  return;
@@ -902,6 +948,17 @@ async function removeSubAppDemoRoute(filePath) {
902
948
  await writeFile(filePath, updated, "utf8");
903
949
  }
904
950
  }
951
+ async function removeSubAppDemoMockRoute(filePath) {
952
+ if (!existsSync(filePath)) {
953
+ return;
954
+ }
955
+ const source = await readFile(filePath, "utf8");
956
+ let updated = removeRouteObject(source, "path: '/demo/orders'");
957
+ updated = updated.replace(/\n\s+'demo-orders': \(\) => import\('@\/pages\/demo\/orders\/index\.vue'\),/g, "");
958
+ if (updated !== source) {
959
+ await writeFile(filePath, updated, "utf8");
960
+ }
961
+ }
905
962
  async function replaceText(filePath, searchValue, replaceValue) {
906
963
  if (!existsSync(filePath)) {
907
964
  return;
@@ -45,7 +45,7 @@ pnpm format
45
45
 
46
46
  ## 本地联调主子应用
47
47
 
48
- 主应用已预留 `qiankun` 微前端入口,默认有一个本地测试子应用配置:
48
+ 主应用已预留 `qiankun` 微前端入口。保留 demo 时,模板会带一个本地测试子应用配置:
49
49
 
50
50
  ```txt
51
51
  http://localhost:8082
@@ -61,7 +61,9 @@ pnpm --filter __MORNINGFAST_MAIN_APP_DIR__ dev
61
61
  pnpm --filter __MORNINGFAST_SUB_APP_DIR__ dev
62
62
  ```
63
63
 
64
- 然后访问主应用里的“微前端 / 子应用示例”。
64
+ 保留 demo 时,然后访问主应用里的“微前端 / 订单管理”。
65
+
66
+ 如果生成项目时使用了 `--no-demo`,不会带默认子应用配置和 demo 菜单,需要在业务路由里添加自己的微前端菜单,再在“系统 / 应用配置”页面维护子应用入口。
65
67
 
66
68
  如果子应用地址不同,在主应用“系统 / 应用配置”页面维护子应用:
67
69
 
@@ -80,6 +82,35 @@ pnpm --filter __MORNINGFAST_SUB_APP_DIR__ dev
80
82
  4. 保存后刷新主应用。
81
83
  5. 通过主应用菜单进入该子应用路径。
82
84
 
85
+ 子应用菜单路由复用同一个平台承载页,不需要为每个子应用新增 `componentKey` 或组件映射。完全新增一个子应用菜单时,在 `src/router/mock-routes.ts` 里写 `microApp: true` 即可:
86
+
87
+ ```ts
88
+ {
89
+ path: 'fms',
90
+ name: 'fms',
91
+ title: 'FMS 系统',
92
+ icon: 'Connection',
93
+ redirect: '/fms/orders',
94
+ children: [
95
+ {
96
+ path: 'orders',
97
+ name: 'fms-orders',
98
+ title: '订单管理',
99
+ microApp: true,
100
+ },
101
+ {
102
+ path: ':pathMatch(.*)*',
103
+ name: 'fms-fallback',
104
+ title: 'FMS 页面',
105
+ hidden: true,
106
+ microApp: true,
107
+ },
108
+ ],
109
+ }
110
+ ```
111
+
112
+ 上面菜单对应的应用配置 `路由前缀` 应填写 `/fms`,入口地址填写该子应用的 dev server 或部署地址。隐藏的 `:pathMatch(.*)*` 用来兜住子应用深层路径刷新或跳转。
113
+
83
114
  配置字段对应 qiankun 注册字段:
84
115
 
85
116
  - `应用名称` 会成为 qiankun `name` 和主应用展示名。
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@element-plus/icons-vue": "^2.3.2",
21
- "@morningfast/platform": "^0.0.7",
21
+ "@morningfast/platform": "^0.0.8",
22
22
  "axios": "^1.16.0",
23
23
  "element-plus": "^2.14.0",
24
24
  "localforage": "^1.10.0",
@@ -0,0 +1,12 @@
1
+ import type { AppConfigItem } from '@morningfast/platform/main/micro-apps/config';
2
+
3
+ export const demoAppConfigs: AppConfigItem[] = [
4
+ {
5
+ id: 'sub-orders',
6
+ name: '订单子应用',
7
+ type: 'sub',
8
+ description: '独立业务子应用,先在本地配置里测试子应用结构和菜单入口。',
9
+ basePath: '/micro/sub-app',
10
+ entry: 'http://localhost:8082',
11
+ },
12
+ ];
@@ -1,4 +1,5 @@
1
1
  import type { RouteRecordRaw } from 'vue-router';
2
+ import { MicroAppView } from '@morningfast/platform/main';
2
3
 
3
4
  export interface MockRouteItem {
4
5
  path: string;
@@ -9,6 +10,7 @@ export interface MockRouteItem {
9
10
  hidden?: boolean;
10
11
  redirect?: string;
11
12
  componentKey?: string;
13
+ microApp?: boolean;
12
14
  children?: MockRouteItem[];
13
15
  }
14
16
 
@@ -63,6 +65,34 @@ export const mockRouteItems: MockRouteItem[] = [
63
65
  },
64
66
  ],
65
67
  },
68
+ {
69
+ path: 'micro',
70
+ name: 'micro',
71
+ title: '微前端',
72
+ icon: 'Connection',
73
+ redirect: '/micro/sub-app/demo/orders',
74
+ children: [
75
+ {
76
+ path: 'sub-app/demo/orders',
77
+ name: 'micro-sub-app-demo-orders',
78
+ title: '订单管理',
79
+ microApp: true,
80
+ },
81
+ {
82
+ path: ':pathMatch(.*)*',
83
+ name: 'micro-sub-app-fallback',
84
+ title: '子应用页面',
85
+ hidden: true,
86
+ microApp: true,
87
+ },
88
+ ],
89
+ },
90
+ {
91
+ path: 'micro2/sub-app-2/demo/orders',
92
+ name: 'micro2-sub-app2-demo-orders',
93
+ title: '订单管理',
94
+ microApp: true,
95
+ },
66
96
  ];
67
97
 
68
98
  const componentMap: Record<string, RouteRecordRaw['component']> = {
@@ -97,6 +127,10 @@ function createRouteFromMock(item: MockRouteItem): RouteRecordRaw {
97
127
  route.component = componentMap[item.componentKey];
98
128
  }
99
129
 
130
+ if (item.microApp) {
131
+ route.component = MicroAppView;
132
+ }
133
+
100
134
  if (item.children?.length) {
101
135
  route.children = createRoutesFromMock(item.children);
102
136
  }
@@ -1,6 +1,11 @@
1
1
  import type { MountMorningfastAppContext } from '@morningfast/platform/main';
2
+ import { setDefaultAppConfigs } from '@morningfast/platform/main/micro-apps/config';
3
+
4
+ import { demoAppConfigs } from './micro-apps/demo-apps';
2
5
 
3
6
  export async function setupBusinessApp({ app, router, pinia, http }: MountMorningfastAppContext) {
7
+ setDefaultAppConfigs(demoAppConfigs);
8
+
4
9
  // 业务插件、全局组件、指令、监控 SDK 等启动扩展写在这里。
5
10
  void app;
6
11
  void router;
@@ -39,7 +39,7 @@ VITE_QIANKUN_DEV_MODE=true pnpm dev
39
39
  - 主应用默认挂载路径:`/micro/__MORNINGFAST_SUB_APP_DIR__`。
40
40
  - 主应用应用配置里的 `入口地址` 要指向子应用访问地址,例如 `http://localhost:8082`。
41
41
  - 主应用应用配置里的 `路由前缀` 要和子应用挂载路径一致,例如 `/micro/__MORNINGFAST_SUB_APP_DIR__`。
42
- - 子应用默认保留一个基础 `home` 页面,业务示例放在 `src/pages/demo/orders`,对应路由是 `/demo/orders`。
42
+ - 子应用默认保留一个基础 `home` 页面。保留 demo 时,业务示例放在 `src/pages/demo/orders`,对应路由是 `/demo/orders`。
43
43
  - 子应用独立运行时,`html`、`body`、`#sub-app` 必须形成 `height: 100%; min-height: 0;` 的闭合高度链,否则
44
44
  `MpTablePage` / `MpMainContainerDefault` / `MpTable` 的固定高度表格无法计算内容高度。
45
45
  公共组件统一从 `morningfast-plus/components` 引入:
@@ -226,7 +226,7 @@ window.dispatchEvent(
226
226
 
227
227
  - 基础页面放 `src/pages/<module>/index.vue`。
228
228
  - 演示页面放 `src/pages/demo/<module>/index.vue`。
229
- - 当前订单示例是 `src/pages/demo/orders/index.vue`。
229
+ - 保留 demo 时,当前订单示例是 `src/pages/demo/orders/index.vue`。
230
230
 
231
231
  ## 后续建设方向
232
232
 
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@element-plus/icons-vue": "^2.3.2",
17
- "@morningfast/platform": "^0.0.7",
17
+ "@morningfast/platform": "^0.0.8",
18
18
  "@types/sortablejs": "^1.15.9",
19
19
  "@vitejs/plugin-vue": "^6.0.6",
20
20
  "element-plus": "^2.14.0",
@@ -15,7 +15,7 @@
15
15
  <span class="result-page__eyebrow">ACCESS CONTROL</span>
16
16
  <h1>当前子应用页面暂未开放</h1>
17
17
  <p class="result-page__desc">
18
- 登录状态正常,但当前角色还没有这个子应用页面或操作权限。请返回工作台,或联系管理员核对权限配置。
18
+ 登录状态正常,但当前角色还没有这个子应用页面或操作权限。请返回上一页,或联系管理员核对权限配置。
19
19
  </p>
20
20
  <div class="result-page__meta">
21
21
  <span>状态码 403</span>
@@ -23,8 +23,7 @@
23
23
  </div>
24
24
 
25
25
  <div class="result-page__actions">
26
- <el-button @click="router.back()">返回上一页</el-button>
27
- <el-button type="primary" @click="router.push('/home')">返回工作台</el-button>
26
+ <el-button type="primary" @click="router.back()">返回上一页</el-button>
28
27
  </div>
29
28
  </div>
30
29
  </div>
@@ -23,8 +23,7 @@
23
23
  </div>
24
24
 
25
25
  <div class="result-page__actions">
26
- <el-button @click="router.back()">返回上一页</el-button>
27
- <el-button type="primary" @click="router.push('/home')">返回工作台</el-button>
26
+ <el-button type="primary" @click="router.back()">返回上一页</el-button>
28
27
  </div>
29
28
  </div>
30
29
  </div>
@@ -5,6 +5,7 @@
5
5
  ".gitignore",
6
6
  "docs",
7
7
  "README.md",
8
+ "scripts",
8
9
  "pnpm-workspace.yaml"
9
10
  ],
10
11
  "preservedRoots": [
@@ -34,6 +34,12 @@ pnpm dev:main
34
34
  pnpm dev:sub
35
35
  ```
36
36
 
37
+ 默认会启动生成时的第一个子应用。多个子应用时,可以把子应用目录名或 package name 作为参数传进去:
38
+
39
+ ```sh
40
+ pnpm dev:sub order-app
41
+ ```
42
+
37
43
  主子应用本地联调时,需要开两个终端同时运行:
38
44
 
39
45
  ```sh
@@ -45,9 +51,11 @@ pnpm dev:sub
45
51
  ```
46
52
 
47
53
  默认主应用使用 Vite 默认端口,通常是 `http://localhost:8081`。
48
- 默认子应用端口是 `http://localhost:8082`,主应用内置的子应用入口也指向这个地址。
54
+ 默认子应用端口是 `http://localhost:8082`。保留 demo 时,主应用示例子应用入口也指向这个地址。
49
55
 
50
- 启动后先访问主应用地址,再从主应用菜单进入“微前端 / 子应用示例”。如果子应用端口或部署地址不同,在主应用“系统 / 应用配置”里维护子应用入口地址。
56
+ 保留 demo 时,启动后先访问主应用地址,再从主应用菜单进入“微前端 / 订单管理”。如果子应用端口或部署地址不同,在主应用“系统 / 应用配置”里维护子应用入口地址。
57
+
58
+ 如果生成项目时使用了 `--no-demo`,不会带默认子应用配置和 demo 菜单,需要在业务路由里添加自己的微前端菜单。
51
59
 
52
60
  ## 检查和构建
53
61
 
@@ -63,6 +71,12 @@ pnpm check:main
63
71
  pnpm check:sub
64
72
  ```
65
73
 
74
+ 检查指定子应用:
75
+
76
+ ```sh
77
+ pnpm check:sub order-app
78
+ ```
79
+
66
80
  构建主应用:
67
81
 
68
82
  ```sh
@@ -75,6 +89,12 @@ pnpm build:main
75
89
  pnpm build:sub
76
90
  ```
77
91
 
92
+ 构建指定子应用:
93
+
94
+ ```sh
95
+ pnpm build:sub order-app
96
+ ```
97
+
78
98
  构建全部应用:
79
99
 
80
100
  ```sh
@@ -84,7 +104,7 @@ pnpm build:all
84
104
  `build:*` 会先执行 `check:*`,校验通过后再使用 `vite build --mode pro` 做正式环境构建。
85
105
 
86
106
  `apps/main-app` 是微前端主应用,`apps/sub-app` 是默认子应用。后续新增业务子应用时,建议继续放在 `apps/` 下。
87
- 多个子应用不要共用 `dev:sub`,使用具体应用名脚本,例如 `dev:order-app`、`build:order-app`。
107
+ 主应用不需要复制子应用启动命令,只需要在“系统 / 应用配置”维护对应子应用的路由前缀和入口地址。
88
108
 
89
109
  初始化时可以自定义应用目录名:
90
110
 
@@ -5,14 +5,14 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev:main": "pnpm --filter main-app dev",
8
- "dev:sub": "pnpm --filter sub-app dev",
9
- "dev:sub-app": "pnpm --filter sub-app dev",
8
+ "dev:sub": "node scripts/run-sub-app.mjs dev sub-app",
9
+ "dev:sub-app": "node scripts/run-sub-app.mjs dev sub-app",
10
10
  "check:main": "pnpm --filter main-app check",
11
- "check:sub": "pnpm --filter sub-app check",
12
- "check:sub-app": "pnpm --filter sub-app check",
11
+ "check:sub": "node scripts/run-sub-app.mjs check sub-app",
12
+ "check:sub-app": "node scripts/run-sub-app.mjs check sub-app",
13
13
  "build:main": "pnpm --filter main-app build",
14
- "build:sub": "pnpm --filter sub-app build",
15
- "build:sub-app": "pnpm --filter sub-app build",
14
+ "build:sub": "node scripts/run-sub-app.mjs build sub-app",
15
+ "build:sub-app": "node scripts/run-sub-app.mjs build sub-app",
16
16
  "build:all": "pnpm build:main && pnpm build:sub"
17
17
  },
18
18
  "engines": {
@@ -0,0 +1,23 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ const [script, defaultApp, appArg] = process.argv.slice(2);
4
+ const app = appArg || defaultApp;
5
+
6
+ if (!script || !app) {
7
+ console.error('用法:node scripts/run-sub-app.mjs <dev|check|build> <默认子应用> [子应用目录或包名]');
8
+ process.exit(1);
9
+ }
10
+
11
+ const child = spawn('pnpm', ['--filter', app, script], {
12
+ stdio: 'inherit',
13
+ shell: process.platform === 'win32',
14
+ });
15
+
16
+ child.on('exit', (code, signal) => {
17
+ if (signal) {
18
+ process.kill(process.pid, signal);
19
+ return;
20
+ }
21
+
22
+ process.exit(code ?? 1);
23
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morningfast/create-ui",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Morningfast UI internal system scaffold CLI.",
5
5
  "bin": {
6
6
  "create-morningfast-ui": "dist/index.js"