@fulgurjs/federation 6.8.7 → 6.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.
Files changed (139) hide show
  1. package/CHANGELOG.md +14 -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 +189 -189
  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 +123 -123
  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 +196 -196
  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 +184 -184
  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 +182 -182
  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
@@ -2,7 +2,7 @@ packages:
2
2
  - react-host
3
3
  - vue-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": "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,62 @@
1
+ import { StrictMode, useCallback, useState } from 'react'
2
+ import { defineApplication } from '@fulgurjs/federation/react'
3
+ import { clearAppContext } from '@fulgurjs/federation/react'
4
+ import { createReactBridgeApp } from '@fulgurjs/federation/react'
5
+ import { getLatestHostContext, login, logout } from './host-session'
6
+
7
+ // 工厂选项:fallback(pending 占位)、error(节点或 (error, retry) => ReactNode)、
8
+ // retries、timeout、getContext 与 Vue 宿主同语义
9
+ const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', {
10
+ retries: 1,
11
+ getContext: () => getLatestHostContext(),
12
+ })
13
+
14
+ function Host() {
15
+ const [sessionKey, setSessionKey] = useState<string | null>('login-1-alice')
16
+ const [userName, setUserName] = useState('Alice')
17
+ const [label, setLabel] = useState('来自 React 宿主')
18
+ const [readyCount, setReadyCount] = useState(0)
19
+
20
+ const handleReady = useCallback(() => setReadyCount((c) => c + 1), [])
21
+
22
+ const nextFrame = async (): Promise<void> => {
23
+ await new Promise((r) => requestAnimationFrame(() => r(null)))
24
+ }
25
+
26
+ const switchUser = async (): Promise<void> => {
27
+ setSessionKey(null)
28
+ await nextFrame()
29
+ clearAppContext()
30
+ login('login-2-bob', { id: 2, name: 'Bob' })
31
+ setUserName('Bob')
32
+ setSessionKey('login-2-bob')
33
+ }
34
+
35
+ const doLogout = async (): Promise<void> => {
36
+ setSessionKey(null)
37
+ await nextFrame()
38
+ clearAppContext()
39
+ logout()
40
+ setUserName('')
41
+ }
42
+
43
+ return (
44
+ <div style={{ fontFamily: 'sans-serif', padding: 16, maxWidth: 720 }}>
45
+ <h1>React 宿主 × Vue 子应用(@fulgurjs/federation/react)</h1>
46
+ <p data-testid="demo-session">{`会话:${sessionKey ?? '未登录'}(${userName || '—'})· onReady 次数:${readyCount}`}</p>
47
+ <p>
48
+ <button data-testid="demo-switch" onClick={() => void switchUser()}>切换到 Bob(clearAppContext → 重挂)</button>
49
+ <button data-testid="demo-logout" onClick={() => void doLogout()}>登出(sessionKey → null)</button>
50
+ </p>
51
+ <section style={{ marginTop: 12 }}>
52
+ <RemoteVueApp sessionKey={sessionKey} appProps={{ label, onReady: handleReady }} />
53
+ </section>
54
+ </div>
55
+ )
56
+ }
57
+
58
+ export default defineApplication(() => (
59
+ <StrictMode>
60
+ <Host />
61
+ </StrictMode>
62
+ ))
@@ -1,62 +1,2 @@
1
- import { StrictMode, useCallback, useState } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import { clearAppContext } from '@fulgurjs/federation/react'
4
- import { createReactBridgeApp } from '@fulgurjs/federation/react'
5
- import { getLatestHostContext, login, logout } from './host-session'
6
-
7
- // 工厂选项:fallback(pending 占位)、error(节点或 (error, retry) => ReactNode)、
8
- // retries、timeout、getContext 与 Vue 宿主同语义
9
- const RemoteVueApp = createReactBridgeApp('bridge-vue-remote/bridge', {
10
- retries: 1,
11
- getContext: () => getLatestHostContext(),
12
- })
13
-
14
- function Host() {
15
- const [sessionKey, setSessionKey] = useState<string | null>('login-1-alice')
16
- const [userName, setUserName] = useState('Alice')
17
- const [label, setLabel] = useState('来自 React 宿主')
18
- const [readyCount, setReadyCount] = useState(0)
19
-
20
- const handleReady = useCallback(() => setReadyCount((c) => c + 1), [])
21
-
22
- const nextFrame = async (): Promise<void> => {
23
- await new Promise((r) => requestAnimationFrame(() => r(null)))
24
- }
25
-
26
- const switchUser = async (): Promise<void> => {
27
- setSessionKey(null)
28
- await nextFrame()
29
- clearAppContext()
30
- login('login-2-bob', { id: 2, name: 'Bob' })
31
- setUserName('Bob')
32
- setSessionKey('login-2-bob')
33
- }
34
-
35
- const doLogout = async (): Promise<void> => {
36
- setSessionKey(null)
37
- await nextFrame()
38
- clearAppContext()
39
- logout()
40
- setUserName('')
41
- }
42
-
43
- return (
44
- <div style={{ fontFamily: 'sans-serif', padding: 16, maxWidth: 720 }}>
45
- <h1>React 宿主 × Vue 子应用(@fulgurjs/federation/react)</h1>
46
- <p data-testid="demo-session">{`会话:${sessionKey ?? '未登录'}(${userName || '—'})· onReady 次数:${readyCount}`}</p>
47
- <p>
48
- <button data-testid="demo-switch" onClick={() => void switchUser()}>切换到 Bob(clearAppContext → 重挂)</button>
49
- <button data-testid="demo-logout" onClick={() => void doLogout()}>登出(sessionKey → null)</button>
50
- </p>
51
- <section style={{ marginTop: 12 }}>
52
- <RemoteVueApp sessionKey={sessionKey} appProps={{ label, onReady: handleReady }} />
53
- </section>
54
- </div>
55
- )
56
- }
57
-
58
- createRoot(document.getElementById('root')!).render(
59
- <StrictMode>
60
- <Host />
61
- </StrictMode>,
62
- )
1
+ import application from './application'
2
+ void application.start('#root').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
+ }
@@ -1,24 +1,25 @@
1
- # bridge-vue-remote — Vue 桥接子应用示例
1
+ # vue-remote — Vue 子应用
2
2
 
