什么是微前端
简单的理解,微前端就是把一个应用当成vue组件嵌入到另一个应用。一个前端项目无非就是html、css、js三大件,微前端框架不仅要将这三大件引入到主应用,还要做好隔离防止污染(比如两个应用存在重复的class,那么就会照成样式污染,如果window下挂载了同样的方法,方法就会被覆盖)
iframe方案
优点
iframe将web应用完美隔离,无论是dom、css还是js都完全的隔离了起来- 使用简单,一个
iframe标签加一个src属性即可将子应用嵌入进来的
缺点
- dom隔离太严重,子应用的dom无法突破iframe的限制,比如一个fixed定位的元素也只能在iframe区域展示
- 路由无法保活,每次浏览器刷新iframe里的页面就会重置
- 通信困难,全局上下文完全隔离
qiankun方案
优点
- qiankun 使用 HTML entry 及 css沙箱 实现了 dom 和样式隔离
- 实现js隔离,并提供了父子通信及全局状态的方法
- 实现了资源预加载能力,预先子应用
html、js、css资源缓存下来,加快子应用的打开速度
缺点
- 子应用基于路由匹配,父子应用共用一个路由,缺乏灵活性,无法实现多应用激活
- 需要改造子应用,改造成本大
- 开启严格模式后会出现各种问题
其中共用一个路由是让我无法接受的,比如qiankun子应用在layout中,主应用切换页面时还要保持子应用匹配的部分不动。我认为的微前端就应该把子应用当成组件一样嵌入到主应用,除了父子通信外不做其他耦合。直到无界横空出世,我认为这就是我梦寐以求的微前端框架。
无界方案
无界是腾讯团队开源的微前端框架,是一款基于 Web Components + iframe 微前端框架,具备成本低、速度快、原生隔离、功能强等一系列优点
优点
- 使用
iframe作为天然的js沙箱,不会污染主应用环境 - 使用 Web Components 来隔离
html、css,这是一个浏览器原生支持的组件封装技术,可以有效隔离元素之间的样式 - 提供了事件总线和props传参,弥补了iframe通信困难的缺点
- 子应用无需做任何改造,接入更简单
- 实现了多应用激活、应用保活功能
- 提供了vue和react组件的封装,开箱即用
无界用起来感觉和iframe差不多,js沙箱直接使用iframe,dom和样式使用Web Components,巧妙的避开了iframe的缺点 感觉无界就是个缝合怪,这很Tencent
缺点
- Web Components兼容性有限,在不支持Web Components的浏览器中,无界提供了降级处理,html和css的隔离会使用iframe替代,降级后的无界其实就是个iframe了
其实无界的适配问题无伤大雅,就算有些浏览器不支持Web Components,那我们就当是用了个iframe就好了。毕竟qiankun和iframe相比,我还是觉得iframe更友好一点,更何况是拥有事件总线的iframe呢。
快速上手
官方文档:wujie-micro.github.io/doc/
github:github.com/Tencent/wuj…
安装
npm install wujie -S
使用
<template>
<div id="container"></div>
</template>
import { setupApp, preloadApp, startApp, destroyApp } from "wujie";
import { onMounted } from "vue";
// `setupApp`设置子应用默认属性,非必须。startApp、preloadApp会从这里获取子应用默认属性,如果有相同的属性则会直接覆盖
setupApp({ name: "wjapp", url: "子应用路径", el: "#container" });
// 预加载可以极大的提升子应用首次打开速度
// 由于setupApp已经定义好了默认配置,所以preloadApp只需要指定name
preloadApp({name: "wjapp"})
onMounted(()=>{
// 启动子应用 异步返回 destroy 函数,可以销毁子应用
// 可以只使用startApp来启动一个子应用,setupApp、preloadApp都是非必需的
startApp({name: "wjapp"})
})
onBeforeUnmount(()=>{
// 主动销毁子应用,承载子应用的`iframe`和`shadowRoot`都会被销毁,无界实例也会被销毁,相当于所有的缓存都被清空,除非后续不会再使用子应用,否则都不应该主动销毁。
// 参数为子应用`name`
destroyApp("wjapp")
})
setupApp配置参数
type startOption {
/** 唯一性用户必须保证 */
name: string;
/** 需要渲染的url */
url: string;
/** 渲染的容器 */
el: HTMLElement | string;
/** 子应用加载时loading元素 */
loading?: HTMLElement;
/** 路由同步开关, false刷新无效,但是前进后退依然有效 */
sync?: boolean;
/** 子应用短路径替换,路由同步时生效 */
prefix?: { [key: string]: string };
/** 子应用保活模式,state不会丢失 */
alive?: boolean;
/** 注入给子应用的数据 */
props?: { [key: string]: any };
/** js采用fiber模式执行 */
fiber?: boolean;
/** 子应用采用降级iframe方案 */
degrade?: boolean;
/** 自定义iframe属性 */
attrs?: { [key: string]: any };
/** 代码替换钩子 */
replace?: (codeText: string) => string;
/** 自定义fetch,资源和接口 */
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
/** 子应插件 */
plugins: Array<plugin>;
/** 子应用生命周期 */
beforeLoad?: lifecycle;
/** 没有做生命周期改造的子应用不会调用 */
beforeMount?: lifecycle;
afterMount?: lifecycle;
beforeUnmount?: lifecycle;
afterUnmount?: lifecycle;
/** 非保活应用不会调用 */
activated?: lifecycle;
deactivated?: lifecycle;
/** 子应用资源加载失败后调用 */
loadError?: loadErrorHandler
};
通信 bus
type callback = (...args: Array<any>) => any;
export declare class EventBus {
private id;
private eventObj;
constructor(id: string);
$on(event: string, fn: callback): EventBus;
/** 任何$emit都会导致监听函数触发,第一个参数为事件名,后续的参数为$emit的参数 */
$onAll(fn: (event: string, ...args: Array<any>) => any): EventBus;
$once(event: string, fn: callback): void;
$off(event: string, fn: callback): EventBus;
$offAll(fn: callback): EventBus;
$emit(event: string, ...args: Array<any>): EventBus;
$clear(): EventBus;
}
主应用
import { bus } from "wujie";
// 主应用监听事件
bus.$on("事件名字", function (arg1, arg2, ...) {});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2, ...);
// 主应用取消事件监听
bus.$off("事件名字", function (arg1, arg2, ...) {});
子应用
// 子应用监听事件
window.$wujie?.bus.$on("事件名字", function (arg1, arg2, ...) {});
// 子应用发送事件
window.$wujie?.bus.$emit("事件名字", arg1, arg2, ...);
// 子应用取消事件监听
window.$wujie?.bus.$off("事件名字", function (arg1, arg2, ...) {});
无界还提供了vue和react的组件封装,参考官网