面向大型工做台的微前端解决方案 icestark

2017 年中旬,ICE 团队接到一个叫作「阿里创做平台」的项目,这个产品为创做者提供了入驻、账号管理、内容管理、内容发布、粉丝运营、数据分析等等很是完备的功能,页面数 50+、项目一期有 3-6 个前端同时开发、业务将来有二方业务接入的需求……针对这些问题,传统的单页面应用方案实在有点力不从心,所以在详细的技术评估以后咱们最终自研了一套叫作 AppLoader 的方案,而在此时社区可能尚未微前端这个概念。事实上这个方案也是近乎完美的解决了咱们的业务问题,但因为场景比较受限所以咱们一直没有将其对外。javascript

2019 年年初,淘宝有一个很是重要的项目「小二工做台」,简单来说「小二工做台」要打造面向运营小二的操做系统,解决多个后台体验不一致、频繁跳转效率低、重复建设等问题,因而 AppLoader 重出江湖。同时结合淘宝业务发展以及微前端这个概念在社区的普及度,咱们判断将来此类业务场景会愈来愈多,所以咱们对 AppLoader 作了一次能力和品牌的升级,同时面向社区开源,这即是 icestark 的由来。css

业务背景

智者说:抛开业务场景谈技术方案(微前端)都是瞎扯淡,所以在介绍 icestark 以前,咱们先了解下当时面临的业务背景是怎样的。前端

如前文所说,两年多以前咱们接到「阿里创做平台」这个项目,这个产品承载了创做者从入驻到创做完整的生命周期,相比于普通业务有如下两个不一样点:页面数很是多、将来有二方业务接入的需求,针对这两个特色前端方案须要核心解决的问题:vue

  • 用户端必须是「一个系统」的心智,从域名到体验
  • 可以根据功能拆分红多个子应用,每一个子应用独立开发独立部署
  • 子应用尽可能保证跟传统单页面应用同样的开发体验,不要让开发者有太多学习成本
  • 全部子应用可被统一管理起来,不能无限制的泛滥

其中第二点提到的拆分子应用是方案的核心,在上面所说的业务背景下,这个项目的代码量必定会快速增多,那么若是是经过一个前端应用来管理全部代码,不管是当下流行的单页面应用仍是传统的多页面应用都没法规避如下问题:java

  • 代码量达到必定量的时候,单次构建时间很长,开发&发布效率极低
  • 代码库中依赖升级会影响整个应用,而代码量又很是大,致使回归成本极高
  • 变成一个很是臃肿的巨石应用,彻底失去灵活性,不管是多人协做仍是业务接入成本都会大大增长

针对这些问题咱们作了一轮完整的技术调研。react

技术调研

单/多页面应用

如上所述,不具备可行性。git

iframe

每一个子应用独立开发独立部署,而后经过 iframe 的方式将这些应用嵌入到同一个系统中,这是一个很完全的方案同时也是使用最多的一个方案,但 iframe 的体验问题一直是个难以解决的问题:github

  • iframe 体验问题:页面加载慢;双滚动条问题;内部蒙层没法遮罩到外部框架,同时布局也没法居中;内部跳转后外部无响应,刷新后又会回到 iframe 首页……
  • 每一个子应用须要依赖服务部署,方案变重,成本增长,同时可能会引入跨域之类的问题

这些问题有的能够解决,有的很难解决,有的几乎没法解决,所以咱们舍弃了 iframe 的方案。npm

封装框架组件

封装一个统一的框架 UI 组件,每一个子应用自行接入框架组件,框架组件能够是一个 npm 或者覆盖式的 cdn 资源或者 vmcommon,这个就相似淘宝 PC 端业务接统一吊顶的场景。可是这个方案有如下几个问题:后端

  • 访问入口不统一,本质仍是多个系统,只是看起来框架是一致的
  • 子应用没法被统一管控,可能会无限制的增长

事实上这个方案更适合于各个应用很是独立,从前端到后端服务所有都归属于某个业务方,并且各个业务方之间也比较独立,没有中心化管理的需求。

微前端?

比较遗憾的是在 2017 年中旬咱们尚未了解到「微前端」这个概念(可能还没诞生?),至于今天社区中相对有名的微前端解决方案 single-spa 可能尚未或者知名度比较小,并无进入到咱们的调研列表里。

另外,微前端这个概念我也是今年才真正接触到,也才意识到咱们两年前作的方案就是今天所谓的微前端。

自研 icestark

完成技术调研以后,咱们最终决定自研一套方案即 icestark,这套方案具体的设计思路和使用方式参考下文。

架构设计

image.png

