@yarch/create-admin 0.1.0 → 0.2.1

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 (63) hide show
  1. package/README.md +94 -26
  2. package/bin/create-admin.mjs +115 -20
  3. package/bin/gen-registry-snapshot.mjs +32 -0
  4. package/bin/registry-snapshot.json +15 -0
  5. package/bin/registry.mjs +19 -0
  6. package/package.json +2 -2
  7. package/templates/admin-antd/AGENTS.md +33 -0
  8. package/templates/admin-antd/CLAUDE.md +1 -0
  9. package/templates/admin-antd/GEMINI.md +1 -0
  10. package/templates/admin-antd/archetype.json +48 -7
  11. package/templates/admin-antd/package.json +1 -0
  12. package/templates/admin-arco/AGENTS.md +33 -0
  13. package/templates/admin-arco/CLAUDE.md +1 -0
  14. package/templates/admin-arco/GEMINI.md +1 -0
  15. package/templates/admin-semi/AGENTS.md +33 -0
  16. package/templates/admin-semi/CLAUDE.md +1 -0
  17. package/templates/admin-semi/GEMINI.md +1 -0
  18. package/templates/base-semi/AGENTS.md +30 -0
  19. package/templates/base-semi/CLAUDE.md +1 -0
  20. package/templates/base-semi/GEMINI.md +1 -0
  21. package/templates/base-semi/README.md +35 -0
  22. package/templates/base-semi/archetype.json +13 -0
  23. package/templates/base-semi/conf/nginx.conf.example +19 -0
  24. package/templates/base-semi/index.html +11 -0
  25. package/templates/base-semi/package.json +29 -0
  26. package/templates/base-semi/src/app/micro-apps.config.ts +16 -0
  27. package/templates/base-semi/src/app/router.tsx +44 -0
  28. package/templates/base-semi/src/layouts/basic-layout.tsx +51 -0
  29. package/templates/base-semi/src/main.tsx +22 -0
  30. package/templates/base-semi/src/micro/shared.ts +43 -0
  31. package/templates/base-semi/src/micro/sub-app.tsx +65 -0
  32. package/templates/base-semi/src/pages/dashboard.tsx +16 -0
  33. package/templates/base-semi/src/pages/login.tsx +18 -0
  34. package/templates/base-semi/src/pages/not-found.tsx +13 -0
  35. package/templates/base-semi/src/stores/auth.ts +13 -0
  36. package/templates/base-semi/src/ui/theme.ts +7 -0
  37. package/templates/base-semi/src/vite-env.d.ts +1 -0
  38. package/templates/base-semi/tsconfig.json +16 -0
  39. package/templates/base-semi/vite.config.ts +17 -0
  40. package/templates/sub-semi/AGENTS.md +33 -0
  41. package/templates/sub-semi/CLAUDE.md +1 -0
  42. package/templates/sub-semi/GEMINI.md +1 -0
  43. package/templates/sub-semi/README.md +28 -0
  44. package/templates/sub-semi/archetype.json +13 -0
  45. package/templates/sub-semi/index.html +11 -0
  46. package/templates/sub-semi/package.json +34 -0
  47. package/templates/sub-semi/src/app/root.tsx +23 -0
  48. package/templates/sub-semi/src/app/router.tsx +26 -0
  49. package/templates/sub-semi/src/events.ts +4 -0
  50. package/templates/sub-semi/src/features/invoices/api.ts +19 -0
  51. package/templates/sub-semi/src/features/invoices/hooks/use-invoices.ts +18 -0
  52. package/templates/sub-semi/src/layouts/sub-layout.tsx +12 -0
  53. package/templates/sub-semi/src/main.tsx +28 -0
  54. package/templates/sub-semi/src/pages/invoices.tsx +49 -0
  55. package/templates/sub-semi/src/supply/integrated.ts +48 -0
  56. package/templates/sub-semi/src/supply/shims/contract.ts +25 -0
  57. package/templates/sub-semi/src/supply/standalone.ts +38 -0
  58. package/templates/sub-semi/src/supply/types.ts +18 -0
  59. package/templates/sub-semi/src/types/index.ts +6 -0
  60. package/templates/sub-semi/src/ui/popup.ts +13 -0
  61. package/templates/sub-semi/src/vite-env.d.ts +1 -0
  62. package/templates/sub-semi/tsconfig.json +20 -0
  63. package/templates/sub-semi/vite.config.ts +96 -0
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,30 @@
1
+ # AGENTS.md — {{packageName}}(微前端基座)工程守则
2
+
3
+ > 本工程由 yarch 脚手架生成(`--micro base`),基于跨栈统一契约(yarch 仓 `contract/`)与微前端规约 `contract/web/micro-frontend.md`。
4
+ > 任何 AI 编码代理改码前先读本文件;条文与直觉冲突时以本文件为准,红线改动直接拒绝——机检与 CR 均按此执行。
5
+
6
+ ## 基座五权(只归基座,条文 micro-frontend.md 一-1)
7
+
8
+ 登录态唯一持有(十-1)· 导航端口唯一注册(十-2)· 菜单与路由分发(三-4)· 主题下发(六-1)· 错误兜底(四-5)。子应用索要任何一项 = 架构违规。
9
+
10
+ ## 契约红线(违反 = 机检失败 / CR 必拒)
11
+
12
+ | 红线 | 规则 | 出处 |
13
+ |---|---|---|
14
+ | manifest 装配 | 子应用接入/下线**只改 `src/app/micro-apps.config.ts` 登记表**(entry/version/routePrefix/menu);禁在任何组件里硬编码子应用路由或菜单 | 十二-2 / 三-4 |
15
+ | 登记先行 | 新子应用应用名须先在 yarch 仓 `contract/registry.md` 四节登记(两段式、首段=已登记服务名、全局不重名),再入本工程登记表 | registry.md 四 |
16
+ | 共享运行时 | `src/micro/shared.ts` 注册 `__YARCH_REACT__ / __YARCH_CONTRACT__ / __YARCH_SUPPLY__` 后才能启动载器;新增共享成员须同步子应用供给层口径 | 八-1 / 八-2 |
17
+ | 事件订阅宿主 | 子应用事件(`{应用名}:{动词-名词}`)的订阅挂**常驻布局壳**(`layouts/basic-layout.tsx`),不挂会随路由卸载的页面;订阅与退订成对 | 九-1 / 七-4 |
18
+ | 统一信封 / 错误码 / traceId | 同单应用档:判错只看 `code`,错误码只用常量,报障必附 traceId | contract/api/* |
19
+ | 底座升级 | `@yarch/contract`·`@yarch/react`·`@micro-zoe/micro-app` 一律 `pnpm update`;禁 patch / 内联拷贝 | — |
20
+
21
+ ## 验证(改完必跑)
22
+
23
+ ```bash
24
+ pnpm build # tsc --noEmit + vite build
25
+ pnpm build:micro 同款子应用联调:入口 URL 经 VITE_SUB_*_URL 注入,禁改死代码
26
+ ```
27
+
28
+ ## 完成定义(DoD)
29
+
30
+ tsc 零错 · build 通过 · 新子应用接入只动了登记表 · 基座在子应用全部下线后仍可独立运行(三-1)。
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,35 @@
1
+ # {{appName}} · 微前端基座
2
+
3
+ > {{description}}。规约依据:[contract/web/micro-frontend.md](https://github.com/ydonghao/yarch/blob/main/contract/web/micro-frontend.md) v1.0;
4
+ > 载器 micro-app(contract/README.md 关键架构决策登记表默认档,观察哨:停更切 qiankun)。
5
+
6
+ ## 五权归基座(三-2/十-1/十-2)
7
+
8
+ 登录态与 token、菜单/路由分发、主题与 UI 档定义、全局错误兜底、布局外壳——全部在本工程;
9
+ 子应用经 `__YARCH_SUPPLY__`(props 下行,五-5)与共享 contract 单例(八-1)取用,禁子应用自建。
10
+
11
+ ## 开发
12
+
13
+ ```bash
14
+ pnpm install && pnpm dev # http://localhost:{{port}}
15
+ ```
16
+
17
+ ## 子应用接入三步(十二-2:入口变更只改 manifest)
18
+
19
+ 1. 生成子应用:`npm create @yarch/admin ysaas-billing -- --micro sub --registry <contract/registry.md>`
20
+ 2. 在 `src/app/micro-apps.config.ts` 登记入口/路由前缀/菜单元数据(跨环境入口用 `.env.local` 覆盖,十二-7)
21
+ 3. 子应用侧起 `pnpm dev:micro`(集成模式 dev server,本基座即可加载)
22
+
23
+ ## 双模式冒烟(十一-3,CI 口径)
24
+
25
+ - 子应用独立:`pnpm build`(自足产物)
26
+ - 子应用集成:`pnpm build:micro`(框架/contract 经 window 全局 shim,产物体积消失——八-1/八-2 机检对象)
27
+
28
+ ## 部署(十二-3/十二-4)
29
+
30
+ 构建产物内容寻址(hash 文件名);nginx 一应用一 root + `try_files` 兜底——样例见 `conf/nginx.conf.example`。
31
+
32
+ ## 登记提醒
33
+
34
+ 应用名 `{{packageName}}`(首段服务名 `{{service}}`)须在 yarch 仓 `contract/registry.md` 四节前端应用名登记表登记(二-1/二-4);
35
+ storage key / 事件名 / 路由前缀均以应用名为前缀(二-2 一名三用)。
@@ -0,0 +1,13 @@
1
+ {
2
+ "description": "yarch 微前端基座模板 · Semi 档:micro-app 载器(默认档)+ manifest 声明式装配 + 登录态/导航/主题/错误兜底五权归基座",
3
+ "variables": [
4
+ { "name": "packageName", "desc": "应用名(= npm 包名 = 路由前缀 = storage/事件前缀,micro-frontend.md 二-1/二-2)", "required": true, "default": "" },
5
+ { "name": "appName", "desc": "展示名(index.html 标题)", "required": false, "default": "= packageName" },
6
+ { "name": "service", "desc": "首段服务名(registry.md 已登记,二-1)", "required": false, "default": "" },
7
+ { "name": "description", "desc": "工程描述(package.json)", "required": false, "default": "" },
8
+ { "name": "port", "desc": "vite dev 端口", "required": false, "default": "5173" },
9
+ { "name": "proxyTarget", "desc": "/api/v1 反向代理目标", "required": false, "default": "http://localhost:8080" },
10
+ { "name": "yarchContractDep", "desc": "@yarch/contract 依赖(发版前 file: 本地路径,发版后 ^x.y.z)", "required": false, "default": "" },
11
+ { "name": "yarchReactDep", "desc": "@yarch/react 依赖(同上)", "required": false, "default": "" }
12
+ ]
13
+ }
@@ -0,0 +1,19 @@
1
+ # 静态托管样例(micro-frontend.md 十二-3/十二-4,对齐 contract/infra/nginx.md 一-2 配置纪律):
2
+ # 构建产物内容寻址(hash 文件名)且旧版本保留——回滚 = 基座 manifest 指针回拨,不重发构建;
3
+ # 一应用一 root,try_files 兜底 history 路由 404。
4
+ server {
5
+ listen 80;
6
+ server_name console.example.com;
7
+
8
+ # 基座(本工程产物)
9
+ location /ysaas-console/ {
10
+ alias /data/www/ysaas-console/;
11
+ try_files $uri $uri/ /ysaas-console/index.html;
12
+ }
13
+
14
+ # 子应用(独立发版、独立回滚,十二-1;入口 URL 登记在基座 micro-apps.config.ts)
15
+ location /ysaas-billing/ {
16
+ alias /data/www/ysaas-billing/;
17
+ try_files $uri $uri/ /ysaas-billing/index.html;
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <title>{{appName}}</title>
6
+ </head>
7
+ <body>
8
+ <div id="root"></div>
9
+ <script type="module" src="/src/main.tsx"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "{{packageName}}",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "description": "{{description}}",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "tsc --noEmit && vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^19",
14
+ "react-dom": "^19",
15
+ "react-router": "^7",
16
+ "react-router-dom": "^7",
17
+ "@yarch/contract": "{{yarchContractDep}}",
18
+ "@yarch/react": "{{yarchReactDep}}",
19
+ "@douyinfe/semi-ui": "^2",
20
+ "@micro-zoe/micro-app": "^1.0.0-rc.32"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19",
24
+ "@types/react-dom": "^19",
25
+ "@vitejs/plugin-react": "^4",
26
+ "typescript": "^5.6",
27
+ "vite": "^6"
28
+ }
29
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 子应用入口登记表(十二-2 manifest,git 声明式纳管):应用名 → 入口/版本/路由前缀/菜单元数据。
3
+ * 子应用接入只改此表;跨环境入口 URL 由环境配置注入(十二-7,.env.local 覆盖),禁硬编码。
4
+ */
5
+ import type { MicroAppsConfig } from "@yarch/contract";
6
+ import { subAppRoutePrefix } from "@yarch/contract";
7
+
8
+ export const microApps: MicroAppsConfig = [
9
+ {
10
+ name: "ysaas-billing",
11
+ entry: import.meta.env.VITE_SUB_BILLING_URL ?? "http://127.0.0.1:5174/ysaas-billing/",
12
+ version: "0.1.0",
13
+ routePrefix: subAppRoutePrefix("ysaas-billing"),
14
+ menu: [{ key: "ysaas-billing:invoices", path: "/invoices", title: "账单" }],
15
+ },
16
+ ];
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 基座路由:登录页 + 布局壳(基座自有页面 + 子应用前缀分发 + 404 兜底,四-5)。
3
+ * 子应用路由由 manifest 声明式装配(三-4:基座禁硬编码子应用内部路由)。
4
+ */
5
+ import { Navigate, Routes, Route, useNavigate } from "react-router-dom";
6
+ import { useEffect, type ReactNode } from "react";
7
+ import { setupReactNavigator } from "@yarch/react";
8
+ import BasicLayout from "../layouts/basic-layout";
9
+ import Dashboard from "../pages/dashboard";
10
+ import Login from "../pages/login";
11
+ import NotFound from "../pages/not-found";
12
+ import SubAppView from "../micro/sub-app";
13
+ import { microApps } from "./micro-apps.config";
14
+ import { authStore } from "../stores/auth";
15
+
16
+ function NavigatorSetup() {
17
+ const navigate = useNavigate();
18
+ useEffect(() => { setupReactNavigator(navigate); }, [navigate]);
19
+ return null;
20
+ }
21
+
22
+ /** 登录门禁(十-1 登录态唯一归基座):未持 token 一律回登录页 */
23
+ function AuthGate({ children }: { children: ReactNode }) {
24
+ if (!authStore.token) return <Navigate to="/login" replace />;
25
+ return <>{children}</>;
26
+ }
27
+
28
+ export default function App() {
29
+ return (
30
+ <>
31
+ <NavigatorSetup />
32
+ <Routes>
33
+ <Route path="/login" element={<Login />} />
34
+ <Route path="/" element={<AuthGate><BasicLayout /></AuthGate>}>
35
+ <Route index element={<Dashboard />} />
36
+ {microApps.map((reg) => (
37
+ <Route key={reg.name} path={`${reg.routePrefix}/*`} element={<SubAppView appName={reg.name} />} />
38
+ ))}
39
+ <Route path="*" element={<NotFound />} />
40
+ </Route>
41
+ </Routes>
42
+ </>
43
+ );
44
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 基座布局与外壳(三-2):菜单/导航树 = 基座自有项 + manifest 声明式装配的子应用项(三-4)。
3
+ * 主题与 UI 档在此定义并下发(三-2/六-1/六-5)。
4
+ */
5
+ import { useEffect } from "react";
6
+ import { Layout, Nav, Toast } from "@douyinfe/semi-ui";
7
+ import { Outlet, useLocation, useNavigate } from "react-router-dom";
8
+ import { getAppEventBus } from "@yarch/contract";
9
+ import { microApps } from "../app/micro-apps.config";
10
+
11
+ export default function BasicLayout() {
12
+ const navigate = useNavigate();
13
+ const location = useLocation();
14
+ const { Sider, Header, Content } = Layout;
15
+
16
+ // 事件上行订阅挂常驻布局壳(九-1/九-3):路由切换不卸载,子应用事件随时可达(七-4 订阅与移除成对)
17
+ useEffect(() => {
18
+ const offList = microApps.map((reg) =>
19
+ getAppEventBus().on(`${reg.name}:export-done`, (payload) => {
20
+ Toast.success(`${reg.name} 事件上行:export-done ${JSON.stringify(payload)}`);
21
+ }),
22
+ );
23
+ return () => offList.forEach((off) => off());
24
+ }, []);
25
+
26
+ const items = [
27
+ { itemKey: "/", text: "首页" },
28
+ ...microApps.flatMap((reg) =>
29
+ reg.menu.map((m) => ({ itemKey: `${reg.routePrefix}${m.path}`, text: m.title })),
30
+ ),
31
+ ];
32
+
33
+ return (
34
+ <Layout style={{ minHeight: "100vh" }}>
35
+ <Sider>
36
+ <Nav
37
+ selectedKeys={[location.pathname]}
38
+ header={{ text: "{{appName}}" }}
39
+ items={items}
40
+ onSelect={(data) => navigate(String(data.itemKey))}
41
+ />
42
+ </Sider>
43
+ <Layout>
44
+ <Header style={{ background: "#fff", padding: "0 24px" }} />
45
+ <Content style={{ padding: 24 }}>
46
+ <Outlet />
47
+ </Content>
48
+ </Layout>
49
+ </Layout>
50
+ );
51
+ }
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { createRoot } from "react-dom/client";
4
+ import { semiGlobal } from "@douyinfe/semi-ui";
5
+ // React 19 下 Semi 命令式渲染(Toast/Notification 等)须显式注入 createRoot,否则弹层静默不渲染
6
+ semiGlobal.config.createRoot = createRoot;
7
+ import { BrowserRouter } from "react-router-dom";
8
+ import microApp from "@micro-zoe/micro-app";
9
+ import App from "./app/router";
10
+ import { registerSharedRuntimes } from "./micro/shared";
11
+
12
+ // 基座先行注册共享运行时(八-1/八-2),再启动载器——子应用集成构建依赖 window 全局
13
+ registerSharedRuntimes();
14
+ microApp.start();
15
+
16
+ ReactDOM.createRoot(document.getElementById("root")!).render(
17
+ <React.StrictMode>
18
+ <BrowserRouter>
19
+ <App />
20
+ </BrowserRouter>
21
+ </React.StrictMode>
22
+ );
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 共享运行时注册(八-1/八-2):整个微前端体系只允许一份 @yarch/contract 与框架运行时,
3
+ * 由基座注册到 window 全局;子应用集成构建(--mode integrated)经同名 shim 取用,
4
+ * 产物中框架与 contract 体积消失(十三表八-1/八-2 机检的运行时基础)。
5
+ */
6
+ import * as contract from "@yarch/contract";
7
+ import * as react from "react";
8
+ import * as reactDom from "react-dom";
9
+ import * as reactDomClient from "react-dom/client";
10
+ import * as reactJsxRuntime from "react/jsx-runtime";
11
+ import * as reactJsxDevRuntime from "react/jsx-dev-runtime";
12
+ import * as reactRouter from "react-router";
13
+ import * as reactRouterDom from "react-router-dom";
14
+ import type { SubAppUser } from "@yarch/contract";
15
+ import { authStore } from "../stores/auth";
16
+ import { themeToken } from "../ui/theme";
17
+
18
+ export const SHARED_REACT_KEY = "__YARCH_REACT__";
19
+ export const SUPPLY_KEY = "__YARCH_SUPPLY__";
20
+
21
+ export function registerSharedRuntimes(): void {
22
+ const g = globalThis as Record<string, unknown>;
23
+ g[SHARED_REACT_KEY] = {
24
+ "react": react,
25
+ "react-dom": reactDom,
26
+ "react-dom/client": reactDomClient,
27
+ "react/jsx-runtime": reactJsxRuntime,
28
+ "react/jsx-dev-runtime": reactJsxDevRuntime,
29
+ "react-router": reactRouter,
30
+ "react-router-dom": reactRouterDom,
31
+ };
32
+ // 八-1:contract 单例标记(子应用 shim 与 e2e instanceof 断言的取用点)
33
+ contract.setSharedContract(contract);
34
+
35
+ // props 下行供给(五-5 最小集;getter 保证子应用每次取值都是最新登录态,十-1 token 只归基座)
36
+ g[SUPPLY_KEY] = {
37
+ getToken: (): string | null => authStore.token || null,
38
+ get user(): SubAppUser | null {
39
+ return authStore.token ? { id: "demo", name: "演示用户" } : null;
40
+ },
41
+ themeToken,
42
+ };
43
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * <micro-app> 基座侧封装:loading 骨架(五-2:mount 完成前 UI 态由基座持有)+
3
+ * 加载失败页含重试入口(四-5:禁白屏)。子应用路由前缀分发在 router.tsx 由 manifest 装配(三-4)。
4
+ */
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { Button, Spin, Typography } from "@douyinfe/semi-ui";
7
+ import { microApps } from "../app/micro-apps.config";
8
+
9
+ declare module "react" {
10
+ namespace JSX {
11
+ interface IntrinsicElements {
12
+ "micro-app": React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
13
+ name: string;
14
+ url: string;
15
+ };
16
+ }
17
+ }
18
+ }
19
+
20
+ export default function SubAppView({ appName }: { appName: string }) {
21
+ const reg = microApps.find((r) => r.name === appName);
22
+ const hostRef = useRef<HTMLElement | null>(null);
23
+ const [loading, setLoading] = useState(true);
24
+ const [failed, setFailed] = useState(false);
25
+ const [retry, setRetry] = useState(0);
26
+
27
+ useEffect(() => {
28
+ const el = hostRef.current;
29
+ if (!el || !reg) return;
30
+ // 自定义元素事件经 addEventListener(成对移除,七-4)
31
+ const onMounted = () => { setLoading(false); setFailed(false); };
32
+ const onError = () => { setLoading(false); setFailed(true); };
33
+ el.addEventListener("mounted", onMounted);
34
+ el.addEventListener("error", onError);
35
+ return () => {
36
+ el.removeEventListener("mounted", onMounted);
37
+ el.removeEventListener("error", onError);
38
+ };
39
+ }, [reg, retry]);
40
+
41
+ if (!reg) {
42
+ return <Typography.Text type="danger">未登记的子应用:{appName}(见 micro-apps.config.ts,十二-2)</Typography.Text>;
43
+ }
44
+
45
+ return (
46
+ <div data-sub-app={reg.name}>
47
+ {loading && <Spin size="large" style={{ display: "flex", justifyContent: "center", padding: 48 }} />}
48
+ {failed && (
49
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 12, padding: 48 }}>
50
+ <Typography.Text type="danger">子应用 {reg.name} 加载失败(入口 {reg.entry})</Typography.Text>
51
+ <Button theme="solid" type="warning" onClick={() => { setLoading(true); setRetry((n) => n + 1); }}>
52
+ 重试
53
+ </Button>
54
+ </div>
55
+ )}
56
+ <micro-app
57
+ key={`${reg.name}-${retry}`}
58
+ ref={(el: HTMLElement | null) => { hostRef.current = el; }}
59
+ name={reg.name}
60
+ url={reg.entry}
61
+ style={{ display: failed ? "none" : "block" }}
62
+ />
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 基座自有首页(三-1:去掉全部子应用后基座仍能运行——登录/菜单框架/404/错误页齐全)。
3
+ * 事件上行订阅挂常驻布局壳(layouts/basic-layout.tsx)——页面切换不丢订阅。
4
+ */
5
+ import { Typography } from "@douyinfe/semi-ui";
6
+ import { microApps } from "../app/micro-apps.config";
7
+
8
+ export default function Dashboard() {
9
+ return (
10
+ <div>
11
+ <h2>基座首页</h2>
12
+ <p>已登记子应用:{microApps.map((r) => r.name).join("、") || "(无)"}</p>
13
+ <Typography.Text type="tertiary">菜单由 micro-apps.config.ts 声明式装配(三-4)</Typography.Text>
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,18 @@
1
+ import { Button } from "@douyinfe/semi-ui";
2
+ import { Navigate, useNavigate } from "react-router-dom";
3
+ import { authStore } from "../stores/auth";
4
+
5
+ export default function Login() {
6
+ const navigate = useNavigate();
7
+ // 已登录访问登录页一律回首页(十-1 登录态唯一归基座——覆盖会话恢复后直达 /login 的场景)
8
+ if (authStore.token) return <Navigate to="/" replace />;
9
+ return (
10
+ <div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
11
+ <form onSubmit={(e: React.FormEvent) => { e.preventDefault(); authStore.token = "demo"; navigate("/"); }}>
12
+ <h2>{{appName}} · 登录</h2>
13
+ <p>演示——真实认证走 yarch-auth JWT;登录态唯一归基座(十-1)</p>
14
+ <Button theme="solid" type="primary" htmlType="submit" block>登录</Button>
15
+ </form>
16
+ </div>
17
+ );
18
+ }
@@ -0,0 +1,13 @@
1
+ import { Button, Typography } from "@douyinfe/semi-ui";
2
+ import { useNavigate } from "react-router-dom";
3
+
4
+ /** 四-5:未匹配路由兜底 404,禁白屏 */
5
+ export default function NotFound() {
6
+ const navigate = useNavigate();
7
+ return (
8
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 12, padding: 48 }}>
9
+ <Typography.Title heading={3}>404 · 页面不存在</Typography.Title>
10
+ <Button theme="solid" onClick={() => navigate("/")}>回首页</Button>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 登录态持有与刷新(十-1:token 只由基座持有;二-2/七-3:storage key 带 {应用名}: 前缀)。
3
+ * 子应用经 __YARCH_SUPPLY__.getToken 取用(五-5 props 下行),禁自行持久化。
4
+ */
5
+ import { createAppStorage } from "@yarch/contract";
6
+
7
+ const storage = createAppStorage("{{appName}}");
8
+
9
+ export const authStore = {
10
+ get token() { return storage.get<string>("token") ?? ""; },
11
+ set token(v: string) { storage.set("token", v); },
12
+ clear() { storage.remove("token"); },
13
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 主题 token(六-5:主题从基座下发,子应用禁自带第二套主题定义)——
3
+ * 经 __YARCH_SUPPLY__.themeToken 下发;semi 主题定制走 semi 官方 DSM 通道,此处登记业务 token。
4
+ */
5
+ export const themeToken: Record<string, string> = {
6
+ "brand": "#165DFF",
7
+ };
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "jsx": "react-jsx",
11
+ "noEmit": true
12
+ },
13
+ "include": [
14
+ "src"
15
+ ]
16
+ }
@@ -0,0 +1,17 @@
1
+ import react from "@vitejs/plugin-react";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ host: "127.0.0.1", // 钉死 IPv4 回环:与默认入口 URL/CI 同口径(vite 默认 localhost 在 macOS 为 ::1 only)
8
+ port: {{port}},
9
+ proxy: {
10
+ // 后端 API 反向代理(对接 yarch java/golang 栈的信封接口)
11
+ "/api/v1": {
12
+ target: "{{proxyTarget}}",
13
+ changeOrigin: true,
14
+ },
15
+ },
16
+ },
17
+ });
@@ -0,0 +1,33 @@
1
+ # AGENTS.md — {{packageName}}(微前端子应用)工程守则
2
+
3
+ > 本工程由 yarch 脚手架生成(`--micro sub`),独立·集成双运行形态(yarch 仓 `contract/web/micro-frontend.md` 十一)。
4
+ > 任何 AI 编码代理改码前先读本文件;条文与直觉冲突时以本文件为准,红线改动直接拒绝——机检与 CR 均按此执行。
5
+
6
+ ## 双模式铁律(条文十一-2)
7
+
8
+ 一切运行期差异**只进 `src/supply/` 供给层**(standalone.ts / integrated.ts),业务代码零分支;`vite --mode integrated` 构建时 react 系与 `@yarch/contract` 由基座 window 全局提供(八-1/八-2),产物 `dist-micro/` 里框架体积必须消失。
9
+
10
+ ## 契约红线(违反 = 机检失败 / CR 必拒)
11
+
12
+ | 红线 | 规则 | 出处 |
13
+ |---|---|---|
14
+ | 禁载器 | 子应用**禁 import `@micro-zoe/micro-app`**——载器只属基座(depcruise 规则 micro-loader-only-in-base) | 三-2 |
15
+ | 导航注册 | `@yarch/react` 只允许在 `src/supply/` 出现(独立模式注册导航);业务层禁 import(十-2) | 十-2 |
16
+ | 禁连基座 | 禁 import 基座工程源码——共享只经基座 window 全局与挂载 props(五-5) | 十二-2 |
17
+ | 事件名 | 自定义事件一律 `{应用名}:{动词-名词}`(如 `{{packageName}}:export-done`);用 `defineAppEvents` 封装,禁裸事件名 | 九-3 |
18
+ | storage key | 一律 `{应用名}:` 前缀(`createAppStorage` 已内置);**禁裸 key 直写 localStorage**(跨应用覆写事故) | 二-2 / 七-3 |
19
+ | 登录态 | token 只从 `__YARCH_SUPPLY__.getToken` / 供给层取;**禁自行持久化登录态**(唯一归基座) | 十-1 |
20
+ | 路由 | 路由 base = 应用名前缀(供给层注入);基座 manifest 里声明的每个 menu path 在本工程路由表必须有对应路由 | 四-1 / 四-2 |
21
+ | 卸载清理 | unmount 彻底清理(root.unmount / 定时器 / 监听 / 动态 DOM);重挂幂等,来回切换不崩不白屏 | 五-2 / 五-3 |
22
+ | 弹层容器 | Modal/Toast 挂本应用容器(六-3);React 19 下命令式弹层须注入 `semiGlobal.config.createRoot`(入口已预置,勿删) | 六-3 |
23
+
24
+ ## 验证(改完必跑)
25
+
26
+ ```bash
27
+ pnpm build # 独立模式:tsc --noEmit + vite build
28
+ pnpm build:micro # 集成模式:产物 dist-micro/(框架体积消失是八-2 的构建口径)
29
+ ```
30
+
31
+ ## 完成定义(DoD)
32
+
33
+ 双模式 build 全绿 · 双模式运行可进可出 · 新页面在基座集成档与独立直开档行为一致。
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,28 @@
1
+ # {{appName}} · 微前端子应用
2
+
3
+ > {{description}}。规约依据:[contract/web/micro-frontend.md](https://github.com/ydonghao/yarch/blob/main/contract/web/micro-frontend.md) v1.0。
4
+
5
+ ## 双模式(十一)
6
+
7
+ | 模式 | 命令 | 说明 |
8
+ |---|---|---|
9
+ | 独立运行 | `pnpm dev` / `pnpm build` | 全量自足,浏览器直开 http://localhost:{{port}}/ysaas-billing/…(URL 形态与集成一致,四-1) |
10
+ | 集成运行 | `pnpm dev:micro` / `pnpm build:micro` | 供给层切 `__YARCH_SUPPLY__` 实现;react 系与 @yarch/contract 经 window shim 由基座提供(八-1/八-2)——集成产物 `dist-micro/` 中框架与 contract 体积消失 |
11
+
12
+ 双模式差异全部收敛在 `src/supply/`(十一-2 环境供给层);业务代码出现 `if (isMicro)` 即设计缺陷。
13
+
14
+ ## 纪律速查(详见规约)
15
+
16
+ - token 不落本应用(十-1):`supply.getAccessToken()` 注入 createClient;401 只抛 `ApiError`,跳登录归基座(十-2)。
17
+ - storage/事件一律前缀(二-2/九-3):`createAppStorage` / `defineAppEvents`(见 `src/events.ts`),禁裸 key、裸事件名。
18
+ - 弹层挂本应用容器(六-3):`getPopupContainer`(见 `src/ui/popup.ts`)。
19
+ - 全局监听成对移除(七-4/五-2):卸载清理由供给层 `onUnmount` 统一挂接。
20
+ - UI 档与基座一致(六-1):semi(本模板档固化);主题 token 由基座下发(六-5)。
21
+
22
+ ## 接入基座
23
+
24
+ 在基座仓 `src/app/micro-apps.config.ts` 登记本应用(十二-2):`name: "{{packageName}}"`、入口 URL(跨环境 env 注入,十二-7)、菜单元数据。
25
+
26
+ ## 登记提醒
27
+
28
+ 应用名 `{{packageName}}`(首段服务名 `{{service}}`)须在 yarch 仓 `contract/registry.md` 四节前端应用名登记表登记(二-1/二-4)。
@@ -0,0 +1,13 @@
1
+ {
2
+ "description": "yarch 微前端子应用模板 · Semi 档:独立·集成双模式供给层 + micro-app 生命周期固化 + 前缀 storage/事件 + 框架 peerDeps 不打包",
3
+ "variables": [
4
+ { "name": "packageName", "desc": "应用名(= npm 包名 = 路由前缀 = storage/事件前缀,micro-frontend.md 二-1/二-2)", "required": true, "default": "" },
5
+ { "name": "appName", "desc": "展示名(index.html 标题)", "required": false, "default": "= packageName" },
6
+ { "name": "service", "desc": "首段服务名(registry.md 已登记,二-1)", "required": false, "default": "" },
7
+ { "name": "description", "desc": "工程描述(package.json)", "required": false, "default": "" },
8
+ { "name": "port", "desc": "vite dev 端口", "required": false, "default": "5174" },
9
+ { "name": "proxyTarget", "desc": "/api/v1 反向代理目标", "required": false, "default": "http://localhost:8080" },
10
+ { "name": "yarchContractDep", "desc": "@yarch/contract 依赖(发版前 file: 本地路径,发版后 ^x.y.z)", "required": false, "default": "" },
11
+ { "name": "yarchReactDep", "desc": "@yarch/react 依赖(同上)", "required": false, "default": "" }
12
+ ]
13
+ }
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <title>{{appName}}</title>
6
+ </head>
7
+ <body>
8
+ <div id="root"></div>
9
+ <script type="module" src="/src/main.tsx"></script>
10
+ </body>
11
+ </html>