从源码看无界 1.0.28:为何说它是 qiankun 的 “轻量化替代方案”(二)
我们接着上一节的《从源码看无界 1.0.28:为何说它是 qiankun 的 “轻量化替代方案”》内容继续往下。
生命周期图
sandbox.active 方法
我们找到 packages/wujie-core/src/sandbox.ts 文件的第 275 行:
//...
/** 激活子应用
* 1、同步路由
* 2、动态修改iframe的fetch
* 3、准备shadow
* 4、准备子应用注入
*/
public async active(options: {
url: string;
sync?: boolean;
prefix?: { [key: string]: string };
template?: string;
el?: string | HTMLElement;
props?: { [key: string]: any };
alive?: boolean;
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
replace?: (code: string) => string;
}): Promise<void> {
//...
// 设置loading
addLoading(el, loading);
const newSandbox = new WuJie({ name, url, attrs, degradeAttrs, fiber, degrade, plugins, lifecycles });
newSandbox.lifecycles?.beforeLoad?.(newSandbox.iframe.contentWindow);
const { template, getExternalScripts, getExternalStyleSheets } = await importHTML({
url,
html,
opts: {
fetch: fetch || window.fetch,
plugins: newSandbox.plugins,
loadError: newSandbox.lifecycles.loadError,
fiber,
},
});
const processedHtml = await processCssLoader(newSandbox, template, getExternalStyleSheets);
await newSandbox.active({ url, sync, prefix, template: processedHtml, el, props, alive, fetch, replace });
await newSandbox.start(getExternalScripts);
return () => newSandbox.destroy();
}
//...
主子应用路由同步
可以看到,首先将主应用的路由同步到子应用中,然后再把子应用的路由同步到主应用中:
// 处理子应用路由同步
if (this.execFlag && this.alive) {
// 当保活模式下子应用重新激活时,只需要将子应用路径同步回主应用
syncUrlToWindow(iframeWindow);
} else {
// 先将url同步回iframe,然后再同步回浏览器url
syncUrlToIframe(iframeWindow);
syncUrlToWindow(iframeWindow);
}
//...
/**
* 同步主应用路由到子应用
*/
export function syncUrlToIframe(iframeWindow: Window): void {
// ...
if (preAppRoutePath !== appRoutePath) {
iframeWindow.history.replaceState(null, "", inject.mainHostPath + appRoutePath);
}
}
/**
* 同步子应用路由到主应用路由
*/
export function syncUrlToWindow(iframeWindow: Window): void {
//...
if (winUrlElement.href !== window.location.href) {
window.history.replaceState(null, "", winUrlElement.href);
}
winUrlElement = null;
}
可以看到同步路由也是很简单,直接调用 history 的 replaceState 方法把主应用跟子应用的 history 改掉了。
接来下可以看到 active 方法中的这段代码:
我们接着上一节的《从源码看无界 1.0.28:为何说它是 qiankun 的 “轻量化替代方案”》内容继续往下。
生命周期图
sandbox.active 方法
我们找到 packages/wujie-core/src/sandbox.ts 文件的第 139 行:
//...
/** 激活子应用
* 1、同步路由
* 2、动态修改iframe的fetch
* 3、准备shadow
* 4、准备子应用注入
*/
public async active(options: {
url: string;
sync?: boolean;
prefix?: {
[key: string]: string };
template?: string;
el?: string | HTMLElement;
props?: {
[key: string]: any };
alive?: boolean;
fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
replace?: (code: string) => string;
}): Promise<void> {
//...
if (this.shadowRoot) {
/*
document.addEventListener was transfer to shadowRoot.addEventListener
react 16 SyntheticEvent will remember document event for avoid repeat listen
shadowRoot have to dispatchEvent for react 16 so can't be destroyed
this may lead memory leak risk
*/
this.el = renderElementToContainer(this.shadowRoot.host, el);
if (this.alive) return;
} else {
// 预执行无容器,暂时插入iframe内部触发Web Component的connect
const iframeBody = rawDocumentQuerySelector.call(iframeWindow.document, "body") as HTMLElement;
this.el = renderElementToContainer(createWujieWebComponent(this.id), el ?? iframeBody);
}
await renderTemplateToShadowRoot(this.shadowRoot, iframeWindow, this.template);
this.patchCssRules();
// inject shadowRoot to app
this.provide.shadowRoot = this.shadowRoot;
}
我们第一次渲染子应用的时候,这个 shadowRoot 对象肯定是没有初始化的,所以会走以下代码:
// 预执行无容器,暂时插入iframe内部触发Web Component的connect
const iframeBody = rawDocumentQuerySelector.call(iframeWindow.document, "body") as HTMLElement;
this.el = renderElementToContainer(createWujieWebComponent(this.id), el ?? iframeBody);
//...
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;
}}
创建自定义元素 wujie-app
可以看到,首先调用了 createWujieWebComponent 方法注册了一个自定义元素 wujie-app,那么有小伙伴要问了,这个 “wujie-app” 元素到底是啥?
其实在框架加载的时候就已经往全局注册了一个自定义元素 wujie-app,可以找到 packages/wujie-core/src/index.ts 文件的第 171 行代码:
/**
* 定义 wujie webComponent,将shadow包裹并获得dom装载和卸载的生命周期
*/
export function defineWujieWebComponent() {
const customElements = window.customElements;
if (customElements && !customElements?.get("wujie-app")) {
class WujieApp extends HTMLElement {
connectedCallback(): void {
if (this.shadowRoot) return;
// 为自定义元素附加一个 Shadow DOM,实现样式和结构的封装
const shadowRoot = this.attachShadow({
mode: "open" });
const sandbox = getWujieById(this.getAttribute(WUJIE_APP_ID));
patchElementEffect(shadowRoot, sandbox.iframe.contentWindow);
sandbox.shadowRoot = shadowRoot;
}
disconnectedCallback(): void {
const sandbox = getWujieById(this.getAttribute(WUJIE_APP_ID));
sandbox?.