微前端在网易七鱼的实践

1、前言

网易七鱼是提供围绕客户服务与智能营销的 SaaS 平台。在七鱼业务中,有在线系统、呼叫系统、机器人、工单系统、数据大屏等业务线,它们分布在两个业务端,管理端和客服端。这两个端的功能框架相似,都是由外层框架(顶部导航、一级菜单)及中间的内容区组成。css

2、业务现状

随着业务体量的增大与功能的增多,主系统做为一个巨石应用复杂度愈来愈高,全部的业务线耦合在一块儿,在系统构建、业务分离、开发维护方面带来了新的挑战。html

为解决以上问题,咱们最初采用了 「MPA + iframe」 的技术方案。先按业务维度从巨型单体应用中拆分出多个子应用,并用 React 技术栈对它们进行了重构,经过 iframe 的方式隔离新老技术栈。这些子应用基于 URL 解耦,每一个子应用能够独立开发、运行和部署。前端

采用「MPA + iframe」 的技术方案是一把双刃剑,用它能够较方便地解决现有的问题,但同时也带来了一些新的问题。react

MPA 方案能够容许子应用使用不一样技术栈,父子应用之间自然隔离,可是浏览器页面跳转时不能保持单页应用的流畅体验,父子应用通讯困难。webpack

iframe 能够方便地隔离新老技术栈,可是也带来了一些问题:git

问题 举例 较好的解决方案
父子框架 URL 不一样步、浏览器前进后退按钮异常 -- 定义父子框架路由映射,利用 postMessage 和 history API 解决
父子框架 UI 不一样步 遮罩层只能遮盖 iframe 所在的区域、iframe 内的弹框没法相对外层页面居中
子框架的全局上下文与父框架彻底隔离,致使父子框架通讯困难、同步数据冗余 --
加载慢,体验较差 --

项目最开始时采用的开发框架是 NEJ(Nice Easy Javascript),它的依赖管理系统、控件系统等特性为早期的项目开发作出了很大的贡献,如今它完成了本身的历史使命,项目开始向 React 技术栈过渡。github

下图展现了应用框架现状:web

应用框架现状

能够看到,整个系统中使用了 NEJReact 两套技术栈。npm

React 外层框架内部嵌入的是 React 应用,这些应用分别引用了各自的外层框架,并经过 React 业务组件库复用。json

NEJ 外层框架内部的状况则比较复杂,部分场景嵌入的是 NEJ 应用,还有部分场景是经过 iframe 嵌入的 React 应用,这些 React 应用中的部分页面中也有经过 iframe 再次嵌入 NEJ 应用的场景。

由于 NEJ 老技术栈的组件支持匮乏,并且历史遗留代码较多,致使它们的开发和维护成本都很高。

目前前端工程正处于技术栈统一的过渡期,须要维护两套外层框架,后续将逐渐由 NEJ 转向 React。对于新增的应用,则直接采用 React 技术栈。

随着新应用的增多,外层框架被引用的次数愈来愈多,每次更新都须要发布多个应用,使用新技术栈外层框架的维护成本为愈来愈高。

微前端是目前比较火的话题,它是微服务在前端领域的扩展。它将前端总体拆分为多个更小、更易管理的片断,能够解决工程复杂度高、多技术栈共存、开发维护困难等问题。微前端的两大特性微应用技术栈无关,每一个微应用能够独立开发、运行和部署,能够很好的匹配现有的业务场景。

所以咱们将目光转到了对现有应用进行微前端改造上。

3、微前端改造

改造的好处

将现有的应用进行微前端改造能够带来如下好处:

  • 积累实践经验,为未来从巨石应用拆分及微前端改造作准备;
  • 去除接入二方应用时使用的 iframe,优化产品体验;
  • 收敛外层框架,提高研发效率,下降维护成本;
  • 提供前端增量升级能力,后续能够更好地复用历史代码、实施渐进式重构;

