无界与其他微前端框架(例如qiankun)的主要区别在于其独特的 JS 沙箱机制。qiankun采用了自定义沙箱,而无界使用 iframe 来实现 JS 沙箱。iframe沙箱具有其独特的优势,具体如下:
1.应用切换没有清理成本
2.允许一个页面同时激活多个子应用
3.iframe性能相对更优
下面将从以下几步,分析无界渲染子应用步骤以及常见问题。
· 1.创建子应用 iframe
· 2.解析 HTML分离js和css
· 3.创建 webComponent,并挂载 HTML
1.创建和主应用同源的子应用iframe。
export function iframeGenerator(
sandbox: WuJie,
attrs: { [key: string]: any },
mainHostPath: string,
appHostPath: string,
appRoutePath: string
): HTMLIFrameElement {
const iframe = window.document.createElement("iframe");
const attrsMerge = { src: mainHostPath, style: "display: none",
...attrs, name: sandbox.id, [WUJIE_DATA_FLAG]: "" };
setAttrsToElement(iframe, attrsMerge);
window.document.body.appendChild(iframe);
const iframeWindow = iframe.contentWindow;
// 变量需要提前注入,在入口函数通过变量防止死循环
patchIframeVariable(iframeWindow, sandbox, appHostPath);
sandbox.iframeReady = stopIframeLoading(iframeWindow).then(() => {
if (!iframeWindow.__WUJIE) {
patchIframeVariable(iframeWindow, sandbox, appHostPath);
}
initIframeDom(iframeWindow, sandbox, mainHostPath, appHostPath);
/**
* 如果有同步优先同步,非同步从url读取
*/
if (!isMatchSyncQueryById(iframeWindow.__WUJIE.id)) {
iframeWindow.history.replaceState(null, "", mainHostPath + appRoutePath);
}
});
return iframe;
}
上述代码逻辑为
1.创建一个新的iframe,避免各个应用之间相互污染。
2.给iframe设置子应用相关属性,如主应用域名等。
3.将iframe插入到baody中。
4.调用stopIframeLoading函数停止iframe加载
为什么子应用的iframe的地址和主应用的一样?
无界为了解决iframe之间的通信,所以将iframe的src设置为主应用域名(即同源状态下的状态共享,如共用localstroage等)。比如主应用域名为a.com,子应用iframe的src指向同样也为a.com。但子应用的资源会在别的域名下如b.com。所以会造成跨域,这也就是无界的子应用为什么需要设置允许跨域。
如何设置跨域?
在开发环境下,通过在vue.config.js文件中devServer下添加请求头headers:{“Access-Control-Allow-Origin”:”*”},来允许开发环境跨域。 在生产环境下可通过在nginx上添加代理,add_header Access-Control-Allow-Origin “主应用域名”,来解决跨域。这两种方式对于前端来说也算轻车熟路。
为什么要调用stopIframeLoading函数来停止iframe加载?
因为会造成变量污染,例如主应用和子应用如果存在相同名称的变量,而其实我们只是需要一个空的iframe,并不需要执行主应用的代码,无界通过判断iframeWindow.document是否存在,来调用iframeWindow.stop停止iframe加载,来解决了iframe执行主应用代码问题。
2.解析 HTML 分离js和css。
因为子应用的dom运行在WebComponents中,而js运行在iframe中,所以无界会将html文件进行解析通过(类似import-html-entry插件功能,无界实现了自己的插件)最终分离出css和js相关信息。然后通过fetch发送请求将css和js资源请求回来,最终的css代码将会形成内部样式。
export function getExternalStyleSheets(
styles: StyleObject[],
fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response> = defaultFetch,
loadError: loadErrorHandler
): StyleResultList {
return styles.map(({ src, content, ignore }) => {
// 内联
if (content) {
return { src: "", contentPromise: Promise.resolve(content) };
} else if (isInlineCode(src)) {
// if it is inline style
return { src: "", contentPromise: Promise.resolve(getInlineCode(src)) };
} else {
// external styles
return {
src,
ignore,
contentPromise: ignore ? Promise.resolve("") :
fetchAssets(src, styleCache, fetch, true, loadError),
};
}
});
}
子应用为什么采用了内部样式而link标签引入?
因为需要对样式经常处理,所以需要将样式请求回来进行处理再放回去,还有一个就是子应用切换后样式需要恢复必须把样式收集起来,内联样式更好收集处理。
子应用里面js文件中使用var声明的变量为什么不能共享?
在无界微前端中子应用代码运行在闭包内,为什么要在闭包内执行代码?为了劫持修改 window.location,因为 location 的 configuable 为 false, 所以采用闭包解决
code = `(function(window, self, global, location) {
${code}}).bind(window.__WUJIE.proxy)
( window.__WUJIE.proxy,
window.__WUJIE.proxy,
window.__WUJIE.proxy,
window.__WUJIE.proxyLocation,);`;
这样就会导致子应用的window是无界的代理对象而非真实的window对象,而闭包也会导致失去变量声明提升。可通过在plugins添加jsIgnores来决定让子应用自己执行某些js,来解决这个问题。
3.创建 webComponent,并挂载 HTML
首先创建一个webComponent,并设置id与css。
export function createWujieWebComponent(id: string): HTMLElement {
const contentElement = window.document.createElement("wujie-app");
contentElement.setAttribute(WUJIE_APP_ID, id);
contentElement.classList.add(WUJIE_IFRAME_CLASS);
return contentElement;
}
调用processCssLoaderForTemplate函数,处理css-before-loader以及css-after-loader,,将通过fetch请求下来的css内容与plugins中的loader合并生成内部样式。
调用renderTemplateToShadowRoot函数,将生成的css等内容挂载到shadowRoot中,最后再挂载在主应用的dom中。
const processedHtml = await processCssLoaderForTemplate(iframeWindow.__WUJIE, html);
// change ownerDocument
shadowRoot.appendChild(processedHtml);
const shade = document.createElement("div");
shade.setAttribute("style", WUJIE_SHADE_STYLE);
processedHtml.insertBefore(shade, processedHtml.firstChild);
shadowRoot.head = shadowRoot.querySelector("head");
shadowRoot.body = shadowRoot.querySelector("body");
以上就是对无界源码的初步解析,通过了解框架的设计思路可以帮我们更好解决在使用中的问题。感兴趣的朋友可以在官网与github进行深入学习与了解。
欢迎大家一起交流~~