sl-evolink
v1.0.3
Published
商龙通信进化 SDK - 微前端/iframe 应用间通信解决方案
Downloads
140
Maintainers
Readme
sl-evolink
商龙通信进化 SDK - 微前端/iframe 应用间通信解决方案
基于 Comlink 封装,提供类型安全的 RPC 调用,支持 wujie、普通 iframe 等微前端场景。
特性
- 类型安全的远程方法调用
- 支持多实例管理(多 Tab 场景)
- 支持 Class 远程实例化
- 支持子应用间直连通信
- 同时兼容 wujie 和普通 iframe(渐进式迁移友好)
- 兼容 wujie、qiankun、iframe 等微前端方案
安装
npm install sl-evolink
# 或
pnpm add sl-evolink快速开始
1. 主应用(父页面)
// main-app/src/bridge.ts
import { ComlinkBridge, AppType } from 'sl-evolink'
// 定义子应用 API 类型(可选,用于类型提示)
interface ChildAppAPI {
getStatus: () => Promise<{ appName: string; version: string }>
sendMessage: (msg: string) => Promise<{ success: boolean }>
}
// 创建 Bridge 实例
export const bridge = new ComlinkBridge({
appName: 'main',
appType: AppType.SLY,
api: {
// 暴露给子应用调用的方法
async getConfig() {
return { theme: 'dark', lang: 'zh-CN' }
},
async notify(message: string) {
console.log('收到子应用消息:', message)
return { success: true }
}
}
})
// 获取子应用代理并调用
async function callChildApp(instanceId?: string) {
// 推荐:多 Tab 场景使用 instanceId 精确匹配
const childApp = instanceId
? await bridge.getAppByInstanceId<ChildAppAPI>(instanceId)
: await bridge.getApp<ChildAppAPI>('child-app')
const status = await childApp.getStatus()
console.log('子应用状态:', status)
}2. 子应用(子页面)
// child-app/src/bridge.ts
import { ComlinkBridge, AppType } from 'sl-evolink'
// 创建 Bridge 实例
export const bridge = new ComlinkBridge({
appName: 'child-app',
appType: AppType.CRM,
api: {
// 暴露给主应用调用的方法
async getStatus() {
return { appName: 'child-app', version: '1.0.0' }
},
async sendMessage(msg: string) {
console.log('收到消息:', msg)
return { success: true }
}
}
})
// 调用主应用方法
async function callParentApp() {
const mainApp = await bridge.getApp('main')
const config = await mainApp.getConfig()
console.log('主应用配置:', config)
}多 Tab 场景(重点)
当同一个子应用需要打开多个 Tab 时,需要使用 instanceId 来区分不同实例。
主应用 - 创建 Tab 并注入 instanceId
import { startApp } from 'wujie'
import {
ComlinkBridge,
AppType,
generateUUID,
createInstancePlugin
} from 'sl-evolink'
// 创建 Bridge
const bridge = new ComlinkBridge({
appName: 'main',
appType: AppType.SLY,
api: { /* ... */ }
})
// Tab 数据结构
interface Tab {
id: string
instanceId: string // 核心:与 AppManager 绑定的唯一标识
appName: string // 子应用名称(可重复)
url: string
}
// 创建新 Tab
async function createTab(appName: string, url: string) {
// 1. 预先生成 instanceId
const instanceId = generateUUID()
// 2. 创建 Tab
const tab: Tab = {
id: `tab-${instanceId.slice(0, 8)}`,
instanceId,
appName,
url
}
// 3. 使用 createInstancePlugin 注入 instanceId
await startApp({
name: `wujie-${instanceId.slice(0, 8)}`,
url,
el: '#container',
plugins: [
createInstancePlugin({
instanceId: tab.instanceId,
tabId: tab.id,
onWindowReady: (childWindow) => {
console.log('子应用窗口已创建')
}
})
]
})
return tab
}
// 通过 instanceId 获取子应用(精确匹配)
async function getAppByTab(tab: Tab) {
return bridge.getAppByInstanceId(tab.instanceId)
}
// 关闭 Tab 时注销
function closeTab(tab: Tab) {
bridge.removeAppByInstanceId(tab.instanceId)
}配合 wujie-vue2 / wujie-vue3 组件使用
<template>
<WujieVue
:name="`wujie-${tab.instanceId.slice(0, 8)}`"
:url="tab.url"
:plugins="getPlugins(tab)"
/>
</template>
<script setup>
import WujieVue from 'wujie-vue3' // 或 wujie-vue2
import { createInstancePlugin, generateUUID } from 'sl-evolink'
// 创建 Tab
function createTab(appName, url) {
const instanceId = generateUUID()
return {
id: `tab-${instanceId.slice(0, 8)}`,
instanceId,
appName,
url
}
}
// 为 Tab 生成 plugins
function getPlugins(tab) {
return [
createInstancePlugin({
instanceId: tab.instanceId,
tabId: tab.id
})
]
}
</script>普通 iframe 支持
SDK 同时支持普通 iframe,适用于以下场景:
- 渐进式迁移:部分应用使用 wujie,部分使用普通 iframe
- 简单集成:不需要 wujie 的沙箱能力
- 跨团队协作:子应用团队使用不同技术栈
主应用 - 加载普通 iframe
方式 1:使用 loadIframe 辅助函数(推荐)
import { loadIframe, generateUUID, ComlinkBridge, AppType } from 'sl-evolink'
// 创建主应用 Bridge
const bridge = new ComlinkBridge({
appName: 'main',
appType: AppType.SLY,
api: { /* ... */ }
})
// 加载子应用 iframe
const container = document.getElementById('app-container')
const { iframe, instanceId, destroy } = loadIframe({
container, // DOM 元素
url: 'http://localhost:3001', // 子应用地址
tabId: 'tab-1', // 可选:关联的 Tab ID
instanceId: generateUUID(), // 可选:不传则自动生成
attrs: { allow: 'fullscreen' }, // 可选:额外的 iframe 属性
params: { theme: 'dark' }, // 可选:额外的 URL 参数
onLoad: (iframe, instanceId) => {
console.log('子应用加载完成:', instanceId)
}
})
// 保存 Tab 映射
tabMap.set('tab-1', { instanceId })
// 获取子应用代理
const childApp = await bridge.getAppByInstanceId(instanceId)
await childApp.someMethod()
// 销毁
destroy()生成的 URL 格式:
http://localhost:3001/?__instanceId=550e8400-e29b-41d4-a716-446655440000&__tabId=tab-1&theme=dark方式 2:手动创建 iframe
import { buildUrlWithInstanceId, generateUUID } from 'sl-evolink'
const instanceId = generateUUID()
const iframe = document.createElement('iframe')
iframe.src = buildUrlWithInstanceId('http://localhost:3001', instanceId)
// 生成: http://localhost:3001/?__instanceId=xxx
container.appendChild(iframe)子应用 - 代码无需修改
子应用代码 完全相同,无论是 wujie 还是普通 iframe:
import { ComlinkBridge, AppType } from 'sl-evolink'
// SDK 自动检测 instanceId 来源:
// 1. options.instanceId(手动传入)
// 2. window.__COMLINK_PREASSIGNED_INSTANCE_ID__(wujie 注入)
// 3. URL 参数 ?__instanceId=xxx(普通 iframe)
// 4. 自动生成 UUID
const bridge = new ComlinkBridge({
appName: 'child-app',
appType: AppType.CY,
api: {
async getStatus() {
return { appName: 'child-app', version: '1.0.0' }
}
}
})
await bridge.waitReady()wujie 和 iframe 对比
| 对比项 | wujie | 普通 iframe |
|--------|-------|-------------|
| 通信机制 | postMessage + MessageChannel | 相同 |
| instanceId 注入 | createInstancePlugin | URL 参数 ?__instanceId=xxx |
| 子应用代码 | new ComlinkBridge({ ... }) | 相同 |
| JS 沙箱 | 有 | 无 |
| CSS 隔离 | 有 | 天然隔离 |
| 路由同步 | 支持 | 需手动处理 |
API 参考
ComlinkBridge
主要的通信桥接类。
const bridge = new ComlinkBridge({
appName: string, // 应用名称
appType: AppType, // 应用类型
instanceId?: string, // 实例ID(可选,不传则自动生成或使用注入的)
timeout?: number, // 超时时间,默认 10000ms
api?: object // 暴露给其他应用的 API
})方法:
| 方法 | 说明 |
|------|------|
| getApp<T>(appName) | 通过 appName 获取应用代理 |
| getAppByInstanceId<T>(instanceId) | 通过 instanceId 获取应用代理(推荐) |
| getAppByType<T>(appType) | 通过应用类型获取代理 |
| getAppsByName<T>(appName) | 获取同名的所有实例 |
| removeApp(appName) | 移除应用(按 appName) |
| removeAppByInstanceId(instanceId) | 移除应用(按 instanceId,推荐) |
| waitReady(timeout?) | 等待就绪 |
| getRegisteredApps() | 获取所有已注册应用名 |
| getRegisteredInstances() | 获取所有实例详情 |
| onAppRegistered(callback) | 监听应用注册事件 |
| onAppRemoved(callback) | 监听应用移除事件 |
AppType
应用类型枚举:
enum AppType {
CY = 'CY', // 餐饮系统
CRM = 'CRM', // CRM 系统
SLY = 'SLY', // 商龙云
AI = 'AI', // AI 助手
SCM = 'SCM' // 供应链系统
}工具函数
import {
// 通用
generateUUID, // 生成 UUID
generateShortId, // 生成短 ID(8位)
// wujie 专用
createInstancePlugin, // 创建 wujie plugin
getPreassignedInstanceId, // 获取 wujie 注入的 instanceId
getTabId, // 获取 wujie 注入的 Tab ID
// 普通 iframe 专用
loadIframe, // 加载 iframe 并注入 instanceId
buildUrlWithInstanceId, // 构建带 instanceId 的 URL
getInstanceIdFromUrl, // 从 URL 读取 instanceId
getTabIdFromUrl // 从 URL 读取 Tab ID
} from 'sl-evolink'createInstancePlugin(wujie 专用)
创建 wujie plugin,用于注入 instanceId 到子应用:
createInstancePlugin({
instanceId: string, // 必填,实例ID
tabId?: string, // 可选,Tab ID
onWindowReady?: (win) => void // 可选,窗口就绪回调
})loadIframe(普通 iframe 专用)
加载普通 iframe 并自动注入 instanceId:
interface IframeLoaderOptions {
container: HTMLElement // iframe 容器元素
url: string // 子应用 URL
instanceId?: string // 可选,不传则自动生成
tabId?: string // 可选,Tab ID
name?: string // 可选,iframe name 属性
attrs?: Record<string, string> // 可选,额外的 iframe 属性
params?: Record<string, string> // 可选,额外的 URL 参数
onLoad?: (iframe, instanceId) => void // 加载完成回调
onError?: (error) => void // 加载失败回调
}
interface IframeLoaderResult {
iframe: HTMLIFrameElement // iframe 元素
instanceId: string // 分配的 instanceId
destroy: () => void // 销毁方法
}
const result = loadIframe(options): IframeLoaderResult高级用法
暴露 Class 给远程调用
// 子应用
import { ComlinkBridge, AppType, proxy } from 'sl-evolink'
class Calculator {
private value = 0
add(n: number) {
this.value += n
return this.value
}
getValue() {
return this.value
}
}
const bridge = new ComlinkBridge({
appName: 'calc-app',
appType: AppType.SLY,
api: {
// 方式1:直接暴露 Class
Calculator,
// 方式2:返回实例(需要 proxy 包装)
createCalculator(initial: number) {
return proxy(new Calculator(initial))
}
}
})// 主应用调用
const app = await bridge.getApp('calc-app')
// 远程实例化
const calc = await new app.Calculator(100)
await calc.add(50)
const value = await calc.getValue() // 150
// 或使用工厂方法
const calc2 = await app.createCalculator(200)
await calc2.add(100)返回复杂对象
如果返回值包含函数,需要使用 proxy 包装:
import { proxy } from 'sl-evolink'
const bridge = new ComlinkBridge({
appName: 'my-app',
appType: AppType.SLY,
api: {
async getData() {
return proxy({
value: 100,
// 返回的对象包含函数
format: (prefix: string) => `${prefix}: ${100}`
})
}
}
})子应用间直连
// 子应用 A 调用子应用 B
const appB = await bridge.getApp('app-b')
const result = await appB.someMethod()监听应用注册/移除事件
当有新的子应用注册或现有应用被移除时,可以通过事件监听来实时响应:
import { ComlinkBridge, AppType } from 'sl-evolink'
import type { AppManagerEventData } from 'sl-evolink'
const bridge = new ComlinkBridge({
appName: 'my-app',
appType: AppType.CRM,
api: { /* ... */ }
})
// 监听新应用注册
bridge.onAppRegistered((info: AppManagerEventData) => {
// 过滤掉自己
if (info.instanceId === bridge.instanceId) return
console.log(`新应用加入: ${info.appName}`)
console.log(`实例ID: ${info.instanceId}`)
console.log(`应用类型: ${info.appType}`)
// 刷新应用列表
refreshPeerApps()
})
// 监听应用移除
bridge.onAppRemoved((info: AppManagerEventData) => {
// 过滤掉自己
if (info.instanceId === bridge.instanceId) return
console.log(`应用离开: ${info.appName}`)
// 清理相关状态
if (selectedPeer?.instanceId === info.instanceId) {
selectedPeer = null
}
refreshPeerApps()
})AppManagerEventData 类型:
interface AppManagerEventData {
instanceId: string // 实例唯一标识
appName: string // 应用名称
appType: AppType // 应用类型
timestamp: number // 事件时间戳
}使用场景:
- 实时更新已连接应用列表
- 当目标应用离线时,自动清理相关状态
- 显示应用上下线通知
完整示例
参考 examples/ 目录:
examples/main/- 主应用示例(Vue 3)examples/sub-vue2/- Vue 2 子应用示例examples/sub-vue3/- Vue 3 子应用示例
License
MIT