社区内的微前端解决方案有许多种,包括:

  • Single-spa:只解决了应用之间的加载方案,没有考虑其余的周边问题;
  • qiankun:基于 single-spa,提供了更加开箱即用的 API,具有 JS 沙箱、样式隔离、子应用并行等能力;
  • Icestark:约束了框架应用必须基于 React,不利于后续的技术栈优化;
  • Magix:适合作单页应用的项目,不支持多个实例,不知足业务需求;
  • Luigi:是一个基于 iframe 的微前端框架,仍有前文提到的 iframe 带来的产品体验问题;
  • Ara Framework:是一个基于 Airbnb's Hypernova 的,由服务端渲染延伸出的微前端框架,接入时对原应用的侵入较多;
  • WidgetJS:是一个轻量级的微前端方案,文档不够友好;

综合考虑业务场景、上手难度、文档友好性、代码入侵性、可维护性等方面,最终选择的微前端解决方案是 qiankun。接下来就是基于 qiankun 的微前端改造了。

业务分析与改造效果

七鱼的微前端改造,从技术层面涉及到 React、NEJ 两类技术栈,从业务层面涉及到管理端、客服端。

由于最终目的是全部前端工程统一到 React 技术栈,而管理端部分应用的外层框架已经用 React 重构过,因此先从管理端下手。

首先分别重新、老技术栈应用中选取一个应用进行改造,积累相关经验。应用选择的标准是无复杂的业务逻辑、流量少,以下降改造风险。新技术栈应用选的是首页应用,老技术栈应用选的是数据大屏应用。

来看一下七鱼微前端改造后的主页:

七鱼微前端改造后主页

这里说明两个概念,基座应用(也称为主应用、框架应用等)和子应用(也称为微应用):
**

  • 基座应用负责总体布局、子应用的配置和调度,通常包含各个子应用公有的部分,好比外层框架;
  • 子应用负责自身业务逻辑的渲染;

能够看到,上图用红框标出了主页的两个组成部分,外层框架(顶部导航、一级菜单)和中间内容区。

外层框架就是由基座应用控制的,经过监听 URL 进行路由分发、子应用调度等。内容区由一个或多个子应用控制,上图中的内容区就是由一个首页子应用控制的。

大体的改造步骤

  1. 建立管理端基座工程 basic-admin;

    1. 基座应用只包含各个子应用共有的部分;
  2. 建立首页子工程 micro-index、大屏子工程 micro-bigscreen,以及相应的应用和集群;
  3. 在项目的入口文件里,暴露相应的生命周期钩子,供 qiankun 识别;
  4. 修改打包配置,使物料以 umd 的方式输出,以 webpack 为例:
const webpackConfig = {
    //...
    output: {
        //...
        library: `${packageName}-[name]`, // 此处的packageName为子应用名,如micro-bigscreen
        libraryTarget: 'umd',
        jsonpFunction: `webpackJsonp_${packageName}`,
    }
};
  1. 新增微应用对应的内部路由,改造网关:

    1. 内部路由用于注册子应用,正常状况下用户没法直接访问到;
    2. 改造后的网关须要将全部匹配到基座 URL 前缀的请求,都定向到基座应用;
  2. 兼容七鱼 PC 客户端(低版本 Chrome 浏览器内核):

    1. qiankun 加载资源时依赖的 fetch API 的兼容性问题;
    2. 由于 height 继承等致使的样式问题;
  3. 在基座应用中调用 qiankun 的 API,将子应用注册到基座应用,如:
registerMicroApps(
  [
    {
      name: 'micro-index',
      entry: '//' + location.hostname + '/_MicroIndex',
      container: '#subapp-container',
      activeRule: '/madmin/home',
    },
    {
      name: 'micro-bigscreen',
      entry: '//' + location.hostname + '/_MicroBigscreen/index',
      container: '#subapp-container',
      activeRule: '/madmin/dashboard',
    }
  ]
);

4、微前端架构下的业务变化

服务网关的变化