如上图所示,首先引入框架应用和子应用的概念,框架应用负责系统总体布局以及子应用的注册、加载与渲染,同时在设计原则上咱们但愿「子应用尽可能保持跟传统单页面应用同样的开发体验」,保证子应用自身可独立运行、存量应用可快速迁移适配、增量应用跟传统方式开发体验一致。

在确认上述核心设计思路以后,接下来就是对问题进行具体拆解:子应用是一个传统的 SPA 应用(可包含一个或多个页面),会打包出 bundle 同时发布到 CDN,那么咱们须要在框架应用中注册管理全部子应用,而后在适当的时机加载对应的子应用 bundle 并将其渲染到指定节点(系统布局里面)。在这个流程里核心要解决的技术问题以下。

1. 何时加载哪一个子应用?

子应用包含多个页面即路由,只有页面路由的变化会引发子应用的切换,那么咱们只要创建子应用和路由的映射关系便可。为每一个子应用分配一个基准路由如 /seller ,这个子应用保证全部的路由定义在 /seller 下,那么当从其余路由跳转到 /seller 路由时咱们就能够加载渲染 /seller 对应的子应用 bundle 了。PS:除了基准路由这种约束方式也支持其余更加松散的方式。

2. 如何捕获到系统中全部路由的变化?

icestark 经过劫持 history.pushState 和 history.replaceState 两个 API,同时监听 popstate 事件,保证可以捕获到到全部路由变化。当捕获到路由变化时,根据路由查找对应的子应用,若是对应的仍是当前这个子应用则什么事情都不作,若是对应的是新的一个子应用则卸载以前的子应用,同时加载新的子应用并渲染之。

3. 如何将子应用的 bundle 渲染到指定节点?

框架应用有系统的 Layout,咱们须要将子应用渲染到 Layout 里面,可是单页面应用都是直接经过 ReactDOM.render(<App />, document.getElementById('#root'))  的方式渲染,若是直接执行那么渲染的位置是没法被控制的,因而 icestark 为子应用提供了一个 getMountNode() 的 API 保证子应用可以渲染到指定的节点里。

4. 子应用使用不一样的前端框架怎么办?

在咱们内部使用时其实并无考虑这个问题,由于咱们内部目前都是 React 的技术栈,基本不存在这样的问题,可是若是要将这个方案开源,那这个特性是必须支持的。

比较有意思的是回顾上述的核心设计,icestark 对子应用的约束很是简单:路由须要规范最好是经过基准路由约束、须要渲染到指定节点里,那么子应用是经过 Vue 或者 ReactDOM 亦或是 jQuery 渲染都无所谓了,总体方案对此没有任何依赖。

这即是 icestark 核心的几块设计思路,接下来咱们简单看下这套方案如何使用。

快速使用

开发框架应用

安装 iceworks CLI 工具:

$ npm i -g iceworks
复制代码

经过命令行初始化一个框架应用:

$ mkdir icestark-framework-app && cd icestark-framework-app
$ iceworks init @icedesign/stark-layout-scaffold
复制代码

完成初始化以后安装依赖而后经过 npm start 便可进行预览。

src/App.jsx 中咱们能够看到核心的子应用注册代码:

import React from 'react';
import { AppRouter, AppRoute } from '@ice/stark';

export default class App extends React.Component {
  render() {
    return (
      <BasicLayout pathname={pathname}>
        <AppRouter
          onRouteChange={this.handleRouteChange}
          onAppLeave={this.handleAppLeave}
          onAppEnter={this.handleAppEnter}
        >
          <AppRoute
            path="/seller"
            basename="/seller"
            title="商家平台"
            url={[
              '//unpkg.com/icestark-child-seller/build/js/index.js',
              '//unpkg.com/icestark-child-seller/build/css/index.css',
            ]}
          />
          <AppRoute 
			// ...
		  />
        </AppRouter>
      </BasicLayout>
    );
  }
}
复制代码

子应用注册的核心信息:

{
  // 为子应用分配的基准路由
	path: '/seller',
  // 子应用的 bundle 地址,用来渲染子应用
	url: [
  	'//unpkg.com/icestark-child-seller/build/js/index.js',
  	'//unpkg.com/icestark-child-seller/build/css/index.css',
	],
}
复制代码

开发子应用

经过命令行初始化子应用:

$ mkdir icestark-child-app-test && cd icestark-child-app-test

# 基于 React 的子应用
$ iceworks init project @icedesign/stark-child-scaffold
# 基于 Vue 的子应用
$ iceworks init project @vue-materials/icestark-child-app
复制代码

一样安装依赖执行 npm start 便可单独开发预览子应用,若是想在框架应用中预览,替换相关 bundle 地址便可。

