@fulgurjs/federation 5.8.0 → 5.9.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.
- package/CHANGELOG.md +19 -4
- package/README.en.md +60 -24
- package/README.md +60 -24
- package/dist/cli.js +327 -29
- package/dist/index.cjs +2 -2
- package/dist/index.js +2 -2
- package/dist/runtime.js +1 -1
- package/docs/API.en.md +14 -3
- package/docs/API.md +7 -6
- package/docs/webpack-mf-/345/257/271/347/205/247/344/270/216/347/274/272/345/217/243.md +2 -2
- package/examples/README.md +3 -67
- package/examples/templates/README.md +88 -0
- package/examples/templates/react-host-vue-remote/README.md +18 -0
- package/examples/templates/react-host-vue-remote/package.json +11 -0
- package/examples/templates/react-host-vue-remote/pnpm-lock.yaml +1627 -0
- package/examples/templates/react-host-vue-remote/pnpm-workspace.yaml +10 -0
- package/examples/{bridge → templates/react-host-vue-remote}/react-host/README.md +8 -7
- package/examples/{bridge → templates/react-host-vue-remote}/react-host/package.json +1 -1
- package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote.d/_paths.d.ts +4 -0
- package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote.d/bridge.ts +3 -0
- package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote.d.ts +9 -0
- package/examples/templates/react-host-vue-remote/scripts/dev.config.json +6 -0
- package/examples/templates/react-host-vue-remote/scripts/dev.mjs +173 -0
- package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/README.md +10 -5
- package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/package.json +1 -1
- package/examples/{react → templates/react-react}/README.md +9 -13
- package/examples/{react → templates/react-react}/host/README.md +6 -8
- package/examples/{react → templates/react-react}/host/package.json +1 -1
- package/examples/templates/react-react/host/src/fulgurjs/types/react-remote.d/ClickButton.ts +3 -0
- package/examples/templates/react-react/host/src/fulgurjs/types/react-remote.d/_paths.d.ts +4 -0
- package/examples/templates/react-react/host/src/fulgurjs/types/react-remote.d/pages/DetailPage.ts +3 -0
- package/examples/templates/react-react/host/src/fulgurjs/types/react-remote.d/pages/HomePage.ts +3 -0
- package/examples/templates/react-react/host/src/fulgurjs/types/react-remote.d/utils.ts +2 -0
- package/examples/templates/react-react/host/src/fulgurjs/types/react-remote.d.ts +26 -0
- package/examples/templates/react-react/package.json +11 -0
- package/examples/templates/react-react/pnpm-lock.yaml +1537 -0
- package/examples/templates/react-react/pnpm-workspace.yaml +10 -0
- package/examples/{react → templates/react-react}/remote/README.md +7 -6
- package/examples/{react → templates/react-react}/remote/package.json +1 -1
- package/examples/templates/react-react/scripts/dev.config.json +6 -0
- package/examples/templates/react-react/scripts/dev.mjs +173 -0
- package/examples/templates/showcase/README.md +109 -0
- package/examples/templates/showcase/package.json +13 -0
- package/examples/templates/showcase/pnpm-lock.yaml +1858 -0
- package/examples/templates/showcase/pnpm-workspace.yaml +12 -0
- package/examples/templates/showcase/react-host/fulgurjs.config.ts +21 -0
- package/examples/templates/showcase/react-host/index.html +13 -0
- package/examples/templates/showcase/react-host/package.json +26 -0
- package/examples/templates/showcase/react-host/src/GuardBanner.tsx +33 -0
- package/examples/templates/showcase/react-host/src/Layout.tsx +42 -0
- package/examples/templates/showcase/react-host/src/ObsPanel.tsx +44 -0
- package/examples/templates/showcase/react-host/src/demo-log.ts +74 -0
- package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote.d/_paths.d.ts +4 -0
- package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote.d/bridge.ts +3 -0
- package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote.d.ts +7 -0
- package/examples/templates/showcase/react-host/src/main.tsx +33 -0
- package/examples/templates/showcase/react-host/src/pages/AboutPage.tsx +18 -0
- package/examples/templates/showcase/react-host/src/pages/BridgeVuePage.tsx +77 -0
- package/examples/templates/showcase/react-host/src/pages/HomePage.tsx +14 -0
- package/examples/templates/showcase/react-host/src/routing.ts +30 -0
- package/examples/templates/showcase/react-host/tsconfig.json +15 -0
- package/examples/templates/showcase/react-host/vite.config.ts +8 -0
- package/examples/templates/showcase/react-remote/fulgurjs.config.ts +18 -0
- package/examples/templates/showcase/react-remote/index.html +13 -0
- package/examples/templates/showcase/react-remote/package.json +25 -0
- package/examples/templates/showcase/react-remote/src/ChildLayout.tsx +48 -0
- package/examples/templates/showcase/react-remote/src/bridge.tsx +62 -0
- package/examples/templates/showcase/react-remote/src/child-bus.ts +32 -0
- package/examples/templates/showcase/react-remote/src/main.tsx +18 -0
- package/examples/templates/showcase/react-remote/src/pages/Locked.tsx +23 -0
- package/examples/templates/showcase/react-remote/src/pages/OrderDetail.tsx +36 -0
- package/examples/templates/showcase/react-remote/src/pages/OrderList.tsx +110 -0
- package/examples/templates/showcase/react-remote/src/pages/Settings.tsx +50 -0
- package/examples/templates/showcase/react-remote/src/settings-store.ts +11 -0
- package/examples/templates/showcase/react-remote/tsconfig.json +14 -0
- package/examples/templates/showcase/react-remote/vite.config.ts +14 -0
- package/examples/templates/showcase/scripts/dev.config.json +8 -0
- package/examples/templates/showcase/scripts/dev.mjs +173 -0
- package/examples/templates/showcase/vue-host/fulgurjs.config.ts +21 -0
- package/examples/templates/showcase/vue-host/index.html +13 -0
- package/examples/templates/showcase/vue-host/package.json +25 -0
- package/examples/templates/showcase/vue-host/src/App.vue +50 -0
- package/examples/templates/showcase/vue-host/src/GuardBanner.vue +24 -0
- package/examples/templates/showcase/vue-host/src/ObsPanel.vue +60 -0
- package/examples/templates/showcase/vue-host/src/demo-log.ts +49 -0
- package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote.d/_paths.d.ts +4 -0
- package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote.d/bridge.ts +3 -0
- package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote.d.ts +7 -0
- package/examples/templates/showcase/vue-host/src/guard.ts +47 -0
- package/examples/templates/showcase/vue-host/src/main.ts +30 -0
- package/examples/templates/showcase/vue-host/src/pages/AboutPage.vue +17 -0
- package/examples/templates/showcase/vue-host/src/pages/BridgeReactPage.vue +74 -0
- package/examples/templates/showcase/vue-host/src/pages/HomePage.vue +13 -0
- package/examples/templates/showcase/vue-host/src/routing.ts +31 -0
- package/examples/templates/showcase/vue-host/tsconfig.json +14 -0
- package/examples/templates/showcase/vue-remote/fulgurjs.config.ts +17 -0
- package/examples/templates/showcase/vue-remote/index.html +13 -0
- package/examples/templates/showcase/vue-remote/package.json +23 -0
- package/examples/templates/showcase/vue-remote/src/ChildLayout.vue +54 -0
- package/examples/templates/showcase/vue-remote/src/bridge.ts +48 -0
- package/examples/templates/showcase/vue-remote/src/child-bus.ts +32 -0
- package/examples/templates/showcase/vue-remote/src/main.ts +16 -0
- package/examples/templates/showcase/vue-remote/src/pages/Locked.vue +28 -0
- package/examples/templates/showcase/vue-remote/src/pages/OrderDetail.vue +46 -0
- package/examples/templates/showcase/vue-remote/src/pages/OrderList.vue +116 -0
- package/examples/templates/showcase/vue-remote/src/pages/Settings.vue +34 -0
- package/examples/templates/showcase/vue-remote/src/settings-store.ts +13 -0
- package/examples/templates/showcase/vue-remote/tsconfig.json +14 -0
- package/examples/templates/showcase/vue-remote/vite.config.ts +14 -0
- package/examples/templates/vue-host-react-remote/README.md +18 -0
- package/examples/templates/vue-host-react-remote/package.json +11 -0
- package/examples/templates/vue-host-react-remote/pnpm-lock.yaml +1663 -0
- package/examples/templates/vue-host-react-remote/pnpm-workspace.yaml +10 -0
- package/examples/{bridge → templates/vue-host-react-remote}/react-remote/README.md +9 -4
- package/examples/{bridge → templates/vue-host-react-remote}/react-remote/package.json +1 -1
- package/examples/templates/vue-host-react-remote/scripts/dev.config.json +6 -0
- package/examples/templates/vue-host-react-remote/scripts/dev.mjs +173 -0
- package/examples/{bridge → templates/vue-host-react-remote}/vue-host/README.md +8 -7
- package/examples/{bridge → templates/vue-host-react-remote}/vue-host/package.json +1 -1
- package/examples/{bridge → templates/vue-host-react-remote}/vue-host/src/App.vue +1 -1
- package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote.d/_paths.d.ts +4 -0
- package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote.d/bridge.ts +3 -0
- package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote.d.ts +9 -0
- package/examples/{vue → templates/vue-vue}/README.md +9 -13
- package/examples/{vue → templates/vue-vue}/host/README.md +6 -8
- package/examples/{vue → templates/vue-vue}/host/package.json +1 -1
- package/examples/templates/vue-vue/host/src/fulgurjs/types/vue-remote.d/ClickButton.ts +4 -0
- package/examples/templates/vue-vue/host/src/fulgurjs/types/vue-remote.d/_paths.d.ts +4 -0
- package/examples/templates/vue-vue/host/src/fulgurjs/types/vue-remote.d/pages/DetailPage.ts +4 -0
- package/examples/templates/vue-vue/host/src/fulgurjs/types/vue-remote.d/pages/HomePage.ts +4 -0
- package/examples/templates/vue-vue/host/src/fulgurjs/types/vue-remote.d/utils.ts +2 -0
- package/examples/templates/vue-vue/host/src/fulgurjs/types/vue-remote.d.ts +29 -0
- package/examples/templates/vue-vue/host/vite.config.ts +8 -0
- package/examples/templates/vue-vue/package.json +11 -0
- package/examples/templates/vue-vue/pnpm-lock.yaml +1199 -0
- package/examples/templates/vue-vue/pnpm-workspace.yaml +10 -0
- package/examples/{vue → templates/vue-vue}/remote/README.md +7 -6
- package/examples/{vue → templates/vue-vue}/remote/package.json +1 -1
- package/examples/{vue → templates/vue-vue}/remote/src/App.vue +1 -1
- package/examples/templates/vue-vue/scripts/dev.config.json +6 -0
- package/examples/templates/vue-vue/scripts/dev.mjs +173 -0
- package/package.json +4 -4
- package/examples/README.en.md +0 -67
- package/examples/bridge/README.md +0 -36
- package/examples/bridge/react-host/package-lock.json +0 -2466
- package/examples/bridge/react-remote/package-lock.json +0 -2391
- package/examples/bridge/vue-host/package-lock.json +0 -1894
- package/examples/bridge/vue-remote/package-lock.json +0 -1865
- package/examples/react/host/package-lock.json +0 -2391
- package/examples/react/remote/package-lock.json +0 -2333
- package/examples/vue/host/package-lock.json +0 -1865
- package/examples/vue/remote/package-lock.json +0 -1843
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/fulgurjs.config.ts +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/index.html +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/src/env.d.ts +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/src/host-session.ts +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/src/main.tsx +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/tsconfig.json +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/react-host/vite.config.ts +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/fulgurjs.config.ts +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/index.html +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/src/App.vue +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/src/bridge.ts +0 -0
- /package/examples/{bridge/react-remote → templates/react-host-vue-remote/vue-remote}/src/env.d.ts +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/src/main.ts +0 -0
- /package/examples/{bridge/vue-host → templates/react-host-vue-remote/vue-remote}/tsconfig.json +0 -0
- /package/examples/{bridge → templates/react-host-vue-remote}/vue-remote/vite.config.ts +0 -0
- /package/examples/{react → templates/react-react}/host/fulgurjs.config.ts +0 -0
- /package/examples/{react → templates/react-react}/host/index.html +0 -0
- /package/examples/{react → templates/react-react}/host/src/App.tsx +0 -0
- /package/examples/{react → templates/react-react}/host/src/main.tsx +0 -0
- /package/examples/{react → templates/react-react}/host/src/pages/UtilsDemo.tsx +0 -0
- /package/examples/{react → templates/react-react}/host/src/remotePages.tsx +0 -0
- /package/examples/{bridge/react-remote → templates/react-react/host}/tsconfig.json +0 -0
- /package/examples/{react → templates/react-react}/host/vite.config.ts +0 -0
- /package/examples/{react → templates/react-react}/remote/fulgurjs.config.ts +0 -0
- /package/examples/{react → templates/react-react}/remote/index.html +0 -0
- /package/examples/{react → templates/react-react}/remote/src/App.tsx +0 -0
- /package/examples/{react → templates/react-react}/remote/src/exposes/ClickButton.tsx +0 -0
- /package/examples/{react → templates/react-react}/remote/src/exposes/pages/DetailPage.tsx +0 -0
- /package/examples/{react → templates/react-react}/remote/src/exposes/pages/HomePage.tsx +0 -0
- /package/examples/{react → templates/react-react}/remote/src/exposes/utils.ts +0 -0
- /package/examples/{react → templates/react-react}/remote/src/main.tsx +0 -0
- /package/examples/{react/host → templates/react-react/remote}/tsconfig.json +0 -0
- /package/examples/{react → templates/react-react}/remote/vite.config.ts +0 -0
- /package/examples/{vue/host → templates/showcase/vue-host}/vite.config.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/react-remote/fulgurjs.config.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/react-remote/index.html +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/react-remote/src/bridge.tsx +0 -0
- /package/examples/{bridge/vue-host → templates/vue-host-react-remote/react-remote}/src/env.d.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/react-remote/src/main.tsx +0 -0
- /package/examples/{react/remote → templates/vue-host-react-remote/react-remote}/tsconfig.json +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/react-remote/vite.config.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/vue-host/fulgurjs.config.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/vue-host/index.html +0 -0
- /package/examples/{bridge/vue-remote → templates/vue-host-react-remote/vue-host}/src/env.d.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/vue-host/src/host-session.ts +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/vue-host/src/main.ts +0 -0
- /package/examples/{bridge/vue-remote → templates/vue-host-react-remote/vue-host}/tsconfig.json +0 -0
- /package/examples/{bridge → templates/vue-host-react-remote}/vue-host/vite.config.ts +0 -0
- /package/examples/{vue → templates/vue-vue}/host/fulgurjs.config.ts +0 -0
- /package/examples/{vue → templates/vue-vue}/host/index.html +0 -0
- /package/examples/{vue → templates/vue-vue}/host/src/App.vue +0 -0
- /package/examples/{vue → templates/vue-vue}/host/src/main.ts +0 -0
- /package/examples/{vue → templates/vue-vue}/host/src/pages/HomePage.vue +0 -0
- /package/examples/{vue → templates/vue-vue}/host/src/pages/UtilsDemo.vue +0 -0
- /package/examples/{vue → templates/vue-vue}/host/tsconfig.json +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/fulgurjs.config.ts +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/index.html +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/src/exposes/ClickButton.vue +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/src/exposes/pages/DetailPage.vue +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/src/exposes/pages/HomePage.vue +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/src/exposes/utils.ts +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/src/main.ts +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/tsconfig.json +0 -0
- /package/examples/{vue → templates/vue-vue}/remote/vite.config.ts +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
packages:
|
|
2
|
+
- host
|
|
3
|
+
- remote
|
|
4
|
+
minimumReleaseAgeExclude:
|
|
5
|
+
- '@fulgurjs/federation@5.7.1 || 5.8.0 || 5.9.0'
|
|
6
|
+
# pnpm >=12 构建脚本批准(12 起弃用 onlyBuiltDependencies;pnpm 9-11 忽略本键:
|
|
7
|
+
# esbuild >=0.16 二进制经 optionalDependencies 平台包分发,postinstall 仅校验,不阻塞安装)
|
|
8
|
+
allowBuilds:
|
|
9
|
+
esbuild: true
|
|
10
|
+
vue-demi: true
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# react-remote —— React 远程(fulgurjs 联邦演示)
|
|
2
2
|
|
|
3
|
-
> **角色:React 远程(被消费方)。** 向 [`../host/`](../host
|
|
3
|
+
> **角色:React 远程(被消费方)。** 向 [`../host/`](../host)(react-host,端口 5204)暴露可点击按钮组件、普通 TS 工具模块、联邦首页与参数详情页;也可独立运行调试。
|
|
4
4
|
|
|
5
5
|
## 从干净拷贝运行
|
|
6
6
|
|
|
7
|
+
复制整个 `react-react/` 模板,在模板根安装依赖后启动本应用:
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
npm run dev # http://localhost:5203
|
|
10
|
+
pnpm install --frozen-lockfile
|
|
11
|
+
pnpm --dir remote dev
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
宿主需要先启动对应远程;也可以在模板根执行 `pnpm dev` 启动完整组合。环境和完整运行步骤见[组合说明](../README.md)。
|
|
14
15
|
|
|
15
16
|
## 暴露的模块(exposes)
|
|
16
17
|
|
|
@@ -37,7 +38,7 @@ data?.sumNumbers(2, 3, 7) // 12
|
|
|
37
38
|
## 生产构建与部署
|
|
38
39
|
|
|
39
40
|
```bash
|
|
40
|
-
|
|
41
|
+
pnpm run build # dist/(含 fulgurjs-remoteEntry.js 与 fulgurjs-manifest.json)
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
部署到 Nginx 子路径 `/react-remote/`(与宿主 `prod: '/react-remote'` 对应)。完整 Nginx 示例见[组合说明](../README.md#生产构建与最小-nginx-部署)。
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 模板统一 dev 启动器(规范源)。
|
|
4
|
+
*
|
|
5
|
+
* 本文件经 examples/scripts/sync-template-scripts.mjs 同步到五个模板的 scripts/dev.mjs;
|
|
6
|
+
* 模板内副本必须与规范源逐字节一致(check-catalog.mjs 校验哈希),模板复制出仓库后独立可用——
|
|
7
|
+
* 只依赖同目录 dev.config.json 与 Node 内置模块,不依赖仓库其他文件。
|
|
8
|
+
*
|
|
9
|
+
* 行为契约(与模板 README 一致):
|
|
10
|
+
* - 启动前预检全部端口:任何端口被占用则拒绝启动并指认处理方法(绝不按端口杀别人的进程);
|
|
11
|
+
* - 按 dev.config.json 顺序(远程在前宿主在后)启动各应用,逐个探活;
|
|
12
|
+
* - 任一应用启动失败/超时/中途退出:整组停止(只杀本次启动的子进程),非零退出并打印该应用日志尾部;
|
|
13
|
+
* - Ctrl+C / SIGTERM:清理本次启动的全部子进程后退出;不依赖 PID 文件;
|
|
14
|
+
* - 全部就绪后打印访问入口;探活 = 端口可连接(dev server 已监听),页面可用以浏览器验收为准。
|
|
15
|
+
*
|
|
16
|
+
* 平台:macOS/Linux 实测;Windows 走 taskkill 兜底路径,未经验证(见模板 README)。
|
|
17
|
+
*/
|
|
18
|
+
import { spawn } from 'node:child_process'
|
|
19
|
+
import fs from 'node:fs'
|
|
20
|
+
import net from 'node:net'
|
|
21
|
+
import path from 'node:path'
|
|
22
|
+
import { fileURLToPath } from 'node:url'
|
|
23
|
+
|
|
24
|
+
const templateRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
25
|
+
const { apps, readyTimeoutMs = 120000 } = JSON.parse(
|
|
26
|
+
fs.readFileSync(path.join(templateRoot, 'scripts', 'dev.config.json'), 'utf8'),
|
|
27
|
+
)
|
|
28
|
+
const isTTY = process.stdout.isTTY
|
|
29
|
+
const color = (code, text) => (isTTY ? `\x1b[${code}m${text}\x1b[0m` : text)
|
|
30
|
+
const EXIT = { ok: 0, startupFailed: 1, portConflict: 2, signal: 130 }
|
|
31
|
+
|
|
32
|
+
/** 双栈端口探测(vite 可能只绑 IPv4 或 IPv6 回环) */
|
|
33
|
+
function isPortUp(port) {
|
|
34
|
+
const probe = (host) =>
|
|
35
|
+
new Promise((resolve, reject) => {
|
|
36
|
+
const s = net.connect({ port, host, timeout: 800 })
|
|
37
|
+
s.on('connect', () => { s.destroy(); resolve(true) })
|
|
38
|
+
s.on('error', reject)
|
|
39
|
+
s.on('timeout', () => { s.destroy(); reject(new Error('timeout')) })
|
|
40
|
+
})
|
|
41
|
+
return Promise.any([probe('127.0.0.1'), probe('::1')]).catch(() => false)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const tailOf = new Map()
|
|
45
|
+
const children = new Map()
|
|
46
|
+
let shuttingDown = false
|
|
47
|
+
|
|
48
|
+
function recordOutput(name, chunk) {
|
|
49
|
+
const lines = (tailOf.get(name) ?? [])
|
|
50
|
+
for (const line of String(chunk).split('\n')) {
|
|
51
|
+
if (line === '') continue
|
|
52
|
+
lines.push(line)
|
|
53
|
+
}
|
|
54
|
+
while (lines.length > 60) lines.shift()
|
|
55
|
+
tailOf.set(name, lines)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function printAppLine(name, text) {
|
|
59
|
+
process.stdout.write(`${color('2', `[${name}]`)} ${text}\n`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function preflight() {
|
|
63
|
+
const conflicts = []
|
|
64
|
+
for (const app of apps) {
|
|
65
|
+
if (await isPortUp(app.port)) conflicts.push(app)
|
|
66
|
+
}
|
|
67
|
+
if (conflicts.length === 0) return
|
|
68
|
+
console.error(color('31', '[dev] 启动前检查失败:以下端口已被占用(不区分占用者,一律不启动):'))
|
|
69
|
+
for (const app of conflicts) {
|
|
70
|
+
const posix = `lsof -nP -iTCP:${app.port} -sTCP:LISTEN`
|
|
71
|
+
const win = `netstat -ano | findstr :${app.port}`
|
|
72
|
+
console.error(
|
|
73
|
+
` ${app.name} → 端口 ${app.port} 已监听。查看占用:${process.platform === 'win32' ? win : posix};` +
|
|
74
|
+
`结束占用进程,或同步修改 ${app.dir}/package.json 的 dev/preview 端口与宿主 fulgurjs.config.ts 的 remotes dev 地址后重试`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
console.error('[dev] 本启动器只清理自己启动的进程,不会替你结束端口上的既有服务。')
|
|
78
|
+
process.exit(EXIT.portConflict)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function spawnApp(app) {
|
|
82
|
+
const child = spawn('pnpm', ['--dir', app.dir, 'run', 'dev'], {
|
|
83
|
+
cwd: templateRoot,
|
|
84
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
85
|
+
// POSIX:独立进程组,便于整组终止;Windows:shell 解析 pnpm.cmd,树杀交给 taskkill
|
|
86
|
+
detached: true,
|
|
87
|
+
shell: process.platform === 'win32',
|
|
88
|
+
})
|
|
89
|
+
children.set(app.name, child)
|
|
90
|
+
child.stdout.on('data', (c) => { recordOutput(app.name, c); String(c).split('\n').filter(Boolean).forEach((l) => printAppLine(app.name, l)) })
|
|
91
|
+
child.stderr.on('data', (c) => { recordOutput(app.name, c); String(c).split('\n').filter(Boolean).forEach((l) => printAppLine(app.name, l)) })
|
|
92
|
+
return child
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const exitedOnce = (child) =>
|
|
96
|
+
new Promise((resolve) => {
|
|
97
|
+
child.once('exit', (code, signal) => resolve({ code, signal }))
|
|
98
|
+
child.once('error', (e) => resolve({ code: -1, signal: null, error: e }))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
async function waitReady(app, child) {
|
|
102
|
+
const exited = exitedOnce(child)
|
|
103
|
+
const deadline = Date.now() + readyTimeoutMs
|
|
104
|
+
while (Date.now() < deadline) {
|
|
105
|
+
if (await isPortUp(app.port)) return { ok: true }
|
|
106
|
+
const done = await Promise.race([exited.then(() => 'exit'), new Promise((r) => setTimeout(() => r('tick'), 400))])
|
|
107
|
+
if (done === 'exit') return { ok: false, reason: `进程提前退出(启动命令失败——看下方日志尾部)` }
|
|
108
|
+
}
|
|
109
|
+
return { ok: false, reason: `${Math.round(readyTimeoutMs / 1000)}s 内端口 ${app.port} 未就绪(dev server 未监听)` }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function killTree(appName) {
|
|
113
|
+
const child = children.get(appName)
|
|
114
|
+
if (!child || child.exitCode !== null || child.signalCode !== null) return Promise.resolve()
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
const force = setTimeout(() => {
|
|
117
|
+
try { process.kill(process.platform === 'win32' ? child.pid : -child.pid, 'SIGKILL') } catch { /* 已退出 */ }
|
|
118
|
+
}, 3000).unref()
|
|
119
|
+
child.once('exit', () => { clearTimeout(force); resolve() })
|
|
120
|
+
try {
|
|
121
|
+
if (process.platform === 'win32') spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' })
|
|
122
|
+
else process.kill(-child.pid, 'SIGTERM')
|
|
123
|
+
} catch { clearTimeout(force); resolve() }
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function shutdown(exitCode, reason) {
|
|
128
|
+
if (shuttingDown) return
|
|
129
|
+
shuttingDown = true
|
|
130
|
+
if (reason) console.log(`[dev] ${reason}`)
|
|
131
|
+
console.log(`[dev] 正在停止本次启动的 ${children.size} 个应用…`)
|
|
132
|
+
await Promise.all([...children.keys()].map(killTree))
|
|
133
|
+
console.log('[dev] 已全部停止。')
|
|
134
|
+
process.exit(exitCode)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
process.on('SIGINT', () => void shutdown(EXIT.signal, '[dev] 收到 Ctrl+C'))
|
|
138
|
+
process.on('SIGTERM', () => void shutdown(143, '[dev] 收到 SIGTERM'))
|
|
139
|
+
|
|
140
|
+
await preflight()
|
|
141
|
+
|
|
142
|
+
console.log(`[dev] 模板 ${path.basename(templateRoot)}:按顺序启动 ${apps.map((a) => a.name).join(' → ')}`)
|
|
143
|
+
for (const app of apps) {
|
|
144
|
+
printAppLine(app.name, `启动(pnpm --dir ${app.dir} run dev),等待端口 ${app.port} 就绪…`)
|
|
145
|
+
const child = spawnApp(app)
|
|
146
|
+
const ready = await waitReady(app, child)
|
|
147
|
+
if (!ready.ok) {
|
|
148
|
+
console.error(color('31', `\n[dev] 启动失败:${app.name} —— ${ready.reason}`))
|
|
149
|
+
const tail = tailOf.get(app.name) ?? []
|
|
150
|
+
if (tail.length) {
|
|
151
|
+
console.error(color('31', `[dev] ${app.name} 日志尾部(最后 ${tail.length} 行):`))
|
|
152
|
+
for (const line of tail) console.error(` [${app.name}] ${line}`)
|
|
153
|
+
}
|
|
154
|
+
await Promise.all([...children.keys()].map(killTree))
|
|
155
|
+
console.error(`[dev] 已停止本次启动的全部进程(${children.size} 个)。修复上面的问题后重新运行 pnpm dev。`)
|
|
156
|
+
process.exit(EXIT.startupFailed)
|
|
157
|
+
}
|
|
158
|
+
printAppLine(app.name, `就绪 → http://localhost:${app.port}/`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(color('32', '\n[dev] 全部应用已就绪:'))
|
|
162
|
+
for (const app of [...apps].reverse()) {
|
|
163
|
+
console.log(` http://localhost:${app.port}/ ${app.name}`)
|
|
164
|
+
}
|
|
165
|
+
console.log('[dev] Ctrl+C 停止全部应用(只清理本次启动的进程)。dev server 已监听 ≠ 页面必然可用,验收请以浏览器实际加载为准。\n')
|
|
166
|
+
|
|
167
|
+
// 运行期任何子应用退出(崩溃/被外部杀掉)→ 整组停止,不让半残组合冒充正常运行
|
|
168
|
+
for (const [name, child] of children) {
|
|
169
|
+
child.once('exit', (code) => {
|
|
170
|
+
if (!shuttingDown) void shutdown(EXIT.startupFailed, `[dev] ${name} 意外退出(退出码 ${code})——停止其余应用`)
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
await new Promise(() => {})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 双向桥接 URL 同步(examples/templates/showcase)
|
|
2
|
+
|
|
3
|
+
`@fulgurjs/federation` 5.8.0「桥接 URL 同步」(`/bridge/router/*`,README §8.3)的双向旗舰演示:
|
|
4
|
+
两个方向的宿主 × 子应用各一套——
|
|
5
|
+
|
|
6
|
+
- **vue-host(:5334)× react-remote(:5333)**:Vue Router 4 宿主 + `createReactBridgeRouter` React 子应用
|
|
7
|
+
- **react-host(:5336)× vue-remote(:5335)**:React Router 7 data router 宿主(含 `useBlocker` 原生守卫)+ `connectVueBridgeRouter` Vue 子应用
|
|
8
|
+
|
|
9
|
+
宿主 URL 表达子应用内部位置:刷新直达、收藏分享、前进后退、宿主菜单深链、宿主导航唯一写历史的全部语义在本页可逐条操作验证。
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd examples/templates/showcase
|
|
15
|
+
pnpm install --frozen-lockfile
|
|
16
|
+
pnpm dev
|
|
17
|
+
# Vue 宿主:http://localhost:5334/br-react/orders
|
|
18
|
+
# React 宿主:http://localhost:5336/br-vue/orders
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
整个目录是一个独立 pnpm workspace;复制时包含四个应用及根配置和锁文件。`pnpm build` 构建全部应用;环境要求见[模板指南](../README.md)。
|
|
22
|
+
|
|
23
|
+
dev 冒烟自检(本机有全局代理时必须 `--noproxy '*'`):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl --noproxy '*' -s -o /dev/null -w '%{http_code}\n' http://localhost:5334/ # 200
|
|
27
|
+
curl --noproxy '*' -s -o /dev/null -w '%{http_code}\n' http://localhost:5336/ # 200
|
|
28
|
+
curl --noproxy '*' -s -o /dev/null -w '%{http_code}\n' http://localhost:5333/@fulgurjs-entry.js # 200
|
|
29
|
+
curl --noproxy '*' -s -o /dev/null -w '%{http_code}\n' http://localhost:5335/@fulgurjs-entry.js # 200
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 端口与工程表
|
|
33
|
+
|
|
34
|
+
| 工程 | 端口 | 角色 | 桥接前缀(basePath) | 关键路由库 |
|
|
35
|
+
|---|---|---|---|---|
|
|
36
|
+
| `react-remote` | 5333 | React 子应用(expose `./bridge`) | 被 `/br-react` 承载 | react-router-dom ^7(memory data router) |
|
|
37
|
+
| `vue-host` | 5334 | Vue 宿主(套 react-remote) | `/br-react` | vue-router ^4.5(history 模式) |
|
|
38
|
+
| `vue-remote` | 5335 | Vue 子应用(expose `./bridge`) | 被 `/br-vue` 承载 | vue-router ^4.5(createMemoryHistory) |
|
|
39
|
+
| `react-host` | 5336 | React 宿主(套 vue-remote) | `/br-vue` | react-router-dom ^7(createBrowserRouter) |
|
|
40
|
+
|
|
41
|
+
子应用页面集(两框架等价):工单列表 `/orders`(筛选 `q=xxx&page=n` 写入 query,中文可输入)、工单详情 `/orders/:id`(路径参数)、设置页 `/settings`(本地状态表单,刻意不进 URL)、锁定页 `/locked`(宿主守卫放行后才可见)、根路径 `/` 以 replace 规范化到 `/orders`(不产生额外历史条目)。
|
|
42
|
+
|
|
43
|
+
宿主页面集:首页 `/`、关于 `/about`、桥接演示页 `/br-react`(或 `/br-vue`)+ 顶部菜单「工单列表 / 设置」直达子应用深链。**子应用挂载在单条 catch-all 路由**(`/br-react/:pathMatch(.*)*` / `br-vue/*`)下,子应用内部导航不重挂宿主组件。
|
|
44
|
+
|
|
45
|
+
## URL 同步演示操作清单(逐条操作 + 预期结果)
|
|
46
|
+
|
|
47
|
+
以下操作在两个宿主上等价(路径前缀分别替换为 `/br-react` / `/br-vue`)。页面上部「URL 同步观测台」实时展示:宿主当前 URL(location.href)、子应用逻辑位置、history.length、子应用 mount 次数、最近 10 条导航事件(带来源标注)。
|
|
48
|
+
|
|
49
|
+
1. **深链直达**:地址栏直接打开 `http://localhost:5334/br-react/orders`
|
|
50
|
+
预期:子应用自动挂载并渲染工单列表;观测台「子应用逻辑位置」= `/orders`;事件日志出现「子应用 — mount #1」与「子应用初始化 — init 初始位置 → /orders(与宿主深链一致)」。
|
|
51
|
+
2. **根路径 replace 规范化**:打开 `http://localhost:5334/br-react`(或从首页点菜单「桥接演示页」)
|
|
52
|
+
预期:地址自动变为 `/br-react/orders`;事件日志出现「子应用初始化 — init 根路径规范化 replace → /orders(无新增历史条目)」;history.length 只增长 1(宿主那次 push;子应用重定向是 replace)。
|
|
53
|
+
3. **筛选写入 query(中文)**:在子应用工单列表输入「网络」→ 点「筛选(push ?q=&page=1)」
|
|
54
|
+
预期:宿主地址栏变为 `/orders?page=1&q=%E7%BD%91%E7%BB%9C`(网络 的 URL 编码);观测台子应用逻辑位置同步;事件日志新增「子应用 push/replace — push /orders?page=1&q=…」。
|
|
55
|
+
4. **分页**:清空筛选后点「下一页」
|
|
56
|
+
预期:URL 变为 `/orders?page=2`;再输入中文筛选并点「筛选」,页码回到 1 并带 `q=`。
|
|
57
|
+
5. **详情(路径参数)**:点任一「查看详情」
|
|
58
|
+
预期:URL 变为 `/orders/2?src=row` 形态;详情页回显工单 ID 与 query。
|
|
59
|
+
6. **返回列表 = 浏览器历史**:点「返回列表(go(-1))」
|
|
60
|
+
预期:回到第 3 步的 `/orders?page=1&q=网络`——筛选关键词与页码还在(组件状态从 query 还原);mount 次数仍为 1;history.length 不变(POP 不新增条目)。
|
|
61
|
+
7. **路由切换不重挂**:任意在 工单列表 ↔ 详情 ↔ 设置 之间往返
|
|
62
|
+
预期:观测台「子应用 mount 次数」恒为 1;子应用区域顶部的「本会话挂载次数」恒为 1。
|
|
63
|
+
8. **设置页本地状态跨路由保留**:设置页把昵称改为「李四」→ 菜单切去工单列表 → 再切回设置
|
|
64
|
+
预期:昵称仍是「李四」(子应用 store 未重建)。
|
|
65
|
+
9. **宿主菜单深链**:点顶部菜单「设置」
|
|
66
|
+
预期:宿主 `router.push`(React 宿主为 navigate)直达 `/br-vue/settings`,子应用内部同步跟随;事件日志「宿主菜单 — push /…」。
|
|
67
|
+
10. **子应用内 push/replace**:点「子应用 push /orders?q=演示&page=2」再点「子应用 replace /settings」
|
|
68
|
+
预期:push 后 URL 带中文 query;replace 后 URL 变 `/settings`;随后点「子应用 go(-1)」应落在 replace 前的条目(locked),证明 replace 没有新增历史。
|
|
69
|
+
11. **子应用 go(-1)/go(1)**:点「子应用 go(-1)」「子应用 go(1)」
|
|
70
|
+
预期:委托宿主浏览器历史前进后退;事件日志出现「子应用 go — go -1」与「浏览器前进后退 — …」两条。
|
|
71
|
+
12. **深链粘贴框**:向「深链粘贴框」粘贴完整 URL(默认值即带中文 query:`http://…/br-vue/orders?q=网络&page=2`,可改)→ 点「push 复现直达」
|
|
72
|
+
预期:等价于「刷新直达」的 push 侧动作,URL 与子应用位置一次到位。
|
|
73
|
+
13. **宿主守卫——取消**:子应用点「锁定页(触发宿主守卫)」→ 守卫横幅点「取消」
|
|
74
|
+
预期:URL / 历史 / 子应用位置全部保持不变(子应用自动回滚到工单列表);事件日志「宿主守卫 — 取消 → …」。Vue 宿主为异步 `beforeEach`,React 宿主为 `useBlocker` 原生守卫横幅。
|
|
75
|
+
14. **宿主守卫——继续**:再点「锁定页」→ 守卫横幅点「继续」
|
|
76
|
+
预期:URL 变为 `/locked`,子应用渲染锁定页;事件日志「宿主守卫 — 继续 → …」。
|
|
77
|
+
15. **刷新直达(终极验证)**:完成第 13/14 步任意深链后按 F5
|
|
78
|
+
预期:整页刷新后子应用直接恢复到同一内部页面——URL 即子应用位置。
|
|
79
|
+
|
|
80
|
+
## API ↔ 源码对照表
|
|
81
|
+
|
|
82
|
+
| 插件公开 API | 入口 | 本演示接线位置 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `createVueBridgeApp(spec, options)` | `@fulgurjs/federation/bridge/vue` | `vue-host/src/pages/BridgeReactPage.vue`(`<RemoteReactBridge :routing="routing" :app-props="appProps" />`) |
|
|
85
|
+
| `createVueBridgeNavigation(router)` | `@fulgurjs/federation/bridge/router/vue` | `vue-host/src/routing.ts`(模块级单例端口,main.ts 组装后 `initBridgeRouting(router)`) |
|
|
86
|
+
| `connectVueBridgeRouter(routing, router, { signal })` → `await conn.ready` 后 `app.use(router)` | `@fulgurjs/federation/bridge/router/vue` | `vue-remote/src/bridge.ts`(异步工厂:初始 push 落定后再 install,顺序不能反) |
|
|
87
|
+
| `createReactBridgeApp(spec, options)` | `@fulgurjs/federation/bridge/react` | `react-host/src/pages/BridgeVuePage.tsx`(`<RemoteVueBridge routing={routing} appProps={appProps} />`) |
|
|
88
|
+
| `createReactBridgeNavigation(router, { canNavigate?, basename? })`(仅 data router) | `@fulgurjs/federation/bridge/router/react` | `react-host/src/routing.ts`(`createBrowserRouter` 实例组装后初始化) |
|
|
89
|
+
| `createReactBridgeRouter(routing, routes, { signal })` → `{ element }` 直接作为工厂返回值 | `@fulgurjs/federation/bridge/router/react` | `react-remote/src/bridge.tsx`(`/` 经 loader `redirect('/orders')` 以 replace 规范化) |
|
|
90
|
+
| `defineBridgeApp(factory, { routing: true })`(协议声明,缺失即 MFU-031) | `@fulgurjs/federation/runtime`(Vue)/ `@fulgurjs/federation/react`(React) | `vue-remote/src/bridge.ts`、`react-remote/src/bridge.tsx`(工厂内校验 `ctx?.routing`,`ctx.signal` 传入接线自动 dispose) |
|
|
91
|
+
| `BridgeHostRouting`(`{ basePath, navigation }` 组件 prop,独立控制通道不进 appProps) | 类型面见 README §8.3 | `vue-host/src/routing.ts`、`react-host/src/routing.ts`(basePath 常量 `BRIDGE_BASE_PATH`) |
|
|
92
|
+
| `BridgeChildRoute` / `BridgeLocation`(通道与位置契约) | `/bridge/router/*` | 由插件随 mount 第三参数注入子应用;两端仓库源码 `packages/plugin/src/bridge-router-core.ts` |
|
|
93
|
+
|
|
94
|
+
## 配置要点(复刻到其他工程时必读)
|
|
95
|
+
|
|
96
|
+
- **宿主 catch-all**:子应用前缀必须挂单条 catch-all 路由(`/br-react/:pathMatch(.*)*` / `br-vue/*`)渲染同一宿主组件,否则子应用内部导航会重挂宿主组件(子应用 mount 次数 > 1)。
|
|
97
|
+
- **双框架安装合同**:桥接宿主 shared 必须 vue / react / react-dom 三键全 `singleton`(见两个宿主的 `fulgurjs.config.ts`)。
|
|
98
|
+
- **vue-remote 的 vue-router 双实例防护**:`resolve.dedupe: ['vue', 'vue-router']` + `optimizeDeps.exclude: ['vue-router']`(vue-router 纯 ESM,exclude 后其 vue 导入被插件 dev 改写到 shared 门面;同 `fixtures/remote-a`)。
|
|
99
|
+
- **react-remote 的 react-router-dom 必须正常进预构建**(同 e2e 实测的 `fixtures/remote-react`:不 exclude、不 alias):其传递依赖 cookie 是 CJS-only 包,exclude 后浏览器报 `does not provide an export named 'parse'`;预构建内的 react 导入不会双实例——插件 dev 会向预构建注入 shared 键外部化 resolver(`fulgurjs:optimize-shared-external`)。
|
|
100
|
+
- **dev 类型生成**:宿主 dev 启动时插件生成 `src/fulgurjs/types/<remote>.d.ts`(零配置轨,ambient `any`)与 `src/fulgurjs/types/<remote>.d/`(精确轨,需在 tsconfig 配 `paths` 才启用)。跨框架精确轨(React 宿主 tsc 消费 Vue 远程源码)需排除精确轨目录,见 `react-host/tsconfig.json` 的 `exclude: ["src/fulgurjs/types/vue-remote.d"]`。
|
|
101
|
+
- **守卫与 go 的交互**:守卫按目标路径拦截(不区分触发方式),子应用 `go(-1)` 回到受守卫页面同样会弹确认;Vue 宿主守卫在任何新导航开始时自动作废旧待确认(`vue-host/src/guard.ts` 的 `invalidatePending`),避免确认横幅悬挂。
|
|
102
|
+
|
|
103
|
+
## 构建 / 类型检查
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# 4 个工程均独立通过(在各自目录执行)
|
|
107
|
+
pnpm run typecheck # React 工程 tsc --noEmit;Vue 工程 vue-tsc --noEmit
|
|
108
|
+
pnpm run build
|
|
109
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fulgurjs-template-showcase",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev:vue-remote": "pnpm --dir vue-remote dev",
|
|
7
|
+
"dev:react-remote": "pnpm --dir react-remote dev",
|
|
8
|
+
"dev:vue-host": "pnpm --dir vue-host dev",
|
|
9
|
+
"dev:react-host": "pnpm --dir react-host dev",
|
|
10
|
+
"dev": "node scripts/dev.mjs",
|
|
11
|
+
"build": "pnpm -r build"
|
|
12
|
+
}
|
|
13
|
+
}
|