npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sl-material/sl-evolink

v1.0.0

Published

商龙通信进化 SDK - 微前端/iframe 应用间通信解决方案

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);
}

普通 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