likes
comments
collection
share

wujie在第三方SDK开发中的应用实践

作者站长头像
站长
· 阅读数 12

背景

业务介绍

目前作者所开发的项目都涉及对外以 SDK 的形式嵌入到用户系统中,在保证 SDK 不影响用户环境 的同时还要保证 SDK 的整个接入流程必须是 简单可靠的,尽量的降低用户的接入成本,但是实现情况是是:

  • SDK 经过 webpack 构建出 umd 格式的产物,产物中会包含 React、antd、mobx 等依赖,直接运行在用户环境会对用户环境造成污染。
  • 客户的系统技术差、老旧、存在许多全局污染,会影响 SDK 的正常工作,同时我们也很难推动用户系统进行改造。

因此将 SDK 运行在一个与用户系统隔离的沙箱中是有必要的,这既能保证 SDK 有一个独立且纯净的运行环境,也能防止 SDK 污染用户环境。、

需求场景概述

  • SDK 数据导出

    例如 文档标注工具SDK图片编辑器 SDK,用户希望 SDK 能够将处理好的结果数据直接进行导出,由用户自行做进一步的处理。在传统的 iframe 方案中都需要借助浏览器的 postMessage 进行属于的传递。

  • SDK 鉴权

    SDK 交付的另一痛点就是缺少鉴权机制,对于一些对系统安全性要高的客户来说,他们不希望 SDK 能够直接被访问,这就要求 SDK 需要去对接用户的权限管理机制。

第三方javascript概念介绍

SDK通常被称为"第三方javascript"。相较于微前端所强调的能够集成各方模块组成系统, 第三方javascript强调的是如何将已有功能快速无缝集成入客户已存在的系统中,且彼此无冲突。

整体集成入客户系统中的功能既包括前端视图也包含后端服务, 二者需要配合实现整体业务功能, 嵌入用户系统的SDK整体看更像是一个“微系统”。

常见的第三方javascript应用包括

  • 百度统计 -- 寄宿于客户站点内运行, 不包含图形界面
  • 百度爱番番 -- 沟通窗口、留言板等功能寄宿于客户站点内运行, 包含图形界面, 更复杂

第三方SDK开发之殇

需要考虑依赖使用

开发运行于第三方的SDK时, 需要考虑SDK本身的依赖与宿主环境的依赖是否存在冲突。

举例来说, 假如SDK使用了React, 那么就需要考虑宿主环境的React的版本, 如果与宿主环境React冲突或者重叠都将带来Bug, 其他依赖亦然。这给SDK开发者带来了极大的设计负担。

在过去,社区SDK常采取zero dependency策略, 但是对于一些包含复杂功能的SDK而言, 不使用任何依赖基本不现实。

易产生环境冲突

SDK寄宿于第三方运行环境。第三方环境未知性强。部分甲方系统存在质量低劣情况, 如全局调整div样式。

div {
	box-sizing: border-box
}

运行与第三方环境的SDK常常会受到宿主影响, 虽自身代码规范无误, 但因受到宿主环境干扰而导致无法正常使用的例子也较为常见。

难以要求使用方做修改适配

此问题对于一些交付目标客群为金融场景的项目中表现尤为突出, 如国有银行, 具备极强的甲方色彩。

基于此场景, 实际交互过程中较难要求客户对于已有系统进行过多改造以适配SDK集成。这更进一步要求SDK能够开箱即用、接入成本低。

沙箱需要具备的能力

环境隔离

环境隔离包括 js 隔离和 css 隔离,避免和宿主环境出现如全局变量冲突、样式冲突,这些冲突可能会导致应用样式异常,甚至功能不可用。

通信

基于用户的实际场景,沙箱需要提供一套完整的通信系统,宿主环境通过调用沙箱暴露出去的 API 和沙箱进行双向通信。

便捷

沙箱除了具备环境隔离和通信功能以外,还需要考虑用户的接入成本,大部分的第三方客户比较青睐 iframe 这种接入方式,因为这种方式最简单、学习成本低,所以我们提供的沙箱在使用方式上需要尽可能的向 iframe 靠拢。

为什么选择wujie

wujie定义

无界微前端是一款基于 Web Components + iframe 微前端框架,具备接入成本低、应用隔离、速度快等一系列优点,内部提供了一套开箱即用的安全沙箱机制,更多具体特性可查看文档

相较于iframe的优势

在过去, 一些项目在面对包含复杂视图层功能的SDK的需求时, 为了避免与宿主环境产生依赖及运行时的冲突,使用了iframe方案开发。即在集成方页面创建一个iframe, 加载包含视图层功能的页面, 以实现将视图层功能嵌入至宿主环境局部。

iframe方案存在诸多问题。 对此, 国内微前端框架qiankun产出过一篇文档 — Why Not Iframe 文章对于iframe的问题做了讲解。

而基于wujie做第三方SDK开发,能够杜绝使用iframe遇到的问题,如dom割裂严重,弹窗只能在iframe内部展示,无法覆盖全局等。同时具备更优质的通信能力

  • 启动程序时为SDK注入参数
startApp({
  props: {
     param1: 'xxx',
     param2: 'yyy'
  }
});
  • 运行时双向通信

wujie提供了事件总线模块, 基于此能够快速实现运行时双向通信,在此不赘述。

