@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.
- package/CHANGELOG.md +14 -0
- package/README.en.md +3 -0
- package/README.md +3 -0
- package/dist/application-core-BuhXQmLF.d.cts +16 -0
- package/dist/application-core-DWI3eKme.d.ts +16 -0
- package/dist/bridge-app-react.cjs +177 -9
- package/dist/bridge-app-react.d.cts +15 -3
- package/dist/bridge-app-react.d.ts +15 -3
- package/dist/bridge-app-react.js +101 -9
- package/dist/bridge-app-vue.cjs +138 -12
- package/dist/bridge-app-vue.d.cts +20 -5
- package/dist/bridge-app-vue.d.ts +20 -5
- package/dist/bridge-app-vue.js +62 -12
- package/dist/bridge-host-react.d.cts +1 -1
- package/dist/bridge-host-react.d.ts +1 -1
- package/dist/bridge-host-vue.d.cts +1 -1
- package/dist/bridge-host-vue.d.ts +1 -1
- package/dist/chunk-6OB47FSR.js +81 -0
- package/dist/cli.js +349 -23
- package/dist/index.cjs +7 -6
- package/dist/index.js +7 -6
- package/dist/react-adapter.d.cts +1 -1
- package/dist/react-adapter.d.ts +1 -1
- package/dist/react.d.ts +29 -4
- package/dist/react.js +1 -0
- package/dist/{remote-types-xf1B6_pK.d.ts → remote-types-COmBhjRc.d.ts} +1 -1
- package/dist/{remote-types-58hdbx6x.d.cts → remote-types-CStGOJwu.d.cts} +1 -1
- package/dist/runtime.js +1 -1
- package/dist/vue-adapter.d.cts +1 -1
- package/dist/vue-adapter.d.ts +1 -1
- package/dist/vue-store.cjs +40 -0
- package/dist/vue-store.d.cts +4 -0
- package/dist/vue-store.d.ts +4 -0
- package/dist/vue-store.js +15 -0
- package/dist/vue.d.ts +37 -6
- package/dist/vue.js +2 -0
- package/docs/en/reference/api.md +38 -2
- package/docs/en/reference/types.md +27 -0
- package/docs/zh/reference/api.md +46 -2
- package/docs/zh/reference/types.md +27 -0
- package/examples/templates/README.md +3 -0
- package/examples/templates/react-host-vue-remote/README.md +24 -0
- package/examples/templates/react-host-vue-remote/package.json +2 -1
- package/examples/templates/react-host-vue-remote/pnpm-lock.yaml +189 -189
- package/examples/templates/react-host-vue-remote/pnpm-workspace.yaml +1 -1
- package/examples/templates/react-host-vue-remote/react-host/package.json +1 -1
- package/examples/templates/react-host-vue-remote/react-host/src/application.tsx +62 -0
- package/examples/templates/react-host-vue-remote/react-host/src/main.tsx +2 -62
- package/examples/templates/react-host-vue-remote/scripts/prod.mjs +101 -0
- package/examples/templates/react-host-vue-remote/vue-remote/README.md +17 -16
- package/examples/templates/react-host-vue-remote/vue-remote/fulgurjs.config.ts +2 -2
- package/examples/templates/react-host-vue-remote/vue-remote/package.json +1 -1
- package/examples/templates/react-host-vue-remote/vue-remote/src/AboutPage.vue +1 -0
- package/examples/templates/react-host-vue-remote/vue-remote/src/App.vue +15 -5
- package/examples/templates/react-host-vue-remote/vue-remote/src/HomePage.vue +1 -0
- package/examples/templates/react-host-vue-remote/vue-remote/src/application.ts +19 -0
- package/examples/templates/react-host-vue-remote/vue-remote/src/main.ts +2 -15
- package/examples/templates/react-react/README.md +25 -1
- package/examples/templates/react-react/host/package.json +1 -1
- package/examples/templates/react-react/host/src/application.tsx +33 -0
- package/examples/templates/react-react/host/src/main.tsx +3 -32
- package/examples/templates/react-react/package.json +2 -1
- package/examples/templates/react-react/pnpm-lock.yaml +123 -123
- package/examples/templates/react-react/pnpm-workspace.yaml +1 -1
- package/examples/templates/react-react/remote/package.json +1 -1
- package/examples/templates/react-react/remote/src/application.tsx +11 -0
- package/examples/templates/react-react/remote/src/main.tsx +3 -10
- package/examples/templates/react-react/scripts/prod.mjs +101 -0
- package/examples/templates/showcase/README.md +35 -7
- package/examples/templates/showcase/package.json +2 -1
- package/examples/templates/showcase/pnpm-lock.yaml +196 -196
- package/examples/templates/showcase/pnpm-workspace.yaml +1 -1
- package/examples/templates/showcase/react-host/package.json +1 -1
- package/examples/templates/showcase/react-host/src/application.tsx +28 -0
- package/examples/templates/showcase/react-host/src/demo-log.ts +5 -8
- package/examples/templates/showcase/react-host/src/main.tsx +2 -33
- package/examples/templates/showcase/react-host/src/routing.ts +2 -2
- package/examples/templates/showcase/react-remote/fulgurjs.config.ts +1 -1
- package/examples/templates/showcase/react-remote/package.json +1 -1
- package/examples/templates/showcase/react-remote/src/ChildLayout.tsx +3 -2
- package/examples/templates/showcase/react-remote/src/{bridge.tsx → application.tsx} +21 -21
- package/examples/templates/showcase/react-remote/src/child-bus.ts +23 -23
- package/examples/templates/showcase/react-remote/src/main.tsx +2 -18
- package/examples/templates/showcase/react-remote/src/pages/Locked.tsx +2 -1
- package/examples/templates/showcase/react-remote/src/pages/OrderDetail.tsx +2 -1
- package/examples/templates/showcase/react-remote/src/pages/OrderList.tsx +2 -1
- package/examples/templates/showcase/scripts/prod.mjs +101 -0
- package/examples/templates/showcase/vue-host/package.json +1 -1
- package/examples/templates/showcase/vue-host/src/application.ts +28 -0
- package/examples/templates/showcase/vue-host/src/demo-log.ts +4 -7
- package/examples/templates/showcase/vue-host/src/main.ts +2 -30
- package/examples/templates/showcase/vue-host/src/routing.ts +2 -2
- package/examples/templates/showcase/vue-remote/fulgurjs.config.ts +1 -1
- package/examples/templates/showcase/vue-remote/package.json +1 -1
- package/examples/templates/showcase/vue-remote/src/ChildLayout.vue +3 -2
- package/examples/templates/showcase/vue-remote/src/{bridge.ts → application.ts} +10 -13
- package/examples/templates/showcase/vue-remote/src/child-bus.ts +23 -23
- package/examples/templates/showcase/vue-remote/src/main.ts +2 -16
- package/examples/templates/showcase/vue-remote/src/pages/Locked.vue +2 -1
- package/examples/templates/showcase/vue-remote/src/pages/OrderDetail.vue +2 -1
- package/examples/templates/showcase/vue-remote/src/pages/OrderList.vue +2 -1
- package/examples/templates/vue-host-react-remote/README.md +24 -0
- package/examples/templates/vue-host-react-remote/package.json +2 -1
- package/examples/templates/vue-host-react-remote/pnpm-lock.yaml +184 -184
- package/examples/templates/vue-host-react-remote/pnpm-workspace.yaml +1 -1
- package/examples/templates/vue-host-react-remote/react-remote/README.md +17 -17
- package/examples/templates/vue-host-react-remote/react-remote/fulgurjs.config.ts +2 -2
- package/examples/templates/vue-host-react-remote/react-remote/package.json +1 -1
- package/examples/templates/vue-host-react-remote/react-remote/src/{bridge.tsx → application.tsx} +13 -24
- package/examples/templates/vue-host-react-remote/react-remote/src/main.tsx +2 -7
- package/examples/templates/vue-host-react-remote/scripts/prod.mjs +101 -0
- package/examples/templates/vue-host-react-remote/vue-host/package.json +1 -1
- package/examples/templates/vue-host-react-remote/vue-host/src/application.ts +5 -0
- package/examples/templates/vue-host-react-remote/vue-host/src/main.ts +2 -4
- package/examples/templates/vue-vue/README.md +25 -1
- package/examples/templates/vue-vue/host/package.json +1 -1
- package/examples/templates/vue-vue/host/src/application.ts +54 -0
- package/examples/templates/vue-vue/host/src/main.ts +3 -50
- package/examples/templates/vue-vue/package.json +2 -1
- package/examples/templates/vue-vue/pnpm-lock.yaml +182 -182
- package/examples/templates/vue-vue/pnpm-workspace.yaml +1 -1
- package/examples/templates/vue-vue/remote/package.json +1 -1
- package/examples/templates/vue-vue/remote/src/application.ts +6 -0
- package/examples/templates/vue-vue/remote/src/main.ts +3 -5
- package/examples/templates/vue-vue/scripts/prod.mjs +101 -0
- package/package.json +14 -9
- package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote/metadata.json +0 -15
- package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote/modules.d.ts +0 -14
- package/examples/templates/react-host-vue-remote/react-host/src/fulgurjs/types/bridge-vue-remote/registry.d.ts +0 -10
- package/examples/templates/react-host-vue-remote/vue-remote/src/bridge.ts +0 -57
- package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote/metadata.json +0 -16
- package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote/modules.d.ts +0 -71
- package/examples/templates/showcase/react-host/src/fulgurjs/types/vue-remote/registry.d.ts +0 -10
- package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote/metadata.json +0 -16
- package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote/modules.d.ts +0 -79
- package/examples/templates/showcase/vue-host/src/fulgurjs/types/react-remote/registry.d.ts +0 -10
- package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote/metadata.json +0 -16
- package/examples/templates/vue-host-react-remote/vue-host/src/fulgurjs/types/bridge-react-remote/modules.d.ts +0 -10
- 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
|
- vue-host
|
|
3
3
|
- react-remote
|
|
4
4
|
minimumReleaseAgeExclude:
|
|
5
|
-
- '@fulgurjs/federation@6.
|
|
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:
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-remote — React 子应用
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本模板对应未验收的 6.9.0 源码候选,状态与安装方法见[组合说明](../README.md)。复制整个组合目录,在组合根启动。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 按顺序读源码
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
1. `fulgurjs.config.ts`:`./bridge` 直接 expose `src/application.tsx`,公开模块名称不受文件名称约束。
|
|
8
|
+
2. `src/application.tsx`:`defineApplication` 唯一装配;独立模式用浏览器路由,桥接模式用本地 memory 路由,两者显示同一套真实页面、计数与 props。
|
|
9
|
+
3. `src/main.tsx`:只调用 application.start,不再复制初始化或新增转发文件。
|
|
10
|
+
4. `vue-host/`:宿主的挂载、会话与回调,见[组合说明](../README.md)。
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
pnpm install --frozen-lockfile
|
|
11
|
-
pnpm dev # 同时启动宿主和子应用
|
|
12
|
-
pnpm build # 构建完整组合
|
|
13
|
-
# 单独启动本应用:pnpm --dir react-remote dev
|
|
14
|
-
```
|
|
12
|
+
应用代码只从 `@fulgurjs/federation/react` 导入插件 API;子应用不因跨框架嵌入而安装另一框架。这个基础组合不启用宿主 URL 同步;需要地址双向同步时使用 showcase 模板。
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
独立启动可以切换首页/关于页,桥接也使用相同页面;卸载与首次挂载由插件管理。Vue 返回未安装的 app/router,React 返回 BrowserRouter/MemoryRouter 内的同一组件树。
|
|
17
15
|
|
|
18
|
-
##
|
|
16
|
+
## 运行
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
```bash
|
|
19
|
+
pnpm install --frozen-lockfile
|
|
20
|
+
pnpm dev
|
|
21
|
+
pnpm build
|
|
22
|
+
pnpm prod
|
|
23
|
+
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
以上正式安装命令须在 6.9.0 发布后执行;候选验收先按组合根的候选说明使用本轮 tarball。
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { FederationOptions } from '@fulgurjs/federation'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* React 子应用联邦配置:`./bridge` expose =
|
|
4
|
+
* React 子应用联邦配置:`./bridge` expose = defineApplication 统一应用契约。
|
|
5
5
|
* 子应用只安装并共享自己的框架(react + react-dom),不为桥接安装 Vue。
|
|
6
6
|
*/
|
|
7
7
|
export default {
|
|
8
8
|
name: 'bridge-react-remote',
|
|
9
9
|
filename: 'fulgurjs-remoteEntry.js',
|
|
10
10
|
exposes: {
|
|
11
|
-
'./bridge': './src/
|
|
11
|
+
'./bridge': './src/application.tsx',
|
|
12
12
|
},
|
|
13
13
|
shared: {
|
|
14
14
|
react: { singleton: true },
|
package/examples/templates/vue-host-react-remote/react-remote/src/{bridge.tsx → application.tsx}
RENAMED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React 子应用桥接契约(./bridge expose 的默认导出,任务书 §3.3)。
|
|
3
|
-
*
|
|
4
|
-
* defineBridgeApp 工厂接收 props 快照、返回 ReactElement;
|
|
5
|
-
* createRoot/首次提交探测/unmount 由契约实现负责(react-dom/client 在实际 mount 时按需加载)。
|
|
6
|
-
*/
|
|
7
1
|
import { useEffect, useState } from 'react'
|
|
8
|
-
import { MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
|
9
|
-
import {
|
|
2
|
+
import { BrowserRouter, MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
|
3
|
+
import { defineApplication } from '@fulgurjs/federation/react'
|
|
10
4
|
|
|
11
5
|
/** 挂载即回调(宿主验证函数引用跨 root 传递) */
|
|
12
6
|
function OnReady({ onReady }: { onReady?: () => void }) {
|
|
@@ -27,14 +21,16 @@ function Counter() {
|
|
|
27
21
|
)
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
export interface BridgeReactProps { label?: string; onReady?: () => void }
|
|
25
|
+
|
|
26
|
+
function Shell(props: BridgeReactProps) {
|
|
31
27
|
const location = useLocation()
|
|
32
28
|
const navigate = useNavigate()
|
|
33
29
|
return (
|
|
34
30
|
<div style={{ border: '1px solid #61dafb', borderRadius: 8, padding: 12, fontFamily: 'sans-serif' }}>
|
|
35
31
|
<p style={{ margin: '0 0 6px', fontWeight: 600, color: '#0b7285' }}>React 子应用</p>
|
|
36
32
|
<p data-testid="demo-react-props" style={{ margin: '0 0 6px' }}>
|
|
37
|
-
{`appProps.label = ${
|
|
33
|
+
{`appProps.label = ${props.label || '(未传)'}`}
|
|
38
34
|
</p>
|
|
39
35
|
<p data-testid="demo-react-route" style={{ margin: '0 0 8px' }}>{`memory 路由:${location.pathname}`}</p>
|
|
40
36
|
<nav style={{ marginBottom: 8 }}>
|
|
@@ -50,17 +46,10 @@ function Shell(props: Record<string, unknown>) {
|
|
|
50
46
|
)
|
|
51
47
|
}
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</MemoryRouter>
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export default defineBridgeApp((props) => (
|
|
62
|
-
<MemoryRouter>
|
|
63
|
-
<OnReady onReady={(props as any).onReady} />
|
|
64
|
-
<Shell {...props} />
|
|
65
|
-
</MemoryRouter>
|
|
66
|
-
))
|
|
49
|
+
// 独立入口与桥接共享同一套页面、状态和 onReady。
|
|
50
|
+
export default defineApplication<BridgeReactProps>((props, { mode }) => {
|
|
51
|
+
const content = <><OnReady onReady={props.onReady} /><Shell {...props} /></>
|
|
52
|
+
return mode === 'bridge'
|
|
53
|
+
? <MemoryRouter>{content}</MemoryRouter>
|
|
54
|
+
: <BrowserRouter basename={import.meta.env.BASE_URL}>{content}</BrowserRouter>
|
|
55
|
+
})
|
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { App } from './bridge'
|
|
4
|
-
|
|
5
|
-
// 独立运行入口:单独开发/调试子应用自身时使用。
|
|
6
|
-
// 被桥接宿主消费时走 fulgurjs-remoteEntry.js 容器入口与 ./bridge 契约,与本文件无关。
|
|
7
|
-
createRoot(document.getElementById('root')!).render(createElement(App))
|
|
1
|
+
import application from './application'
|
|
2
|
+
void application.start('#root', { label: '独立 React 子应用' }).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,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
|
|
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/` 是插件生成目录,按远程实际版本同步,不维护人工伪造的声明或旧缓存。
|
|
@@ -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
|
|
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
|
-
//
|
|
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
|
}
|