@fulgurjs/federation 6.8.7 → 6.9.0

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 (139) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.en.md +3 -0
  3. package/README.md +3 -0
  4. package/dist/application-core-BuhXQmLF.d.cts +16 -0
  5. package/dist/application-core-DWI3eKme.d.ts +16 -0
  6. package/dist/bridge-app-react.cjs +177 -9
  7. package/dist/bridge-app-react.d.cts +15 -3
  8. package/dist/bridge-app-react.d.ts +15 -3
  9. package/dist/bridge-app-react.js +101 -9
  10. package/dist/bridge-app-vue.cjs +138 -12
  11. package/dist/bridge-app-vue.d.cts +20 -5
  12. package/dist/bridge-app-vue.d.ts +20 -5
  13. package/dist/bridge-app-vue.js +62 -12
  14. package/dist/bridge-host-react.d.cts +1 -1
  15. package/dist/bridge-host-react.d.ts +1 -1
  16. package/dist/bridge-host-vue.d.cts +1 -1
  17. package/dist/bridge-host-vue.d.ts +1 -1
  18. package/dist/chunk-6OB47FSR.js +81 -0
  19. package/dist/cli.js +349 -23
  20. package/dist/index.cjs +7 -6
  21. package/dist/index.js +7 -6
  22. package/dist/react-adapter.d.cts +1 -1
  23. package/dist/react-adapter.d.ts +1 -1
  24. package/dist/react.d.ts +29 -4
  25. package/dist/react.js +1 -0
  26. package/dist/{remote-types-xf1B6_pK.d.ts → remote-types-COmBhjRc.d.ts} +1 -1
  27. package/dist/{remote-types-58hdbx6x.d.cts → remote-types-CStGOJwu.d.cts} +1 -1
  28. package/dist/runtime.js +1 -1
  29. package/dist/vue-adapter.d.cts +1 -1
  30. package/dist/vue-adapter.d.ts +1 -1
  31. package/dist/vue-store.cjs +40 -0
  32. package/dist/vue-store.d.cts +4 -0
  33. package/dist/vue-store.d.ts +4 -0
  34. package/dist/vue-store.js +15 -0
  35. package/dist/vue.d.ts +37 -6
  36. package/dist/vue.js +2 -0
  37. package/docs/en/reference/api.md +38 -2
  38. package/docs/en/reference/types.md +27 -0
  39. package/docs/zh/reference/api.md +46 -2
  40. package/docs/zh/reference/types.md +27 -0
  41. package/examples/templates/README.md +3 -0
  42. package/examples/templates/react-host-vue-remote/README.md +24 -0
  43. package/examples/templates/react-host-vue-remote/package.json +2 -1
  44. package/examples/templates/react-host-vue-remote/pnpm-lock.yaml +7 -7
  45. package/examples/templates/react-host-vue-remote/pnpm-workspace.yaml +1 -1
  46. package/examples/templates/react-host-vue-remote/react-host/package.json +1 -1
  47. package/examples/templates/react-host-vue-remote/react-host/src/application.tsx +62 -0
  48. package/examples/templates/react-host-vue-remote/react-host/src/main.tsx +2 -62
  49. package/examples/templates/react-host-vue-remote/scripts/prod.mjs +101 -0
  50. package/examples/templates/react-host-vue-remote/vue-remote/README.md +17 -16
  51. package/examples/templates/react-host-vue-remote/vue-remote/fulgurjs.config.ts +2 -2
  52. package/examples/templates/react-host-vue-remote/vue-remote/package.json +1 -1
  53. package/examples/templates/react-host-vue-remote/vue-remote/src/AboutPage.vue +1 -0
  54. package/examples/templates/react-host-vue-remote/vue-remote/src/App.vue +15 -5
  55. package/examples/templates/react-host-vue-remote/vue-remote/src/HomePage.vue +1 -0
  56. package/examples/templates/react-host-vue-remote/vue-remote/src/application.ts +19 -0
  57. package/examples/templates/react-host-vue-remote/vue-remote/src/main.ts +2 -15
  58. package/examples/templates/react-react/README.md +25 -1
  59. package/examples/templates/react-react/host/package.json +1 -1
  60. package/examples/templates/react-react/host/src/application.tsx +33 -0
  61. package/examples/templates/react-react/host/src/main.tsx +3 -32
  62. package/examples/templates/react-react/package.json +2 -1
  63. package/examples/templates/react-react/pnpm-lock.yaml +7 -7
  64. package/examples/templates/react-react/pnpm-workspace.yaml +1 -1
  65. package/examples/templates/react-react/remote/package.json +1 -1
  66. package/examples/templates/react-react/remote/src/application.tsx +11 -0
  67. package/examples/templates/react-react/remote/src/main.tsx +3 -10
  68. package/examples/templates/react-react/scripts/prod.mjs +101 -0
  69. package/examples/templates/showcase/README.md +35 -7
  70. package/examples/templates/showcase/package.json +2 -1
  71. package/examples/templates/showcase/pnpm-lock.yaml +11 -11
  72. package/examples/templates/showcase/pnpm-workspace.yaml +1 -1
  73. package/examples/templates/showcase/react-host/package.json +1 -1
  74. package/examples/templates/showcase/react-host/src/application.tsx +28 -0
  75. package/examples/templates/showcase/react-host/src/demo-log.ts +5 -8
  76. package/examples/templates/showcase/react-host/src/main.tsx +2 -33
  77. package/examples/templates/showcase/react-host/src/routing.ts +2 -2
  78. package/examples/templates/showcase/react-remote/fulgurjs.config.ts +1 -1
  79. package/examples/templates/showcase/react-remote/package.json +1 -1
  80. package/examples/templates/showcase/react-remote/src/ChildLayout.tsx +3 -2
  81. package/examples/templates/showcase/react-remote/src/{bridge.tsx → application.tsx} +21 -21
  82. package/examples/templates/showcase/react-remote/src/child-bus.ts +23 -23
  83. package/examples/templates/showcase/react-remote/src/main.tsx +2 -18
  84. package/examples/templates/showcase/react-remote/src/pages/Locked.tsx +2 -1
  85. package/examples/templates/showcase/react-remote/src/pages/OrderDetail.tsx +2 -1
  86. package/examples/templates/showcase/react-remote/src/pages/OrderList.tsx +2 -1
  87. package/examples/templates/showcase/scripts/prod.mjs +101 -0
  88. package/examples/templates/showcase/vue-host/package.json +1 -1
  89. package/examples/templates/showcase/vue-host/src/application.ts +28 -0
  90. package/examples/templates/showcase/vue-host/src/demo-log.ts +4 -7
  91. package/examples/templates/showcase/vue-host/src/main.ts +2 -30
  92. package/examples/templates/showcase/vue-host/src/routing.ts +2 -2
  93. package/examples/templates/showcase/vue-remote/fulgurjs.config.ts +1 -1
  94. package/examples/templates/showcase/vue-remote/package.json +1 -1
  95. package/examples/templates/showcase/vue-remote/src/ChildLayout.vue +3 -2
  96. package/examples/templates/showcase/vue-remote/src/{bridge.ts → application.ts} +10 -13
  97. package/examples/templates/showcase/vue-remote/src/child-bus.ts +23 -23
  98. package/examples/templates/showcase/vue-remote/src/main.ts +2 -16
  99. package/examples/templates/showcase/vue-remote/src/pages/Locked.vue +2 -1
  100. package/examples/templates/showcase/vue-remote/src/pages/OrderDetail.vue +2 -1
  101. package/examples/templates/showcase/vue-remote/src/pages/OrderList.vue +2 -1
  102. package/examples/templates/vue-host-react-remote/README.md +24 -0
  103. package/examples/templates/vue-host-react-remote/package.json +2 -1
  104. package/examples/templates/vue-host-react-remote/pnpm-lock.yaml +7 -7
  105. package/examples/templates/vue-host-react-remote/pnpm-workspace.yaml +1 -1
  106. package/examples/templates/vue-host-react-remote/react-remote/README.md +17 -17
  107. package/examples/templates/vue-host-react-remote/react-remote/fulgurjs.config.ts +2 -2
  108. package/examples/templates/vue-host-react-remote/react-remote/package.json +1 -1
  109. package/examples/templates/vue-host-react-remote/react-remote/src/{bridge.tsx → application.tsx} +13 -24
  110. package/examples/templates/vue-host-react-remote/react-remote/src/main.tsx +2 -7
  111. package/examples/templates/vue-host-react-remote/scripts/prod.mjs +101 -0
  112. package/examples/templates/vue-host-react-remote/vue-host/package.json +1 -1
  113. package/examples/templates/vue-host-react-remote/vue-host/src/application.ts +5 -0
  114. package/examples/templates/vue-host-react-remote/vue-host/src/main.ts +2 -4
  115. package/examples/templates/vue-vue/README.md +25 -1
  116. package/examples/templates/vue-vue/host/package.json +1 -1
  117. package/examples/templates/vue-vue/host/src/application.ts +54 -0
  118. package/examples/templates/vue-vue/host/src/main.ts +3 -50
  119. package/examples/templates/vue-vue/package.json +2 -1
  120. package/examples/templates/vue-vue/pnpm-lock.yaml +7 -7
  121. package/examples/templates/vue-vue/pnpm-workspace.yaml +1 -1
  122. package/examples/templates/vue-vue/remote/package.json +1 -1
  123. package/examples/templates/vue-vue/remote/src/application.ts +6 -0
  124. package/examples/templates/vue-vue/remote/src/main.ts +3 -5
  125. package/examples/templates/vue-vue/scripts/prod.mjs +101 -0
  126. package/package.json +14 -9
  127. package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote/metadata.json +0 -15
  128. package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote/modules.d.ts +0 -14
  129. package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote/registry.d.ts +0 -10
  130. package/examples/templates/react-host-vue-remote/vue-remote/src/bridge.ts +0 -57
  131. package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote/metadata.json +0 -16
  132. package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote/modules.d.ts +0 -71
  133. package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote/registry.d.ts +0 -10
  134. package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote/metadata.json +0 -16
  135. package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote/modules.d.ts +0 -79
  136. package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote/registry.d.ts +0 -10
  137. package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote/metadata.json +0 -16
  138. package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote/modules.d.ts +0 -10
  139. package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote/registry.d.ts +0 -10
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /** Standalone template production launcher. Uses only Node and the existing dev.config.json. */
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import http from 'node:http'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
9
+ const { apps } = JSON.parse(fs.readFileSync(path.join(root, 'scripts/dev.config.json'), 'utf8'))
10
+ if (!Array.isArray(apps) || !apps.length) throw new Error('dev.config.json 未登记应用。')
11
+
12
+ const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.woff': 'font/woff', '.woff2': 'font/woff2', '.map': 'application/json' }
13
+ const inside = (file, directory) => file === directory || file.startsWith(directory + path.sep)
14
+ const mounts = apps.map(app => {
15
+ const dir = path.resolve(root, app.dir, 'dist')
16
+ if (!inside(dir, root)) throw new Error(`${app.name}: 构建目录超出模板。`)
17
+ const index = path.join(dir, 'index.html')
18
+ if (!fs.existsSync(index)) throw new Error(`${app.name}: 缺少 dist/index.html,请先 pnpm build。`)
19
+ const html = fs.readFileSync(index, 'utf8')
20
+ const tags = [...html.matchAll(/<script\b[^>]*>/gi)]
21
+ const tag = tags.find(item => /\btype=["']module["']/i.test(item[0]))?.[0]
22
+ const src = tag?.match(/\bsrc=["']([^"']+)["']/i)?.[1]
23
+ if (!src || /^(?:https?:)?\/\//.test(src)) throw new Error(`${app.name}: 无可本地挂载的模块入口。`)
24
+ // Resolve the actual emitted file suffix, rather than guessing base from an app name.
25
+ const pathname = decodeURIComponent(new URL(src, 'http://template.local/').pathname)
26
+ const parts = pathname.split('/').filter(Boolean)
27
+ const bases = []
28
+ for (let i = 0; i < parts.length; i++) {
29
+ const file = path.resolve(dir, ...parts.slice(i))
30
+ if (inside(file, dir) && fs.existsSync(file) && fs.statSync(file).isFile()) bases.push('/' + parts.slice(0, i).join('/') + (i ? '/' : ''))
31
+ }
32
+ if (bases.length !== 1) throw new Error(`${app.name}: 无法唯一对应构建入口 ${src};检查 Vite base 和干净构建输出。`)
33
+ return { ...app, dir, prefix: bases[0] }
34
+ })
35
+ const hosts = mounts.filter(app => app.host)
36
+ if (!hosts.length) throw new Error('dev.config.json 未登记 host:true 的宿主。')
37
+ const ports = new Set()
38
+ const servers = []
39
+ let stopping = false
40
+ function stop(code = 0) {
41
+ if (stopping) return
42
+ stopping = true
43
+ for (const server of servers) server.close()
44
+ const timer = setTimeout(() => {
45
+ for (const server of servers) server.closeAllConnections?.()
46
+ process.exit(code)
47
+ }, 1000)
48
+ timer.unref()
49
+ process.exitCode = code
50
+ }
51
+ process.once('SIGINT', () => stop(0))
52
+ process.once('SIGTERM', () => stop(0))
53
+ process.once('uncaughtException', error => { console.error(error); stop(1) })
54
+
55
+ for (const host of hosts) {
56
+ if (!Number.isInteger(host.port) || host.port < 1 || host.port > 65535 || ports.has(host.port)) throw new Error(`无效或重复宿主端口:${host.port}`)
57
+ ports.add(host.port)
58
+ const site = [host, ...mounts.filter(app => !app.host)].sort((a, b) => b.prefix.length - a.prefix.length)
59
+ const prefixes = new Set()
60
+ for (const app of site) {
61
+ if (prefixes.has(app.prefix)) throw new Error(`${host.name}: 构建 base ${app.prefix} 冲突;宿主和远程不能部署在同一路径。`)
62
+ prefixes.add(app.prefix)
63
+ }
64
+ const server = http.createServer((req, res) => {
65
+ try {
66
+ const url = new URL(req.url, `http://localhost:${host.port}`)
67
+ if (url.pathname === '/' && host.prefix !== '/') {
68
+ res.writeHead(302, { location: host.prefix, 'cache-control': 'no-cache' })
69
+ return res.end()
70
+ }
71
+ const app = site.find(item => url.pathname === item.prefix.slice(0, -1) || url.pathname.startsWith(item.prefix))
72
+ if (!app) { res.writeHead(404); return res.end('未登记此部署路径') }
73
+ const relative = decodeURIComponent(url.pathname.slice(app.prefix.length)) || 'index.html'
74
+ let file = path.resolve(app.dir, relative)
75
+ if (!inside(file, app.dir)) { res.writeHead(403); return res.end() }
76
+ if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) {
77
+ if (path.extname(relative)) { res.writeHead(404); return res.end('资源不存在;不会回退成HTML') }
78
+ file = path.join(app.dir, 'index.html')
79
+ }
80
+ const stable = /(?:^|\/)(?:index\.html|fulgurjs-(?:remoteEntry\.js|manifest\.json)|_app\.config\.js)$/.test(file)
81
+ res.writeHead(200, {
82
+ 'content-type': mime[path.extname(file)] ?? 'application/octet-stream',
83
+ 'cache-control': stable ? 'no-cache' : 'public, max-age=31536000, immutable',
84
+ 'access-control-allow-origin': '*',
85
+ })
86
+ res.end(fs.readFileSync(file))
87
+ } catch (error) {
88
+ console.error(`生产请求失败:${error.message}`)
89
+ if (!res.headersSent) res.writeHead(400)
90
+ res.end('无效请求或资源不可读')
91
+ }
92
+ })
93
+ servers.push(server)
94
+ server.on('error', error => {
95
+ console.error(`${host.name}:${host.port} 启动失败:${error.message};停止本次全部生产服务器,不会按端口杀已有进程。`)
96
+ stop(1)
97
+ })
98
+ server.listen(host.port, '127.0.0.1', () => {
99
+ if (!stopping) console.log(`生产宿主 ${host.name}: http://localhost:${host.port}${host.prefix}(仅dist,无开发服务器)`)
100
+ })
101
+ }
@@ -11,7 +11,7 @@
11
11
  "typecheck": "vue-tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@fulgurjs/federation": "6.8.5",
14
+ "@fulgurjs/federation": "6.9.0",
15
15
  "react": "^19.1.0",
16
16
  "react-dom": "^19.1.0",
17
17
  "vue": "^3.5.13"
@@ -0,0 +1,5 @@
1
+ import { createApp } from 'vue'
2
+ import { defineApplication } from '@fulgurjs/federation/vue'
3
+ import App from './App.vue'
4
+
5
+ export default defineApplication(() => createApp(App))
@@ -1,4 +1,2 @@
1
- import { createApp } from 'vue'
2
- import App from './App.vue'
3
-
4
- createApp(App).mount('#app')
1
+ import application from './application'
2
+ void application.start('#app').catch(console.error)
@@ -1,5 +1,8 @@
1
1
  # Vue 联邦示例:宿主 + 远程(可直接复制运行)
2
2
 
3
+ > 本目录是 6.9.0 源码候选:已改用 defineApplication,尚未测试和发布。源码依赖已锁定 6.9.0;正式发布前不要拿旧 registry 包运行这份代码。验收先使用本次候选 tarball,发布后再按下列冻结安装命令验证正式包。
4
+
5
+
3
6
  本目录是一对完整、可独立安装运行的 Vue 3 模块联邦演示:
4
7
 
5
8
  | 目录 | 角色 | 端口 | 说明 |
@@ -7,7 +10,7 @@
7
10
  | [`remote/`](./remote/) | **Vue 远程**(被消费方) | 5213 | 暴露可点击按钮组件、普通 TS 工具模块、联邦首页与参数详情页 |
8
11
  | [`host/`](./host/) | **Vue 宿主**(消费方) | 5214 | 消费 vue-remote 的组件/模块/页面,提供导航与懒加载 |
9
12
 
10
- - 包管理器为 **pnpm**;复制整个模板目录,包含 workspace 和锁文件。插件使用 registry 正式精确版本。
13
+ - 包管理器为 **pnpm**;复制整个模板目录,包含 workspace 和锁文件。插件精确依赖本轮 6.9.0;正式包是否可用见本页顶部状态。
11
14
 
12
15
  ## 最快开始(从干净拷贝)
13
16
 
@@ -105,3 +108,24 @@ server {
105
108
  - 改remote(默认 5213):① `remote/package.json` 的 `dev` 与 `preview` 两个脚本的 `--port 5213`;② `host/fulgurjs.config.ts` 里 `remotes['vue-remote'].dev` 的 `http://localhost:5213`;③ 根 `scripts/dev.config.json` 里 remote 的 `port`(**必改**:启动器预检/探活都读它);④ 本 README 顶部端口表。
106
109
  - 改host(默认 5214):① `host/package.json` 的 `dev`/`preview` `--port 5214`;② `scripts/dev.config.json`;③ 本 README 端口表。
107
110
  - 生产部署地址(`prod`,如 `/remote`)是站点路径,与 dev 端口无关,改端口不要动它。改名(容器名)不建议:还需同步 spec 前缀与页面表。
111
+
112
+ ## 一键运行生产产物
113
+
114
+ ```bash
115
+ pnpm build
116
+ pnpm prod
117
+ ```
118
+
119
+ `prod` 只服务本次 dist,不启动远程开发服务器。它复用 scripts/dev.config.json 的宿主端口,从实际构建入口推导 base,并把远程静态产物挂在同源子路径。无需为本地复现手写 Nginx;部署真实服务器时仍按部署指南配置。多宿主模板分别启动各宿主地址,不把两个宿主的根资源混在一起。先停止 dev,端口占用会失败并停止本次已起服务;不会关闭已有占用进程。Ctrl+C 关闭本次生产服务。
120
+
121
+ ## 插件代码阅读地图
122
+
123
+ | 文件 | 职责 |
124
+ |---|---|
125
+ | `host/src/application.ts` | 本应用唯一装配;main 只负责调用 start |
126
+ | `remote/src/application.ts` | 本应用唯一装配;main 只负责调用 start |
127
+ | `host/fulgurjs.config.ts` | 本应用的远程地址、exposes、实际共享依赖 |
128
+ | `remote/fulgurjs.config.ts` | 本应用的远程地址、exposes、实际共享依赖 |
129
+ | `scripts/dev.mjs` / `scripts/prod.mjs` | 统一开发/生产启动器;业务接入不复制它们到应用源码 |
130
+
131
+ `application` 是新增的插件装配文件,`main` 是已有入口接线;页面组件、业务路由、权限和数据不是每个项目都必写的联邦配置。跨框架基础组合不逐页登记子应用菜单;逐页远程页面模板的页面表仅服务它所展示的逐页场景。`src/fulgurjs/types/` 是插件生成目录,按远程实际版本同步,不维护人工伪造的声明或旧缓存。
@@ -11,7 +11,7 @@
11
11
  "typecheck": "vue-tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@fulgurjs/federation": "6.8.5",
14
+ "@fulgurjs/federation": "6.9.0",
15
15
  "vue": "^3.5.13",
16
16
  "vue-router": "^4.5.0"
17
17
  },
@@ -0,0 +1,54 @@
1
+ import { createApp, h } from 'vue'
2
+ import { createRouter, createWebHistory, useRoute } from 'vue-router'
3
+ import { defineApplication, createHostPages } from '@fulgurjs/federation/vue'
4
+ import App from './App.vue'
5
+ import HomePage from './pages/HomePage.vue'
6
+ import UtilsDemo from './pages/UtilsDemo.vue'
7
+ import { pages, remotePrefixes } from '../fulgurjs.config.ts'
8
+
9
+ // 页面适配器:URL 解析、最长前缀远程归属、异步组件缓存、
10
+ // 加载/错误占位(含「重试加载 / 刷新页面重试」)全部由插件完成。
11
+ const hostPages = createHostPages({
12
+ pages,
13
+ remotePrefixes,
14
+ })
15
+
16
+ // 远程页面 Router 记录从页面表派生(route + spec 唯一来源 = fulgurjs.config.ts 的 pages;
17
+ // 业务自有路由仍归本 Router 管理——业务 Router 与联邦页面映射是两类事实)。
18
+ // 远程名按最长前缀从 remotePrefixes 解析(与运行时归属一致);params + query 全量透传给
19
+ // 远程页面组件(响应式:路由变化即重渲染)。
20
+ const remoteNameOf = (route: string): string => {
21
+ const prefix = Object.keys(remotePrefixes)
22
+ .filter((p) => route === p || route.startsWith(p + '/') || route.startsWith(p))
23
+ .sort((a, b) => b.length - a.length)[0]
24
+ const name = prefix ? remotePrefixes[prefix] : undefined
25
+ if (!name) throw new Error(`页面 ${route} 不匹配任何 remotePrefixes 前缀——请核对 fulgurjs.config.ts`)
26
+ return name
27
+ }
28
+ const remoteRoutes = pages.map((page) => ({
29
+ path: page.route,
30
+ component: {
31
+ setup() {
32
+ const route = useRoute()
33
+ return () =>
34
+ h(hostPages.component(`${remoteNameOf(page.route)}/${page.spec}`), {
35
+ ...route.params,
36
+ ...route.query,
37
+ })
38
+ },
39
+ },
40
+ }))
41
+
42
+ // 应用装配只写一次;每次 start 创建自己的 router。
43
+ export default defineApplication(() => {
44
+ const router = createRouter({
45
+ history: createWebHistory(import.meta.env.BASE_URL),
46
+ routes: [
47
+ { path: '/', component: HomePage },
48
+ { path: '/utils-demo', component: UtilsDemo },
49
+ ...remoteRoutes,
50
+ ],
51
+ })
52
+
53
+ return { app: createApp(App), router }
54
+ })
@@ -1,51 +1,4 @@
1
- import { createApp, h } from 'vue'
2
- import { createRouter, createWebHistory, useRoute } from 'vue-router'
3
- import { createHostPages } from '@fulgurjs/federation/vue'
4
- import App from './App.vue'
5
- import HomePage from './pages/HomePage.vue'
6
- import UtilsDemo from './pages/UtilsDemo.vue'
7
- import { pages, remotePrefixes } from '../fulgurjs.config.ts'
1
+ import application from './application'
8
2
 
9
- // 页面适配器:URL 解析、最长前缀远程归属、异步组件缓存、
10
- // 加载/错误占位(含「重试加载 / 刷新页面重试」)全部由插件完成。
11
- const hostPages = createHostPages({
12
- pages,
13
- remotePrefixes,
14
- })
15
-
16
- // 远程页面 Router 记录从页面表派生(route + spec 唯一来源 = fulgurjs.config.ts 的 pages;
17
- // 业务自有路由仍归本 Router 管理——业务 Router 与联邦页面映射是两类事实)。
18
- // 远程名按最长前缀从 remotePrefixes 解析(与运行时归属一致);params + query 全量透传给
19
- // 远程页面组件(响应式:路由变化即重渲染)。
20
- const remoteNameOf = (route: string): string => {
21
- const prefix = Object.keys(remotePrefixes)
22
- .filter((p) => route === p || route.startsWith(p + '/') || route.startsWith(p))
23
- .sort((a, b) => b.length - a.length)[0]
24
- const name = prefix ? remotePrefixes[prefix] : undefined
25
- if (!name) throw new Error(`页面 ${route} 不匹配任何 remotePrefixes 前缀——请核对 fulgurjs.config.ts`)
26
- return name
27
- }
28
- const remoteRoutes = pages.map((page) => ({
29
- path: page.route,
30
- component: {
31
- setup() {
32
- const route = useRoute()
33
- return () =>
34
- h(hostPages.component(`${remoteNameOf(page.route)}/${page.spec}`), {
35
- ...route.params,
36
- ...route.query,
37
- })
38
- },
39
- },
40
- }))
41
-
42
- const router = createRouter({
43
- history: createWebHistory(),
44
- routes: [
45
- { path: '/', component: HomePage },
46
- { path: '/utils-demo', component: UtilsDemo },
47
- ...remoteRoutes,
48
- ],
49
- })
50
-
51
- createApp(App).use(router).mount('#app')
3
+ // 独立入口只负责启动;初始化写在 application 中。
4
+ void application.start('#app').catch(console.error)
@@ -6,6 +6,7 @@
6
6
  "dev:remote": "pnpm --dir remote dev",
7
7
  "dev:host": "pnpm --dir host dev",
8
8
  "dev": "node scripts/dev.mjs",
9
- "build": "pnpm --dir remote build && pnpm --dir host build"
9
+ "build": "pnpm --dir remote build && pnpm --dir host build",
10
+ "prod": "node scripts/prod.mjs"
10
11
  }
11
12
  }