微前端改造后,全部管理端相关子应用的 URL 前缀为「/madmin/」,如主页的 URL 为「/madmin/home/」。服务网关须要将全部以「/madmin/」开头的路由定向到管理端基座应用。

结合网关的微前端架构图以下:

微前端架构图

子应用的开发模式

子应用有独立的仓库,部署完以后,将应用的发布产物注册到基座应用里,这些产物能够是子应用的访问地址,也能够是资源配置对象(scripts + styles + html)。

须要注意的是,在子应用与基座应用开发联调时,子应用读取的是基座应用的同步数据,Mock 的同步数据须要在基座应用中配置。同理,子应用用到的接口代理也须要在基座应用中配置全。

基座应用的总体流程

基座应用启动后会监听 URL 变化,当用户访问系统时,根据当前访问的 URL 和注册的路由信息,可以匹配到当前须要加载的子应用信息,而后去加载子应用的资源并渲染子应用

当用户点击触发跳转时,若是路由变化触发的是一个内部 URL 跳转,会直接根据应用内部的路由逻辑渲染页面。若是路由变化触发的是跨应用的跳转,则从新回到上面的路由匹配的流程中。

下图是微前端改造后的应用框架:

微前端改造后的应用框架

按照上述的子应用改造过程,能够逐步完成管理端的微前端改造。接下来就是对客服端的微前端改造了。

虽然客服端与管理端的框架结构相似,可是它们的 URL 是解耦的,并且它们一级菜单和顶部导航的业务功能差异较大,共用同一个基座应用会致使应用复杂度太高,最好是另外建立一个客服端专用的基座应用,两个基座应用经过业务组件库复用组件。

将来总体的应用框架以下:

将来总体的应用框架

有了微前端的助力,整个系统能够更加平滑地进行技术栈升级,最终实现前端技术栈的统一,更高效地赋能业务发展。

5、遇到的问题及解决方案

一、子应用接入基座应用后,babel-polyfill 报错

babel-polyfill 不支持引用屡次(基座应用和子应用分别引用了一次),直接去除 babel-polyfill 会致使没法单独运行子应用,能够改用 idempodent-babel-polyfill

二、基座应用访问子应用资源报 404 错误

资源路径有问题,须要配置运行时的 public path。

if (window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} else {
    __webpack_public_path__ = window.location.protocol + "//" + window.location.host + "/";
}

三、报错提示找不到子应用容器

将 sandbox 设置为 strictStyleIsolation,会启用严格的样式隔离,原理是把子应用内容渲染到基座容器的 shadow dom 中,致使没法直接获取基座应用的 dom 元素。

取消 strictStyleIsolation,只设置 jsSandBox 为 true 就不会有问题。

样式隔离的最佳实践是采用约定式隔离:用 CSS 命名空间、CSS Module、css-in-js 等工程化手段,避免写全局样式。

四、本地联调时基座应用访问子应用资源时报跨域错误

开发环境使用 browserSync 进行浏览器同步,qiankun 框架经过浏览器的 fetch API 获取子应用的资源,会存在跨域问题,因此须要设置 cors 为 true。

browserSync({
  //...
  cors: true
});

五、子应用引入 qiankun 生命周期后,没法独立运行

添加条件判断,非 qiankun 环境下,走以前的运行环境。
修改 'entry.js' 的 render 条件:

if (!window.__POWERED_BY_QIANKUN__) {
  ReactDOM.render(
    <Root store={store} history={history} routes={routes}/>,    document.getElementById('react-content')
  );
}

六、本地联调时子应用由于有热加载致使报错

使用 ScriptExtHtmlWebpackPlugin 插件修改 webpack 配置,为每一个页面的入口 js 加 entry 属性。

tplPlugins.push(
  new ScriptExtHtmlWebpackPlugin({
    custom: {
      test: /(?<!vendors.*)entry\.js$/,
      attribute: 'entry'
    }
  }
));

七、本地联调时子应用调用 Mock 接口或同步数据报错