3
- `@fulgurjs/federation` 跨框架桥接的 **Vue 3 子应用**侧示例:以 `defineBridgeApp`(`@fulgurjs/federation/runtime`)导出 `mount/unmount` 契约,可被 **React 宿主**(配合 `examples/templates/react-host-vue-remote/react-host-vue-remote/react-host`)整站挂载。
3
+ 本模板对应未验收的 6.9.0 源码候选,状态与安装方法见[组合说明](../README.md)。复制整个组合目录,在组合根启动。
4
4
 
5
- ## 运行
5
+ ## 按顺序读源码
6
6
 
7
- 复制整个 `react-host-vue-remote/` 模板,在模板根执行:
7
+ 1. `fulgurjs.config.ts`:`./bridge` 直接 expose `src/application.ts`,公开模块名称不受文件名称约束。
8
+ 2. `src/application.ts`:`defineApplication` 唯一装配;独立模式用浏览器路由,桥接模式用本地 memory 路由,两者显示同一套真实页面、计数与 props。
9
+ 3. `src/main.ts`:只调用 application.start,不再复制初始化或新增转发文件。
10
+ 4. `react-host/`:宿主的挂载、会话与回调,见[组合说明](../README.md)。
8
11
 
9
- ```bash
10
- pnpm install --frozen-lockfile
11
- pnpm dev # 同时启动宿主和子应用
12
- pnpm build # 构建完整组合
13
- # 单独启动本应用:pnpm --dir vue-remote dev
14
- ```
12
+ 应用代码只从 `@fulgurjs/federation/vue` 导入插件 API;子应用不因跨框架嵌入而安装另一框架。这个基础组合不启用宿主 URL 同步;需要地址双向同步时使用 showcase 模板。
15
13
 
16
- 端口、复制范围与部署路径见[组合说明](../README.md)。
14
+ 独立启动可以切换首页/关于页,桥接也使用相同页面;卸载与首次挂载由插件管理。Vue 返回未安装的 app/router,React 返回 BrowserRouter/MemoryRouter 内的同一组件树。
17
15
 
18
- ## 关键点
16
+ ## 运行
19
17
 
