乐趣区

关于前端:微前端在网易七鱼的实践

一、前言

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

二、业务现状

随着业务体量的增大与性能的增多,主零碎作为一个巨石利用复杂度越来越高,所有的业务线耦合在一起,在零碎构建、业务拆散、开发保护方面带来了新的挑战。

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

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

MPA 计划能够容许子利用应用不同技术栈,父子利用之间人造隔离,然而浏览器页面跳转时不能放弃单页利用的晦涩体验,父子利用通信艰难。

iframe 能够不便地隔离新老技术栈,然而也带来了一些问题:

问题 举例 较好的解决方案
父子框架 URL 不同步、浏览器后退后退按钮异样 定义父子框架路由映射,利用 postMessage 和 history API 解决
父子框架 UI 不同步 遮罩层只能遮蔽 iframe 所在的区域、iframe 内的弹框无奈绝对外层页面居中
子框架的全局上下文与父框架齐全隔离,导致父子框架通信艰难、同步数据冗余
加载慢,体验较差

我的项目最开始时采纳的开发框架是 NEJ(Nice Easy Javascript),它的依赖管理系统、控件零碎等个性为晚期的我的项目开发做出了很大的奉献,当初它实现了本人的历史使命,我的项目开始向 React 技术栈过渡。

下图展现了利用框架现状:

能够看到,整个零碎中应用了 NEJReact 两套技术栈。

React 外层框架外部嵌入的是 React 利用,这些利用别离援用了各自的外层框架,并通过 React 业务组件库复用。

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

因为 NEJ 老技术栈的组件反对匮乏,而且历史遗留代码较多,导致它们的开发和保护老本都很高。

目前前端工程正处于技术栈对立的过渡期,须要保护两套外层框架,后续将逐步由 NEJ 转向 React。对于新增的利用,则间接采纳 React 技术栈。

随着新利用的增多,外层框架被援用的次数越来越多,每次更新都须要公布多个利用,应用新技术栈外层框架的保护老本为越来越高。

微前端是目前比拟火的话题,它是微服务在前端畛域的扩大。它 将前端整体拆分为多个更小、更易治理的片段 ,能够解决 工程复杂度高、多技术栈共存、开发保护艰难 等问题。微前端的 两大个性 微利用技术栈无关,每个微利用能够独立开发、运行和部署,能够很好的匹配现有的业务场景。

因而咱们将眼光转到了对现有利用进行微前端革新上。

三、微前端革新

革新的益处

将现有的利用进行微前端革新能够带来以下益处:

  • 积攒实践经验,为未来从巨石利用拆分及微前端革新做筹备;
  • 去除接入二方利用时应用的 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',
    }
  ]
);

四、微前端架构下的业务变动

服务网关的变动

微前端革新后,所有治理端相干子利用的 URL 前缀为「/madmin/」,如主页的 URL 为「/madmin/home/」。服务网关须要将所有以「/madmin/」结尾的路由定向到治理端基座利用。

联合网关的微前端架构图如下:

子利用的开发模式

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

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

基座利用的整体流程

基座利用启动后会 监听 URL 变动 ,当用户拜访零碎时,依据以后拜访的 URL 和注册的路由信息,可能 匹配到以后须要加载的子利用信息 ,而后去加载子利用的资源并 渲染子利用

当用户点击触发跳转时,如果路由变动触发的是一个外部 URL 跳转,会间接依据利用外部的路由逻辑渲染页面。如果路由变动触发的是跨利用的跳转,则从新回到下面的路由匹配的流程中。

下图是微前端革新后的利用框架:

依照上述的子利用革新过程,能够逐渐实现治理端的微前端革新。接下来就是对客服端的微前端革新了。

尽管客服端与治理端的框架结构相似,然而它们的 URL 是解耦的,而且它们一级菜单和顶部导航的业务性能差异较大,共用同一个基座利用会导致利用简单度过高,最好是另外创立一个客服端专用的基座利用,两个基座利用通过业务组件库复用组件。

将来整体的利用框架如下:

有了微前端的助力,整个零碎能够更加平滑地进行技术栈降级,最终实现前端技术栈的对立,更高效地赋能业务倒退。

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

1、子利用接入基座利用后,babel-polyfill 报错

babel-polyfill 不反对援用屡次(基座利用和子利用别离援用了一次),间接去除 babel-polyfill 会导致无奈独自运行子利用,能够改用 idempodent-babel-polyfill。

2、基座利用拜访子利用资源报 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 + "/";}

3、报错提醒找不到子利用容器

将 sandbox 设置为 strictStyleIsolation,会启用严格的款式隔离,原理是把子利用内容渲染到基座容器的 shadow dom 中,导致无奈间接获取基座利用的 dom 元素。

勾销 strictStyleIsolation,只设置 jsSandBox 为 true 就不会有问题。

款式隔离的最佳实际是采纳约定式隔离:用 CSS 命名空间、CSS Module、css-in-js 等工程化伎俩,防止写全局款式。

4、本地联调时基座利用拜访子利用资源时报跨域谬误

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

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

5、子利用引入 qiankun 生命周期后,无奈独立运行

增加条件判断,非 qiankun 环境下,走之前的运行环境。
批改 ‘entry.js’ 的 render 条件:

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

6、本地联调时子利用因为有热加载导致报错

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

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

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

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

8、低版本浏览器加载资源时 cookie 失落

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

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

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

9、非 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);

10、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();}
    ],
    //...
  }
);

六、总结

本次微前端实际基于 qiankun 框架,创立了治理端基座利用,将治理端首页和数据大屏利用进行了微前端革新,革新波及 React 和 NEJ 两套技术栈,达到了以下目标:

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

微前端不是一个框架,而是一套架构体系 ,基座利用的创立和子利用的革新是它的基础设施,除了基础设施外还有 配置核心和察看工具 。配置核心包含 参数配置、版本治理、公布策略 等。察看工具有肯定的运维职能,包含 利用状态的可见、可控性 等。

有了上述能力后,能够通过它们对立管控所有的微利用,为 SaaS 产品提供自由组合的能力,使技术为业务带来更大的价值。

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

退出移动版