@@ -11,8 +11,8 @@ importers:
11
11
  host:
12
12
  dependencies:
13
13
  '@fulgurjs/federation':
14
- specifier: 6.8.5
15
- version: 6.8.5(vite@6.4.4)(vue-router@4.6.4(vue@3.5.43(typescript@5.9.3)))(vue@3.5.43(typescript@5.9.3))
14
+ specifier: 6.9.0
15
+ version: 6.9.0(vite@6.4.4)(vue-router@4.6.4(vue@3.5.43(typescript@5.9.3)))(vue@3.5.43(typescript@5.9.3))
16
16
  vue:
17
17
  specifier: ^3.5.13
18
18
  version: 3.5.43(typescript@5.9.3)
@@ -36,8 +36,8 @@ importers:
36
36
  remote:
37
37
  dependencies:
38
38
  '@fulgurjs/federation':
39
- specifier: 6.8.5
40
- version: 6.8.5(vite@6.4.4)(vue-router@4.6.4(vue@3.5.43(typescript@5.9.3)))(vue@3.5.43(typescript@5.9.3))
39
+ specifier: 6.9.0
40
+ version: 6.9.0(vite@6.4.4)(vue-router@4.6.4(vue@3.5.43(typescript@5.9.3)))(vue@3.5.43(typescript@5.9.3))
41
41
  vue:
42
42
  specifier: ^3.5.13
43
43
  version: 3.5.43(typescript@5.9.3)
@@ -386,8 +386,8 @@ packages:
386
386
  cpu: [x64]
387
387
  os: [win32]
388
388
 
389
- '@fulgurjs/federation@6.8.5':
390
- resolution: {integrity: sha512-RsaN4n2ZgGBtI9zVQxDclS3QxYVKAZiCGipXR3umreAg651tIYCmtqkVjqPELSCPW9tg/rYQvzzs/SOV6ZiQfg==}
389
+ '@fulgurjs/federation@6.9.0':
390
+ resolution: {tarball: https://registry.npmjs.org/@fulgurjs/federation/-/federation-6.9.0.tgz}
391
391
  engines: {node: '>=18'}
392
392
  hasBin: true
393
393
  peerDependencies:
@@ -955,7 +955,7 @@ snapshots:
955
955
  '@esbuild/win32-x64@0.27.7':
956
956
  optional: true
957
957
 
958
- '@fulgurjs/federation@6.8.5(vite@6.4.4)(vue-router@4.6.4(vue@3.5.43(typescript@5.9.3)))(vue@3.5.43(typescript@5.9.3))':
958
+ '@fulgurjs/federation@6.9.0(vite@6.4.4)(vue-router@4.6.4(vue@3.5.43(typescript@5.9.3)))(vue@3.5.43(typescript@5.9.3))':
959
959
  dependencies:
960
960
  es-module-lexer: 1.7.0
961
961
  esbuild: 0.27.7
@@ -2,7 +2,7 @@ packages:
2
2
  - host
3
3
  - remote
4
4
  minimumReleaseAgeExclude:
5
- - '@fulgurjs/federation@6.8.5'
5
+ - '@fulgurjs/federation@6.9.0'
6
6
  # pnpm >=12 构建脚本批准(12 起弃用 onlyBuiltDependencies;pnpm 9-11 忽略本键:
7
7
  # esbuild >=0.16 二进制经 optionalDependencies 平台包分发,postinstall 仅校验,不阻塞安装)
8
8
  allowBuilds:
@@ -11,7 +11,7 @@
11
11
  "typecheck": "vue-tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@fulgurjs/federation": "6.8.5",
14
+ "@fulgurjs/federation": "6.9.0",
15
15
  "vue": "^3.5.13"
16
16
  },
