什么是微前端(Micro Frontends)
微前端一词于 2016 年底首次出现在ThoughtWorks 中。这个概念将微服务这个被广泛应用的于服务端的技术范式扩展到前端领域,是构建一个能让 多个团队单独交付项目代码的 现代web应用使用的技术、策略和方法。
为什么使用微前端
对于一些巨石应用,将这些大而可怕的事务分割成更小、更易于管理的部分,然后明确他们之间的依赖关系,我们在技术选择、代码库、团队以及发布过程都应该能够做到互相独立的操作和迭代,而不需要过度的协调,这种将web应用由单一单体应用转变为多个小型前端应用聚合为一的思想, 便体现出它的核心要素:
- 拆分巨石应用,使应用方便迭代更新,微应用不限技术、独立仓库、独立部署
- 兼容历史应用,实现增量开发
- 独立的运行时,每个微应用之间状态隔离,运行时状态不共享
技术方案
这里主要介绍qiankun, 它是基于single-spa(最早实现的微前端框架)封装的微前端框架的实现库
为什么不用iframe?
- url不同步
- UI不同步
- 全局上下文完全隔离,内存变量不共享
- 慢,每个子应用进入都是一次浏览器上下文重建、资源重新加载的过程
为什么不用MPA?
- 体验割裂
- 不支持局部渲染
快速上手
1.主应用
这里都以简单的vue2为例,对于主流前端框架使用webpack编译工具的入口和打包形式大同小异,不过会有略微配置区别
- 安装乾坤
// 一个正常的vue项目添加乾坤
yarn add qiankun # 或者 npm i qiankun -S
- 在主应用中注册微应用
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'about', // 子应用名称
entry: '//localhost:7100', // 子应用entry[html]入口
container: '#MICRO_CONTAINER', // 挂载节点
activeRule: '/about', // url base微应用框架
},
{
name: 'dashboard', // 子应用名称
entry: '//localhost:7101', // 子应用entry[html]入口
container: '#MICRO_CONTAINER', // 挂载节点
activeRule: '/dashboard', // url base微应用框架
},
]);
start();
当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。
2. 微应用
微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用, 不过需要在微应用中做细微的修改
- 导出相应的生命周期钩子 微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
/**
bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重 复触发 bootstrap。
通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('app bootstraped');
}
。。。
- 本地启动主应用和子应用,就能看到跑起来的项目
- 配置微应用的打包工具 除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
const packageName = require('./package.json').name;
module.exports = {
module.exports = {
devServer: {
port: 7100,
headers: {
'Access-Control-Allow-Origin': '*'
}
},
configureWebpack: {
output: {
library: `${name}`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
}
}
};
核心特点之 js沙箱隔离
乾坤主要使用快照沙箱和代理沙箱,代理沙箱隔离更彻底 每个应用都创建一个proxy来代理window,好处是每个应用都是相对独立,不需要直接更改全局window属性!
class ProxySandbox {
constructor() {
const rawWindow = window;
const fakeWindow = {}
const proxy = new Proxy(fakeWindow, {
set(target, p, value) {
target[p] = value;
return true
},
get(target, p) {
return target[p] || rawWindow[p];
}
});
this.proxy = proxy
}
}
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.a = 1;
((window) => {
window.a = 'hello';
console.log(window.a)
})(sandbox1.proxy);
((window) => {
window.a = 'world';
console.log(window.a)
})(sandbox2.proxy);
部署
当主应用和微应用部署在不同的服务器,需要使用 Nginx 代理
一般这么做是因为不允许主应用跨域访问微应用,做法就是将主应用服务器上一个特殊路径的请求全部转发到微应用的服务器上,即通过代理实现“微应用部署在主应用服务器上”的效果。
例如,主应用在 A 服务器,微应用在 B 服务器,使用路径 /app1 来区分微应用,即 A 服务器上所有 /app1 开头的请求都转发到 B 服务器上。
qiankun原理
qiankun核心原理其实也就是single-spa的核心原理
single-spa的原理其实很简单,它就是一个子应用加载器 + 状态机的结合体,而且具体怎么加载子应用还是基座应用提供的;框架里面维护了各个子应用的状态,以及在适当的时候负责更改子应用的状态、执行相应的生命周期函数
- 源码分析 从 rollup.config.js 中可以看出,入口文件为 /src/single-spa.js,这个文件导出很多方法和值
- registerApplication注册子应用 single-spa/src/applications/apps.js
sanitizeArguments(appNameOrConfig, appOrLoadApp, activeWhen,customProps),格式化用户传递的应用配置参数
getAppNames() ,去重判断
apps.push(app),将新的应用最加到apps中,并添加一些内置属性status、loadErrorTime、parcels、devtools
ensureJQuerySupport() jQuery打补丁
reroute(),更改app.status和执行生命周期函数