相较于qiankun沙箱的优势

qiankun 是阿里开源的一款微前端框架,和wujie一样他们也实现了一套自己的沙箱机制,用于 css 和 js 隔离,相比于wujie它的不足点如下:

qiankun 所暴露出来的不足在 wujie 中都得到了很好的解决。

使用方式

流程

研发侧

  • 提供 SDK 打包产物

wujie 采用的是 html-entry 的方式去加载子应用,所以 SDK 构建的产物必须是一个包含 html 文件的完整项目,例如下面例子:

wujie在第三方SDK开发中的应用实践

  • 提供 SDK 接入工具

    SDK 接入工具需要遵守两个原则:

    • 能够适配用户所有框架的项目(React、Vue、Angular、无框架)

    • 不需要进行依赖管控

      wujie 为每个框架都提供了相应的快速接入子应用的工具,例如 wujie-vue2wujie-vue2wujie-react,但这种接入工具需要对用户的依赖环境有要求,比如说 wujie-react。

       {
        "name": "wujie-react",
        "version": "1.0.17",
        "description": "无界微前端React组件封装",
        "main": "./lib/index.js",
        "module": "./esm/index.js",
        // ...
        "peerDependencies": {
          "react": ">=16.0.0" //要求react版本一定要大于react
        },
        // ... 
      }
      
      

      他要求用户环境的 react 版本必须要大于等于 16,这对于客户的一些老项目来说是不现实的。

    基于这两条原则,我们将采用 wujie 提供的原生启动子项目的方案,下面是基于该方案的上层封装,最终的构建产物以 IIFE 的形式运行在用户的环境,将 SDK 接入工具挂载到 window 对象上:

    1、安装wujie

    npm install wujie -S
    

    2、封装接入工具

    import { startApp, bus , EventBus} from "wujie";
    const sdkSandbox = {
        startApp({name: sting, url: string, container: HTMLElement}): void;
        bus: EventBus;
    };
    // 给用户提供启动 SDK 的方法
    sdkSandbox.startApp = function({name, url, container}) {
        startApp({ name, url, el: container, sync: true });
    }
    // 为用户提供bus通信工具
    sdkSandbox.bus = bus;
    // 将sdkSandbox挂载到用户环境的window对象中
    window.sdkSandbox = sdkSandbox;
    

    3、产出 umd 格式的产物

用户侧

  • 引入 SDK 接入工具

在 html 文件中通过 script 标签从用户服务器上拉取 sdk 接入工具,在 window 对象上完成 sdk 接入方法的挂载。

<!doctype html>
<html ang="en" translate="no">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="shortcut icon" href="">
    // 引入sdk接入工具
     <script src="/xxx/sdkRegister.js"></script>
</head>
<body>
    <div id="react-app"></div>
</body>
</html>
  • 渲染 SDK

下面以 React 项目为例展示 SDK 的渲染

const TextDiffPage = () => {
    useEffect(() => {
        window.sdkSandbox.startApp('/xxx/textdiffSdk', document.getElementById('textdiffContainer'))
        // 监听事件
        window.sdkSandbox.bus.$on("事件名字", function (arg1, arg2, ...) {});
        
        return () => {
            window.sdkSandbox.bus.$off("事件名字", function (arg1, arg2, ...) {});
        }
    })
    return (
        <div>
            <Header />
            <div id="textdiffContainer"></div>
        </div>
    )
}

问题和挑战

跨域请求

子应用的资源和接口的请求都在主域名发起,所以会有跨域问题,子应用必须做 CORS 设置:

app.use((req, res, next) => {
  // 路径判断等等
  res.set({
    "Access-Control-Allow-Credentials": true,
    "Access-Control-Allow-Origin": req.headers.origin || "*",
    "Access-Control-Allow-Headers": "X-Requested-With,Content-Type",
    "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS",
    "Content-Type": "application/json; charset=utf-8",
  });
  // 其他操作
});

身份丢失

identity discontinuity(身份丢失问题)在基于 iframe 的沙箱中是一个凸现的问题。所有非简单类型(Non-primitive)在跨 iframe 传导中均存在此问题。

const globalOne = window;
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
console.assert(iframeArray !== Array);
const list = iframeArray('a', 'b', 'c');
list instanceof Array; // false
[] instanceof iframeArray; // false
Array.isArray(list); // true

identity discontinuity(身份丢失问题)是TC39对此问题的官方命名, 上述例子也来源于TC39,身份丢失问题最大影响是阻碍应用间通信。非简单类型在通信时会出现错误判断、无法判断的问题。

cookie鉴权

wujie 的 js 沙箱是基于 iframe 实现的,即子应用的所有请求都会在 iframe 中被发起,如果此时子应用和主应用域名不同,那么子应用在发起请求时拿不到主应用的 cookie,也就无法做鉴权。目前作者准备往 idass 方向进行探索。

总结

本篇文章主要是探讨了 wujie 在 SDK 交付的场景下的一些实践,相比于传统的 iframe 来说 wujie 不仅拥有更全面的功能,而且更是一种技术趋势,但是 wujie 的沙箱机制也不是十全十美的,也存在一些暂时解决不了的问题,比如身份丢失,同时在处理 cookie 鉴权方面也没有提供好的处理方式,这些都需求开发着自己结合当前场景去探索。