基础介绍
作为微前端中基于iframe来做隔离的框架wenjie算是创立先河了,结合源码来看看内部实现;
1、wujie也是基于mono-repo + pnpm来做包管理;
2、核心包是wujie-core, 框架层面做了一些插件化的扩展来包装了wujie-core, 于是就有了wujie-react; wujie-vue2; wujie-vue3等包来支持不同框架接入;
基本使用
主应用
import { bus, setupApp, preloadApp, startApp, destroyApp } from "wujie";
// 设置子应用
setupApp({ name: "唯一id", url: "子应用地址", exec: true, el: "容器", sync: true })
// 启动子应用
startApp({ name: "唯一id" });
基本原理
微前端是可以实现多个子应用同时存在,且不相互影响,相互独立,那么就会涉及js运行的独立, css样式隔离的独立;来看看js和css隔离的实现
js沙箱
- iframe有天然的隔离,但是也有他的一些问题存在,比如:
-
-
- 路由状态丢失,刷新一下,iframe的url状态就丢失了;
-
- dom割裂严重,弹窗只能在iframe内部展示,无法覆盖到全局;
- ....
-
结合源码来看看wujie是怎么解决这些问题的, wujie基于proxy + iframe创建js sandbox
// 1. 生成iframeGenerator 创建了隐藏的iframe;
function iframeGenerator(...args) {
const iframe = window.document.createElement('iframe');
const attrsMerge = {
style: "display: none",
...args.attrs,
src,
name,
}
setAttrsToElement(iframe, attrsMerge);
window.document.body.appendChild(iframe);
return iframe;
}
// 2. proxy 各个对象proxyWindow, proxyDocument, proxyLocation 增强了一些功能
function proxyGenerator(...args){
const proxyWindow = new Proxy(iframe.contentWindow, {
get:(tagrt, p) => {
return getTargetValue(tagrt, p)
}
})
const proxyDocument = new Proxy({}, {
get:(_fakeDocument, propKey) => {
const document = window.document;
}
})
const proxyLocation = new Proxy({}, {
get: function(location, key){
const location = iframe.contentWindow.location;
if(key === 'href') {
return location[key].replace(mainHostPath, appHostPath);
}
}
})
return { proxyWindow, proxyDocument, proxyLocation }
}
- wujie是怎么处理路由状态丢失的实现的;
// 重写了iframe的history的pushState, relaceState,实现了将主应用的路由和子应用同步;
function patchIframeHistory = (iframeWindow) => {
const hostory = iframeWindow.history;
history.pushState = function(){
syncUrlToWindow(iframeWindow);
},
history.replaceState = function() {}
syncUrlToWindow(iframeWindow);
}
- 在
iframe内部进行history.pushState,浏览器会自动的在joint session history中添加iframe的session-history,浏览器的前进、后退在不做任何处理的情况就可以直接作用于子应用 - wujie通过劫持
iframe的history.pushState和history.replaceState,就可以将子应用的url同步到主应用的query参数上,当刷新浏览器初始化iframe时,读回子应用的url并使用iframe的history.replaceState进行同步
css沙箱
- 基于web component的sandow dom来实现样式的隔离;
- wujie是子应用的样式挂载到shadow dom上;
- 将
document的查询类接口:getElementsByTagName、getElementsByClassName、getElementsByName、getElementById、querySelector、querySelectorAll、head、body全部代理到webcomponent,这样instance和webcomponent就精准的链接起来。
// 定义web component的容器
function defineWujieWebComponent() {
const custtomElements = window.customElements;
class WujieApp extends HTMLElement {
connectedCallback() {
// attachShdow是可以将shadow DOM挂载到宿主this上;
const shadowRoot = this.attachShadow({ mode: "open" });
const sanbox = getWujieById(this.getAttribute(WUJIE_APP_ID));
patchElementEffect(shadowRoot, shandbox.iframe.contentWindow);
sandbox.shadowRoot = shadowRoot;
},
disconnectedCallback() {
const sandbox = getWujieById(this.getAttribute(WUJIE_APP_ID));
sanbox?.unmount();
}
}
customElements?.define("wujie-app", WujieApp)
}
附加知识点
web components
- web components web component
浏览器原生的组件规范,主要由custom elements, template, shadow DOM, HTML Import组成;
-
Custom Element 用户自定义网页元素,
- custom elment内部有一个shadow root,用来接入外部DOM的根元素;
-
template组件内的html模版代码;
-
shadow DOM, 浏览器将模版,样式,属性,js代码等封装成一个独立的DOM元素,比如这类;
-
HTML import 可以用来加载外部网页;