微前端解读一

286 阅读7分钟

一、背景

随着时间的推移,大型企业或团队的组织结构、软件架构都在不断的更新变化。随之而来的便是多渠道、多样化体验以及对不同技术栈的引入。同时,业务也在不断扩展,导致各个项目越来越臃肿,应用越来越难以维护。

111.png

而现有的 Web SPA 针对上述问题,并不能很好的拓展和部署。

二、微前端定义

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. — Micro Frontends

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

微前端架构具备的几个核心价值:技术栈无关、独立开发、独立运行、独立部署、增量升级。

微前端的核心设计理念:解耦、聚合、技术栈无关。其思路来源于微服务的思想。将微服务的理念应用在浏览器端。即将 Web 应用由单一的单体应用转变为多个微应用聚合为一的应用

目前主流的框架有 Single-SPAqiankunicestarkMooa,其中qiankunMooa都是基于 Single-SPA 的封装。而Mooa是一款为Angular服务的为前端框架。

image.png

2.1 微应用

微应用是指在开发时应用都是以单一、微小应用的形式存在的,而在运行时,则是通过构建系统合并这些应用,并组合成一个新的应用。

2.2 为什么不是iframe

iframe 作为一个非常古老的技术,却一直很管用。iframe 可以创建一个全新的独立的宿主环境,这意味着我们的前端应用之间可以相互独立运行。从这个角度来看,iframe又何尝不是微前端的一种实现呢。

既然如此,为什么不用 iframe,为什么大部分微前端方案又不约而同放弃了 iframe 方案呢?

主要可以从以下几个方面分析:

  1. url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  2. UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
  3. 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。

2.3 Web Components

Web Components 是一套不同的技术,允许您创建可重用的定制元素(它们的功能封装在您的代码之外)并且在您的web应用中使用它们。

image.png

三、微前端落地时架构技术选择

  • 从架构上,常规web应用的架构类型分为两种,一种是MPA,另一种是SPA。它们各有各的优缺点。
    • MPA的优点在于部署简单,具备独立开发和独立部署的特性。但是,它的缺点是页面跳转时需要重新加载整个页面。
    • SPA能极大保证多个任务之间串联的流畅性,内容的改变不需要重新加载整个页面,但问题是通常一个SPA是一个技术栈的应用,很难共存多个技术栈方案的选型,且不利于SEO。
  • 从运行特性上,微前端包含两个类别,一类是单实例,另一类是多实例。

image.png

微前端架构的核心诉求是实现能支持自由组合的微前端架构,将其他的SPA应用以及其他组件级别的应用自由的组合到平台中。

四、技术细节上的决策

为了实现上述的方案,在技术细节上的决策需要注意以下问题:

1、是如何做到子应用之间的技术无关;

2、是如何设计路由和应用导入;

3、是如何做到应用隔离;

4、是基础应用之间资源的处理以及跨应用间通信的选择。

下面以 qiankun为例:

导出相应生命周期的钩子

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log('react app bootstraped');
}


/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}


/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props) {
  ReactDOM.unmountComponentAtNode(
    props.container ? props.container.querySelector('#root') : document.getElementById('root'),
  );
}


/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

对于应用的导入方式的选择:Config EntryHTML Entry

  • Config Entry: 配置每个子应用的 js 和 css,包含内联的部分。(不推荐)

通过在主应用中注册子应用依赖哪些JS。这种方案一目了然,但是最大的问题是ConfigEntry的方式很难描述出一个子应用真实的应用数据信息。真实的子应用会有一些title信息,依赖容器ID节点信息,渲染时会依赖节点做渲染,如果只配JS和CSS,那么很多信息是会丢失的,有可能会导致间接上的依赖。

loadMicroApp({
  name: 'configEntry',
  entry: {
    scripts: ['//t.com/t.js'],
    styles: ['//t.com/t.css'],
  },
});
  • HTML Entry: Config Entry 的进阶版,简化开发者使用,但是把解析消耗留给了用户

HTML Entry直接接入访问地址。使得应用的信息可以得到完整的保留,且接入应用地址只需配一次,子应用的原始开发模式得到完整保留,因为子应用接入只需要告知主应用url在哪,包括在不接入主应用时独立的打开。它的缺点是将解析的消耗留给了运行时。

import { registerMicroApps, start } from 'qiankun';


registerMicroApps([
  {
    name: 'reactApp',
    entry: '//localhost:3000',
    container: '#container',
    activeRule: '/app-react',
  },
  {
    name: 'vueApp',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/app-vue',
  },
  {
    name: 'angularApp',
    entry: '//localhost:4200',
    container: '#container',
    activeRule: '/app-angular',
  },
]);
// 启动 qiankun
start();

对于css隔离问题: 子应用之间样式互不影响,切换时加载与卸载

  • 动态样式表:根据生命周期装载、卸载样式表
  • 工程化手段(BEM、CSS Modules、CSS in JS、Web Components)
  • Shadow DOM:Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样,隐藏的 DOM 样式和其余 DOM 是完全隔离的,类似于 iframe 的样式隔离效果。

对于js隔离:通过 Proxy 来实现的沙箱模式,即在应用的 bootstrap 及 mount 两个生命周期开始之前分别给全局状态打下快照,然后当应用切出/卸载时,将状态回滚至 bootstrap 开始之前的阶段,确保应用对全局状态的污染全部清零。

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。简单来说就是,可以在对目标对象设置一层拦截。无论对目标对象进行什么操作,都要经过这层拦截。

优点: 可以同时运行多个沙箱;不会污染 window 环境

结语

以上便是我在对微前端的一些了解,如果有不对的地方欢迎指正。之后我也会结合业务实践,输出对 qiankun 的一些思考。

参考文献