20
- - `src/bridge.ts`:`defineBridgeApp((props) => VueApp)` 工厂——自行装配 memory 路由(vue-router),作为 `./bridge` expose 的默认导出。
21
- - 子应用只安装并共享 `vue`(+ `vue-router` 为直接依赖),**不安装 React**。
22
- - `sessionKey`/`getContext` 等会话语义由宿主侧控制,见 `examples/templates/react-host-vue-remote/react-host-vue-remote/react-host`。
18
+ ```bash
19
+ pnpm install --frozen-lockfile
20
+ pnpm dev
21
+ pnpm build
22
+ pnpm prod
23
+ ```
23
24
 
24
- 配套阅读:根目录 README §8.2「跨框架桥接 API — /bridge」。
25
+ 以上正式安装命令须在 6.9.0 发布后执行;候选验收先按组合根的候选说明使用本轮 tarball。
@@ -1,14 +1,14 @@
1
1
  import type { FederationOptions } from '@fulgurjs/federation'
2
2
 
3
3
  /**
4
- * Vue 子应用联邦配置:`./bridge` expose = defineBridgeApp 契约模块(任务书 §3.3)。
4
+ * Vue 子应用联邦配置:`./bridge` expose = defineApplication 统一应用契约。
5
5
  * 子应用只安装并共享自己的框架(vue),不为桥接安装对方框架。
6
6
  */