相比于传统的单页面应用,icestark 的子应用有三个须要定制的地方:

  1. 须要主动注册&触发应用的卸载事件
  2. 应用渲染的节点须要经过 getMountNode() API 来获取
// src/index.jsx
import ReactDOM from 'react-dom';
import { getMountNode, registerAppLeave } from '@ice/stark-app';
import router from './router';

registerAppLeave(() => {
  ReactDOM.unmountComponentAtNode(mountNode);
});

ReactDOM.render(router(), getMountNode(document.getElementById('mountNode')));
复制代码
  1. 路由须要定义在约定的基准路由下面:
// src/router.jsx
import { BrowserRouter as Router, Switch } from 'react-router-dom';
import { getBasename } from '@ice/stark-app';

export default () => {
  return (
    <Router basename={getBasename()}> <Switch> // ... </Switch> </Router>
  );
};
复制代码

完整的文档请参考 子应用开发与迁移 。

沙箱与隔离

说到微前端必定逃不了沙箱的相关话题,可是针对这个问题目前业界尚未一个很是完美的机制,具体可参考文档 样式和脚本隔离,这里面有咱们的一些探索。

针对这个问题咱们的一些观点:

  • 大部分业务没有三方接入需求,支持很是完全的沙箱机制没有太大意义,至少在咱们目前落地的业务中尚未出现相互污染的问题
  • 对于可控的二方应用接入,咱们推荐进行一些规范约定便可,好比不要污染全局变量、定时器及时清除、CSS 样式尽可能经过前缀或者 CSS Modules 作隔离
  • 对于不可控的三方应用,建议暂时先经过 iframe 的方式接入
  • 沙箱机制有机会经过 Shadow DOM 和 Web Worker 之类的方案解决,还在探索中,欢迎交流

与 single-spa 的关系

icestark 与 single-spa 都属于微前端的解决方案,二者在能力上并没有太大差异,这里简单梳理下我的的一些观点:

  • single-spa 社区知名度更高,生态以及能力上会完善一些,这个是 icestark 要持续追赶的
  • 子应用方面:icestark 对子应用的侵入几乎能够忽略,使用成本更低,同时子应用也能够独立运行,而 single-spa 相对多了一些侵入,须要了解各类生命周期,子应用是否能单独运行须要确认下
  • API 设计层面:icestark 将应用路由类比为页面路由,类 react-router 的 API 设计更加简单直观一些
  • 架构设计上:icestark 更加简单,single-spa 要重一些,好比须要 single-spa-react/vue/preact 这种设计,具体能够看下二者的核心代码,其实都比较简单

另外 qiankun 是对 single-spa 的一层封装,核心作了构建层面的一些约束以及沙箱能力,构建层面的约束(好比 umd)我的以为会让子应用变复杂,不必定是一个好的方案,而后沙箱这块 icestark 是将 onAppEnter/onAppLeave 这种钩子暴露给框架应用,让业务自身去按需作一些好比全局变量冻结之类的事情。

最佳实践

  • 框架应用职责明确,只作总体布局以及子应用的注册管理,不作其余任何 UI 或逻辑,由于框架应用本质是一个中心化部件,中心化的部分越简单整个系统就越稳定
  • 子应用经过基准路由来划分,这样应用的管理更加简单直观,也很难出现应用间路由冲突的问题
  • 子应用尽可能避免依赖框架应用的能力,好比框架层面提供一些全局的 API 或者组件都是很是很差的架构设计
  • 子应用的信息推荐经过配置平台管理,而后 server 端经过全局变量的方式暴露给框架应用,这样子应用的版本变化、增长删除等都不须要依赖框架应用发布

业务落地

icestark 目前主要落地在阿里内部的业务,社区里可能也有几个项目,不过目前尚未作过专门的统计,若是有用到的话欢迎反馈给咱们。

阿里创做者平台

包含 20+ 子应用,其中 5-8 个子应用由二方业务开发。

阿里健康-熙牛医疗云医院信息系统

淘系小二工做台

面向淘系运营小二的后台都将已子应用的方式接入小二工做台,打造面向运营小二的操做系统。

微前端的将来

微前端当下主要仍是在解决工程问题,好比系统的解耦、多人协做之类的,因此其实去看下核心代码都是很是简单易懂的。在工程问题的基础上接下来咱们会有两个方向:第一是探索沙箱机制,让二方业务更加安全的运行,同时让不可控的三方业务接入逐渐成为可能;第二针对微前端的业务场景逐步完善生态,好比一些鉴权之类的业务需求,这块有需求欢迎反馈。

最后欢迎评论交流 & 点赞 & star,以及经过钉钉群跟咱们沟通。

相关连接

相关文章
相关标签/搜索