17
17
  "devDependencies": {
@@ -0,0 +1,6 @@
1
+ import { createApp } from 'vue'
2
+ import { defineApplication } from '@fulgurjs/federation/vue'
3
+ import App from './App.vue'
4
+
5
+ // 普通组件远程不需要额外桥接/路由;独立调试仍由同一应用入口启动。
6
+ export default defineApplication(() => createApp(App))
@@ -1,6 +1,4 @@
1
- import { createApp } from 'vue'
2
- import App from './App.vue'
1
+ import application from './application'
3
2
 
4
- // 独立运行入口:单独开发/调试远程自身时使用。
5
- // 被宿主消费时走的是 fulgurjs-remoteEntry.js 容器入口,与本文件无关。
6
- createApp(App).mount('#app')
3
+ // 独立入口只负责启动;初始化写在 application 中。
4
+ void application.start('#app').catch(console.error)
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /** Standalone template production launcher. Uses only Node and the existing dev.config.json. */
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import http from 'node:http'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
9
+ const { apps } = JSON.parse(fs.readFileSync(path.join(root, 'scripts/dev.config.json'), 'utf8'))
10
+ if (!Array.isArray(apps) || !apps.length) throw new Error('dev.config.json 未登记应用。')
11
+
12
+ const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.woff': 'font/woff', '.woff2': 'font/woff2', '.map': 'application/json' }
13
+ const inside = (file, directory) => file === directory || file.startsWith(directory + path.sep)
14
+ const mounts = apps.map(app => {
15
+ const dir = path.resolve(root, app.dir, 'dist')
16
+ if (!inside(dir, root)) throw new Error(`${app.name}: 构建目录超出模板。`)
17
+ const index = path.join(dir, 'index.html')
18
+ if (!fs.existsSync(index)) throw new Error(`${app.name}: 缺少 dist/index.html,请先 pnpm build。`)
19
+ const html = fs.readFileSync(index, 'utf8')
20
+ const tags = [...html.matchAll(/<script\b[^>]*>/gi)]
21
+ const tag = tags.find(item => /\btype=["']module["']/i.test(item[0]))?.[0]
22
+ const src = tag?.match(/\bsrc=["']([^"']+)["']/i)?.[1]
23
+ if (!src || /^(?:https?:)?\/\//.test(src)) throw new Error(`${app.name}: 无可本地挂载的模块入口。`)
24
+ // Resolve the actual emitted file suffix, rather than guessing base from an app name.
25
+ const pathname = decodeURIComponent(new URL(src, 'http://template.local/').pathname)
26
+ const parts = pathname.split('/').filter(Boolean)
27
+ const bases = []
28
+ for (let i = 0; i < parts.length; i++) {
29
+ const file = path.resolve(dir, ...parts.slice(i))
30
+ if (inside(file, dir) && fs.existsSync(file) && fs.statSync(file).isFile()) bases.push('/' + parts.slice(0, i).join('/') + (i ? '/' : ''))
31
+ }
32
+ if (bases.length !== 1) throw new Error(`${app.name}: 无法唯一对应构建入口 ${src};检查 Vite base 和干净构建输出。`)
33
+ return { ...app, dir, prefix: bases[0] }
34
+ })
35
+ const hosts = mounts.filter(app => app.host)
36
+ if (!hosts.length) throw new Error('dev.config.json 未登记 host:true 的宿主。')
37
+ const ports = new Set()
38
+ const servers = []
39
+ let stopping = false
40
+ function stop(code = 0) {
41
+ if (stopping) return
42
+ stopping = true
43
+ for (const server of servers) server.close()
44
+ const timer = setTimeout(() => {
45
+ for (const server of servers) server.closeAllConnections?.()
46
+ process.exit(code)
47
+ }, 1000)
48
+ timer.unref()
49
+ process.exitCode = code
50
+ }
51
+ process.once('SIGINT', () => stop(0))
52
+ process.once('SIGTERM', () => stop(0))
53
+ process.once('uncaughtException', error => { console.error(error); stop(1) })
54
+
55
+ for (const host of hosts) {
56
+ if (!Number.isInteger(host.port) || host.port < 1 || host.port > 65535 || ports.has(host.port)) throw new Error(`无效或重复宿主端口:${host.port}`)
57
+ ports.add(host.port)
58
+ const site = [host, ...mounts.filter(app => !app.host)].sort((a, b) => b.prefix.length - a.prefix.length)
59
+ const prefixes = new Set()
60
+ for (const app of site) {
61
+ if (prefixes.has(app.prefix)) throw new Error(`${host.name}: 构建 base ${app.prefix} 冲突;宿主和远程不能部署在同一路径。`)
62
+ prefixes.add(app.prefix)
63
+ }
64
+ const server = http.createServer((req, res) => {
65
+ try {
66
+ const url = new URL(req.url, `http://localhost:${host.port}`)
67
+ if (url.pathname === '/' && host.prefix !== '/') {
68
+ res.writeHead(302, { location: host.prefix, 'cache-control': 'no-cache' })
69
+ return res.end()
70
+ }
71
+ const app = site.find(item => url.pathname === item.prefix.slice(0, -1) || url.pathname.startsWith(item.prefix))
72
+ if (!app) { res.writeHead(404); return res.end('未登记此部署路径') }
73
+ const relative = decodeURIComponent(url.pathname.slice(app.prefix.length)) || 'index.html'
74
+ let file = path.resolve(app.dir, relative)
75
+ if (!inside(file, app.dir)) { res.writeHead(403); return res.end() }
76
+ if (!fs.existsSync(file) || fs.statSync(file).isDirectory()) {
77
+ if (path.extname(relative)) { res.writeHead(404); return res.end('资源不存在;不会回退成HTML') }
78
+ file = path.join(app.dir, 'index.html')
79
+ }
80
+ const stable = /(?:^|\/)(?:index\.html|fulgurjs-(?:remoteEntry\.js|manifest\.json)|_app\.config\.js)$/.test(file)
81
+ res.writeHead(200, {
82
+ 'content-type': mime[path.extname(file)] ?? 'application/octet-stream',
83
+ 'cache-control': stable ? 'no-cache' : 'public, max-age=31536000, immutable',
84
+ 'access-control-allow-origin': '*',
85
+ })
86
+ res.end(fs.readFileSync(file))
87
+ } catch (error) {
88
+ console.error(`生产请求失败:${error.message}`)
89
+ if (!res.headersSent) res.writeHead(400)
90
+ res.end('无效请求或资源不可读')
91
+ }
92
+ })
93
+ servers.push(server)
94
+ server.on('error', error => {
95
+ console.error(`${host.name}:${host.port} 启动失败:${error.message};停止本次全部生产服务器,不会按端口杀已有进程。`)
96
+ stop(1)
97
+ })
98
+ server.listen(host.port, '127.0.0.1', () => {
99
+ if (!stopping) console.log(`生产宿主 ${host.name}: http://localhost:${host.port}${host.prefix}(仅dist,无开发服务器)`)
100
+ })
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fulgurjs/federation",
3
- "version": "6.8.7",
3
+ "version": "6.9.0",
4
4
  "type": "module",
5
5
  "description": "Vite Module Federation for Vue and React: remote modules, shared dependencies and app bridges.",
6
6
  "license": "MIT",
@@ -81,6 +81,10 @@
81
81
  "./internal/registry.js": {
82
82
  "types": "./types/registry.d.ts",
83
83
  "import": "./types/registry.mjs"
84
+ },
85
+ "./internal/vue-store.js": {
86
+ "types": "./dist/vue-store.d.ts",
87
+ "import": "./dist/vue-store.js"
84
88
  }
85
89
  },
86
90
  "typesVersions": {
@@ -110,15 +114,15 @@
110
114
  "docs/zh/reference/errors.md",
111
115
  "docs/en/reference/api.md",
112
116
  "docs/en/reference/errors.md",
113
- "docs/maintainers/webpack-mf-\u5bf9\u7167\u4e0e\u7f3a\u53e3.md",
114
- "docs/maintainers/\u6c99\u7bb1\u8fb9\u754c\u5ba1\u8ba1.md",
117
+ "docs/maintainers/webpack-mf-对照与缺口.md",
118
+ "docs/maintainers/沙箱边界审计.md",
115
119
  "docs/maintainers/compatibility-testing.md",
116
120
  "docs/zh/reference/types.md",
117
121
  "docs/en/reference/types.md",
118
122
  "types"
119
123
  ],
120
124
  "scripts": {
121
- "build": "cp ../../README.md README.md && cp ../../README.en.md README.en.md && mkdir -p docs && mkdir -p docs/zh/reference docs/en/reference docs/maintainers && cp ../../docs/zh/reference/api.md ../../docs/zh/reference/errors.md ../../docs/zh/reference/types.md docs/zh/reference/ && cp ../../docs/en/reference/api.md ../../docs/en/reference/errors.md ../../docs/en/reference/types.md docs/en/reference/ && cp ../../docs/maintainers/webpack-mf-\u5bf9\u7167\u4e0e\u7f3a\u53e3.md ../../docs/maintainers/\u6c99\u7bb1\u8fb9\u754c\u5ba1\u8ba1.md ../../docs/maintainers/compatibility-testing.md docs/maintainers/ && cp ../../CHANGELOG.md CHANGELOG.md && node scripts/sync-package-examples.mjs && esbuild src/runtime/index.ts --bundle --format=esm --target=es2022 --minify --outfile=dist/runtime.js && node scripts/gen-runtime.mjs && tsup src/index.ts src/pages.ts src/vue-adapter.ts src/react-adapter.ts src/context.ts src/bridge-core.ts src/bridge-errors.ts src/bridge-session.ts src/bridge-app-vue.ts src/bridge-app-react.ts src/bridge-host-vue.ts src/bridge-host-react.ts --format esm,cjs --dts --external vite --external @fulgurjs/federation/internal/registry.js --target node18 --clean && tsup src/runtime-entry.ts --format esm --dts --external vite --external @fulgurjs/federation/internal/registry.js --target node18 && tsup src/vue.ts --format esm --dts --external vite,vue,vue-router,@fulgurjs/federation/internal/registry.js --target node18 && tsup src/react.ts --format esm --dts --external vite,react,react-dom,react-router-dom,@fulgurjs/federation/internal/registry.js --target node18 && tsup src/bridge-router-vue.ts --format esm --dts --external vite,vue,vue-router --target node18 && tsup src/bridge-router-react.tsx --format esm --dts --external vite,react,react-dom,react-router-dom --target node18 && esbuild src/cli.ts --bundle --format=esm --target=node18 --platform=node --outfile=dist/cli.js && esbuild src/runtime/index.ts --bundle --format=esm --target=es2022 --minify --outfile=dist/runtime.js && node scripts/gen-runtime-entry.mjs && node scripts/check-gzip.mjs && node scripts/check-manual-codes.mjs",
125
+ "build": "cp ../../README.md README.md && cp ../../README.en.md README.en.md && mkdir -p docs && mkdir -p docs/zh/reference docs/en/reference docs/maintainers && cp ../../docs/zh/reference/api.md ../../docs/zh/reference/errors.md ../../docs/zh/reference/types.md docs/zh/reference/ && cp ../../docs/en/reference/api.md ../../docs/en/reference/errors.md ../../docs/en/reference/types.md docs/en/reference/ && cp ../../docs/maintainers/webpack-mf-对照与缺口.md ../../docs/maintainers/沙箱边界审计.md ../../docs/maintainers/compatibility-testing.md docs/maintainers/ && cp ../../CHANGELOG.md CHANGELOG.md && node scripts/sync-package-examples.mjs && esbuild src/runtime/index.ts --bundle --format=esm --target=es2022 --minify --outfile=dist/runtime.js && node scripts/gen-runtime.mjs && tsup src/index.ts src/pages.ts src/vue-adapter.ts src/react-adapter.ts src/context.ts src/bridge-core.ts src/bridge-errors.ts src/bridge-session.ts src/bridge-app-vue.ts src/bridge-app-react.ts src/vue-store.ts src/bridge-host-vue.ts src/bridge-host-react.ts --format esm,cjs --dts --external vite --external @fulgurjs/federation/internal/registry.js --target node18 --clean && tsup src/runtime-entry.ts --format esm --dts --external vite --external @fulgurjs/federation/internal/registry.js --target node18 && tsup src/vue.ts --format esm --dts --external vite,vue,vue-router,@fulgurjs/federation/internal/registry.js --target node18 && tsup src/react.ts --format esm --dts --external vite,react,react-dom,react-router-dom,@fulgurjs/federation/internal/registry.js --target node18 && tsup src/bridge-router-vue.ts --format esm --dts --external vite,vue,vue-router --target node18 && tsup src/bridge-router-react.tsx --format esm --dts --external vite,react,react-dom,react-router-dom --target node18 && esbuild src/cli.ts --bundle --format=esm --target=node18 --platform=node --outfile=dist/cli.js && esbuild src/runtime/index.ts --bundle --format=esm --target=es2022 --minify --outfile=dist/runtime.js && node scripts/gen-runtime-entry.mjs && node scripts/check-gzip.mjs && node scripts/check-manual-codes.mjs",
122
126
  "dev": "tsup --watch",
123
127
  "test": "vitest run --dir tests",
124
128
  "typecheck": "tsc --noEmit",
@@ -136,23 +140,24 @@
136
140
  "@types/react": "19.3.0",
137
141
  "@types/react-dom": "19.3.0",
138
142
  "jsdom": "^24.0.0",
143
+ "pinia": "3.0.4",
139
144
  "react": "19.3.0",
140
145
  "react-dom": "19.3.0",
146
+ "react-router-dom": "^7.18.4",
141
147
  "tsup": "^8.4.0",
142
148
  "typescript": "^5.8.0",
143
149
  "vitest": "^3.1.0",
144
150
  "vue": "^3.5.22",
145
151
  "vue-router": "^4.6.4",
146
- "react-router-dom": "^7.18.4",
147
152
  "vue-tsc": "^3.3.12"
148
153
  },
149
154
  "peerDependencies": {
150
- "vite": ">=5.1.0",
151
- "vue": ">=3.2.0",
152
155
  "react": ">=18.0.0 <20",
153
156
  "react-dom": ">=18.0.0 <20",
154
- "vue-router": ">=4.1.0 <6",
155
- "react-router-dom": ">=6.11.0 <8"
157
+ "react-router-dom": ">=6.11.0 <8",
158
+ "vite": ">=5.1.0",
159
+ "vue": ">=3.2.0",
160
+ "vue-router": ">=4.1.0 <6"
156
161
  },
157
162
  "pnpm": {
158
163
  "onlyBuiltDependencies": [
@@ -1,15 +0,0 @@
1
- {
2
- "generator": "@fulgurjs/federation",
3
- "ambientFormat": 2,
4
- "alias": "bridge-vue-remote",
5
- "source": "/bridge-vue-remote/fulgurjs-remoteEntry.js",
6
- "revision": "6e2e820a47f98238",
7
- "files": [
8
- "modules.d.ts",
9
- "registry.d.ts"
10
- ],
11
- "externals": [
12
- "@fulgurjs/federation"
13
- ],
14
- "syncedAt": 1791598089894
15
- }
@@ -1,14 +0,0 @@
1
- // 自动生成:fulgurjs-federation 远程类型(remote: bridge-vue-remote,来源: /bridge-vue-remote/fulgurjs-remoteEntry.js,revision: 6e2e820a47f98238)。
2
- // 本文件由插件管理(dev 自动同步 / npx @fulgurjs/federation types),手动修改会被覆盖。
3
- // 外部类型依赖(宿主需可解析):@fulgurjs/federation。
4
- declare module "bridge-vue-remote/bridge" {
5
- /** props 类型:appProps 由宿主传入(label + 稳定回调 onReady) */
6
- export interface BridgeVueProps {
7
- label?: string;
8
- onReady?: () => void;
9
- }
10
- const _default: import("@fulgurjs/federation/vue").BridgeApp & {
11
- __fgBridgeProps?: Record<string, unknown> | undefined;
12
- };
13
- export default _default;
14
- }
@@ -1,10 +0,0 @@
1
- // 自动生成:远程类型注册表(remote: bridge-vue-remote,revision: 6e2e820a47f98238)。
2
- // 向插件共享注册表(internal/registry.js 的 FgRemoteTypes)登记本远程全部公开入口;
3
- // loadRemote / remoteComponent / createVueBridgeApp / createReactBridgeApp 共享该注册表。
4
- import '@fulgurjs/federation/internal/registry.js'
5
-
6
- declare module '@fulgurjs/federation/internal/registry.js' {
7
- interface FgRemoteTypes {
8
- "bridge-vue-remote/bridge": typeof import("bridge-vue-remote/bridge")
9
- }
10
- }