7
7
  export default {
8
8
  name: 'bridge-vue-remote',
9
9
  filename: 'fulgurjs-remoteEntry.js',
10
10
  exposes: {
11
- './bridge': './src/bridge.ts',
11
+ './bridge': './src/application.ts',
12
12
  },
13
13
  shared: {
14
14
  vue: { singleton: true, requiredVersion: '^3.4.0' },
@@ -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 @@
1
+ <template><p data-testid="demo-vue-page">页面:关于</p></template>
@@ -1,12 +1,22 @@
1
1
  <script setup lang="ts">
2
- import { ref } from 'vue'
3
-
4
- // 子应用自身的页面组件(独立运行态与桥接态共用)
2
+ import { onMounted, ref } from 'vue'
3
+ import { RouterView, useRoute, useRouter } from 'vue-router'
4
+ import type { BridgeVueProps } from './application'
5
+ const props = defineProps<BridgeVueProps>()
6
+ const route = useRoute()
7
+ const router = useRouter()
8
+ const mountTime = ref(new Date().toLocaleTimeString())
5
9
  const count = ref(0)
10
+ onMounted(() => props.onReady?.())
6
11
  </script>
7
12
 
8
13
  <template>
9
- <div>
14
+ <section style="border:1px solid #42b883;border-radius:8px;padding:12px;font-family:sans-serif">
15
+ <p style="font-weight:600;color:#42b883">Vue 子应用(挂载于 {{ mountTime }})</p>
16
+ <p data-testid="demo-vue-props">appProps.label = {{ props.label || '(未传)' }}</p>
17
+ <p data-testid="demo-vue-route">路由:{{ route.path }}</p>
18
+ <nav><button @click="router.push('/')">首页</button> <button @click="router.push('/about')">关于</button></nav>
10
19
  <button data-testid="demo-vue-counter" @click="count++">本地状态:{{ count }}</button>
11
- </div>
20
+ <RouterView />
21
+ </section>
12
22
  </template>
@@ -0,0 +1 @@
1
+ <template><p data-testid="demo-vue-page">页面:首页</p></template>
@@ -0,0 +1,19 @@
1
+ import { createApp } from 'vue'
2
+ import { createMemoryHistory, createWebHistory, createRouter } from 'vue-router'
3
+ import { defineApplication } from '@fulgurjs/federation/vue'
4
+ import App from './App.vue'
5
+ import HomePage from './HomePage.vue'
6
+ import AboutPage from './AboutPage.vue'
7
+
8
+ export interface BridgeVueProps { label?: string; onReady?: () => void }
9
+
10
+ // 同一工厂服务独立调试和宿主桥接。这个基础模板没有启用宿主 URL 同步。
11
+ export default defineApplication<BridgeVueProps>((props, { mode }) => {
12
+ const router = createRouter({
13
+ history: mode === 'bridge' ? createMemoryHistory() : createWebHistory(import.meta.env.BASE_URL),
14
+ routes: [{ path: '/', component: HomePage }, { path: '/about', component: AboutPage }],
15
+ })
16
+ const app = createApp(App, { ...props })
17
+ // 不提前安装 router:独立 web 与本地 memory 都由统一入口安装/等待。
18
+ return { app, router }
19
+ })
@@ -1,15 +1,2 @@
1
- import { createApp, defineComponent, h } from 'vue'
2
- import App from './App.vue'
3
-
4
- // 独立运行入口:单独开发/调试子应用自身时使用(渲染同一套页面组件)。
5
- // 被桥接宿主消费时走 fulgurjs-remoteEntry.js 容器入口与 ./bridge 契约,与本文件无关。
6
- const Standalone = defineComponent({
7
- setup() {
8
- return () =>
9
- h('div', { style: 'font-family:sans-serif;padding:16px' }, [
10
- h('h1', 'bridge-vue-remote(独立运行态)'),
11
- h(App),
12
- ])
13
- },
14
- })
15
- createApp(Standalone).mount('#app')
1
+ import application from './application'
2
+ void application.start('#app', { label: '独立 Vue 子应用' }).catch(console.error)
@@ -1,5 +1,8 @@
1
1
  # React 联邦示例:宿主 + 远程(可直接复制运行)
2
2
 
3
+ > 本目录是 6.9.0 源码候选:已改用 defineApplication,尚未测试和发布。源码依赖已锁定 6.9.0;正式发布前不要拿旧 registry 包运行这份代码。验收先使用本次候选 tarball,发布后再按下列冻结安装命令验证正式包。
4
+
5
+
3
6
  本目录是一对完整、可独立安装运行的 React 19 模块联邦演示:
4
7
 
5
8
  | 目录 | 角色 | 端口 | 说明 |
@@ -7,7 +10,7 @@
7
10
  | [`remote/`](./remote/) | **React 远程**(被消费方) | 5203 | 暴露可点击按钮组件、普通 TS 工具模块、联邦首页与参数详情页 |
8
11
  | [`host/`](./host/) | **React 宿主**(消费方) | 5204 | 消费 react-remote 的组件/模块/页面,提供导航与懒加载 |
9
12
 
10
- - 包管理器为 **pnpm**;复制整个模板目录,包含 workspace 和锁文件。插件使用 registry 正式精确版本。
13
+ - 包管理器为 **pnpm**;复制整个模板目录,包含 workspace 和锁文件。插件精确依赖本轮 6.9.0;正式包是否可用见本页顶部状态。
11
14
 
12
15
  ## 最快开始(从干净拷贝)
13
16
 
@@ -83,3 +86,24 @@ server {
83
86
  - 改remote(默认 5203):① `remote/package.json` 的 `dev` 与 `preview` 两个脚本的 `--port 5203`;② `host/fulgurjs.config.ts` 里 `remotes['react-remote'].dev` 的 `http://localhost:5203`;③ 根 `scripts/dev.config.json` 里 remote 的 `port`(**必改**:启动器预检/探活都读它);④ 本 README 顶部端口表。
84
87
  - 改host(默认 5204):① `host/package.json` 的 `dev`/`preview` `--port 5204`;② `scripts/dev.config.json`;③ 本 README 端口表。
85
88
  - 生产部署地址(`prod`,如 `/remote`)是站点路径,与 dev 端口无关,改端口不要动它。改名(容器名)不建议:还需同步 spec 前缀与页面表。
89
+
90
+ ## 一键运行生产产物
91
+
92
+ ```bash
93
+ pnpm build
94
+ pnpm prod
95
+ ```
96
+
97
+ `prod` 只服务本次 dist,不启动远程开发服务器。它复用 scripts/dev.config.json 的宿主端口,从实际构建入口推导 base,并把远程静态产物挂在同源子路径。无需为本地复现手写 Nginx;部署真实服务器时仍按部署指南配置。多宿主模板分别启动各宿主地址,不把两个宿主的根资源混在一起。先停止 dev,端口占用会失败并停止本次已起服务;不会关闭已有占用进程。Ctrl+C 关闭本次生产服务。
98
+
99
+ ## 插件代码阅读地图
100
+
101
+ | 文件 | 职责 |
102
+ |---|---|
103
+ | `host/src/application.tsx` | 本应用唯一装配;main 只负责调用 start |
104
+ | `remote/src/application.tsx` | 本应用唯一装配;main 只负责调用 start |
105
+ | `host/fulgurjs.config.ts` | 本应用的远程地址、exposes、实际共享依赖 |
106
+ | `remote/fulgurjs.config.ts` | 本应用的远程地址、exposes、实际共享依赖 |
107
+ | `scripts/dev.mjs` / `scripts/prod.mjs` | 统一开发/生产启动器;业务接入不复制它们到应用源码 |
108
+
109
+ `application` 是新增的插件装配文件,`main` 是已有入口接线;页面组件、业务路由、权限和数据不是每个项目都必写的联邦配置。跨框架基础组合不逐页登记子应用菜单;逐页远程页面模板的页面表仅服务它所展示的逐页场景。`src/fulgurjs/types/` 是插件生成目录,按远程实际版本同步,不维护人工伪造的声明或旧缓存。
@@ -11,7 +11,7 @@
11
11
  "typecheck": "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
  "react-router-dom": "^7.1.0"
@@ -0,0 +1,33 @@
1
+ import { StrictMode } from 'react'
2
+ import { defineApplication } from '@fulgurjs/federation/react'
3
+ import { BrowserRouter, Link, Route, Routes, useParams, useSearchParams } from 'react-router-dom'
4
+ import App from './App'
5
+ import UtilsDemo from './pages/UtilsDemo'
6
+ import { RemoteHomePage, RemoteDetailPage } from './remotePages'
7
+
8
+ export default defineApplication(() => (
9
+ <StrictMode>
10
+ <BrowserRouter basename={import.meta.env.BASE_URL}>
11
+ <nav style={{ display: 'flex', gap: 16, padding: 16, fontFamily: 'sans-serif' }}>
12
+ <Link to="/">首页</Link>
13
+ <Link to="/remote/home">远程首页</Link>
14
+ <Link to="/remote/detail/7?tab=basic">远程详情(id=7)</Link>
15
+ <Link to="/utils-demo">TS 模块调用</Link>
16
+ </nav>
17
+ <Routes>
18
+ <Route path="/" element={<App />} />
19
+ <Route path="/utils-demo" element={<UtilsDemo />} />
20
+ <Route path="/remote/home" element={<RemoteHomePage />} />
21
+ {/* 远程参数页:params + query 全量透传给远程页面组件 */}
22
+ <Route path="/remote/detail/:id" element={<RemoteDetailRoute />} />
23
+ </Routes>
24
+ </BrowserRouter>
25
+ </StrictMode>
26
+ ))
27
+
28
+ /** 从 react-router 取 params/query 再交给页面适配器组件(适配器与路由库解耦) */
29
+ function RemoteDetailRoute() {
30
+ const { id } = useParams()
31
+ const [search] = useSearchParams()
32
+ return <RemoteDetailPage id={id} tab={search.get('tab') ?? undefined} />
33
+ }
@@ -1,33 +1,4 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import { BrowserRouter, Link, Route, Routes, useParams, useSearchParams } from 'react-router-dom'
4
- import App from './App'
5
- import UtilsDemo from './pages/UtilsDemo'
6
- import { RemoteHomePage, RemoteDetailPage } from './remotePages'
1
+ import application from './application'
7
2
 
8
- createRoot(document.getElementById('root')!).render(
9
- <StrictMode>
10
- <BrowserRouter>
11
- <nav style={{ display: 'flex', gap: 16, padding: 16, fontFamily: 'sans-serif' }}>
12
- <Link to="/">首页</Link>
13
- <Link to="/remote/home">远程首页</Link>
14
- <Link to="/remote/detail/7?tab=basic">远程详情(id=7)</Link>
15
- <Link to="/utils-demo">TS 模块调用</Link>
16
- </nav>
17
- <Routes>
18
- <Route path="/" element={<App />} />
19
- <Route path="/utils-demo" element={<UtilsDemo />} />
20
- <Route path="/remote/home" element={<RemoteHomePage />} />
21
- {/* 远程参数页:params + query 全量透传给远程页面组件 */}
22
- <Route path="/remote/detail/:id" element={<RemoteDetailRoute />} />
23
- </Routes>
24
- </BrowserRouter>
25
- </StrictMode>,
26
- )
27
-
28
- /** 从 react-router 取 params/query 再交给页面适配器组件(适配器与路由库解耦) */
29
- function RemoteDetailRoute() {
30
- const { id } = useParams()
31
- const [search] = useSearchParams()
32
- return <RemoteDetailPage id={id} tab={search.get('tab') ?? undefined} />
33
- }
3
+ // 独立入口只负责启动;初始化写在 application 中。
4
+ void application.start('#root').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
  }