在子应用与基座应用开发联调时,子应用读取的是基座应用的全局配置。本地环境基座应用可能接入不少子应用,其余子应用用到的接口代理要配全,不然调不到接口。同理,Mock 的同步数据也要在基座应用配置全。

八、低版本浏览器加载资源时 cookie 丢失

qiankun 框架经过浏览器的 fetch API 获取子应用的资源。Chrome 内核71及以前的版本,即便网址与调用脚本同源,fetch API 也不会自动发送 cookie。

须要在基座应用中启动应用时,对 fetch 进行显式的参数配置:

qiankun.start({
  //...
  fetch: (url, init) => {
    return window.fetch(url, {
      ...init,
      credentials: 'same-origin'  // 在当前域名内自动发送 cookie
    });
  }
});

九、非 React 环境引入 qiankun 生命周期的方式

定义一个与子应用名称一致的全局变量,生命周期钩子函数必须返回 promise,若是不支持 promise 须要引入 promise-polyfill。入口文件能够这样写:

(function(win) {
    // 此处的'micro-bigscreen'与注册到基座应用的子应用名称一致
    win['micro-bigscreen'] = {
        bootstrap: function() {
            // 必须返回promise,不然子应用没法正常启动
            return Promise.resolve();
        },
        mount: function() {
            return Promise.resolve();
        },
        unmount: function() {
            return Promise.resolve();
        }
    };
})(window);

十、PC 客户端子应用变量访问报错:Uncaught TypeError: 'get' on proxy

PC 客户端注入了 window.cefQuery 与 window.cefQueryCancel 变量,它们的属性描述符中 writable 与 configurable 都为 false,通过 JS 沙箱 Proxy 后直接访问它们会报错:Uncaught TypeError: 'get' on proxy。

由于只有子应用用到了沙箱,此报错只会影响子应用,基座应用不受影响。

解决方法是:分别从 window.cefQuery 与 window.cefQueryCancel 复制出新的变量 window.cefQuery2 与 window.cefQueryCancel2,修改它们的属性描述符 writable 与 configurable 为 true。而后将微前端子应用中引用 window.cefQuery 与 window.cefQueryCancel 的地方分别修改成 window.cefQuery2 与 window.cefQueryCancel2。

基座应用中的相关代码:

const polyfillPcPlatform = () => {
  if (window.cefQuery) {
    Object.defineProperty(window, 'cefQuery2', {
      value: window.cefQuery,
      writable: true,
      configurable: true
    });
  }
  if (window.cefQueryCancel) {
    Object.defineProperty(window, 'cefQueryCancel2', {
      value: window.cefQueryCancel,
      writable: true,
      configurable: true
    });
  }
};

//注册子应用
registerMicroApps(
  [
    //...
  ],
  {
    beforeLoad: [
      app => {
        // 兼容PC客户端
        polyfillPcPlatform();
      }
    ],
    //...
  }
);

6、总结

本次微前端实践基于 qiankun 框架,建立了管理端基座应用,将管理端首页和数据大屏应用进行了微前端改造,改造涉及 React 和 NEJ 两套技术栈,达到了如下目的:

  1. 积累了微前端实践经验,为未来从巨石应用拆分及微前端改造作准备;
  2. 使管理端不一样技术栈的二方应用接入再也不须要使用 iframe,优化了产品体验;
  3. 收敛了管理端外层框架,使新应用的接入再也不须要理会顶部导航和一级菜单;
  4. 提供了前端增量升级能力,后续能够更好地复用历史代码、实施渐进式重构;

微前端不是一个框架,而是一套架构体系,基座应用的建立和子应用的改造是它的基础设施,除了基础设施外还有配置中心和观察工具。配置中心包括参数配置、版本管理、发布策略等。观察工具备必定的运维职能,包括应用状态的可见、可控性等。

有了上述能力后,能够经过它们统一管控全部的微应用,为 SaaS 产品提供自由组合的能力,使技术为业务带来更大的价值。

更多技术内容,欢迎关注【网易智企技术+】公众号。

相关文章
相关标签/搜索