微前端(wujie)源码解析-7.wujie-core沙箱运行

699 阅读4分钟

无界沙箱运行

前言

上一篇文章中,我们分析了无界的沙箱激活机制,沙箱激活过程中,将子应用的html模板渲染到webComponent中,但是距离子应用的运行还差一步,子应用的js还没有运行,本篇文章我们将一起分析沙箱的运行机制

运行js沙箱

沙箱的运行入口是start函数,函数的定义如下:

class Wujie {
    public async start(getExternalScripts: () => ScriptResultList): Promise<void> {
        // debugger
        this.execFlag = true;
        // 执行脚本
        const scriptResultList = await getExternalScripts();
        // 假如已经被销毁了
        if (!this.iframe) return;
        const iframeWindow = this.iframe.contentWindow;
        // 标志位,执行代码前设置
        iframeWindow.__POWERED_BY_WUJIE__ = true;
        // 同步代码
        const syncScriptResultList: ScriptResultList = [];
        
        scriptResultList.forEach((scriptResult) => {
            // other code 处理defer和async
            // 同步代码
            syncScriptResultList.push(scriptResult);
        });
        
        // 同步代码
        syncScriptResultList.forEach((scriptResult) => {
            this.execQueue.push(() =>
                scriptResult.contentPromise.then((content) =>
                    this.fiber
                        ? requestIdleCallback(() => insertScriptToIframe({ ...scriptResult, content }, iframeWindow))
                        : insertScriptToIframe({ ...scriptResult, content }, iframeWindow)
                )
            );
        });

        //框架主动调用mount方法
        this.execQueue.push(this.fiber ? () => requestIdleCallback(() => this.mount()) : () => this.mount());
        
        // 执行队列
        this.execQueue.shift()();

        // 所有的execQueue队列执行完毕,start才算结束,保证串行的执行子应用
        return new Promise((resolve) => {
            this.execQueue.push(() => {
                resolve();
                this.execQueue.shift()?.();
            });
        });
    }
}

上面的代码中,我们略作删减,留下一些关键代码,可以看到在start函数中,最终是要将子应用的js代码插入到iframe中,然后调用mount函数,实现的步骤如下:

  • 向执行队列execQueue中添加子应用js代码
  • 向执行队列execQueue中添加mount函数,当子应用挂载时调用
  • 依次执行队列execQueue中的函数

如下图所示:

image.png

如上所示使我们的队列中等待执行的函数,当我们调用 this.execQueue.shift()();时,就会执行队列中的函数,并且会串行的执行所有的队列中的代码,那么这是怎么实现的呢?我们通过一个例子来了解下:

image.png

这张图片中,蓝色的为我们的子应用代码,红色的为我们的mount函数,绿色的为我们的start函数,当我们执行队列中函数时,遇到子应用代码则插入到iframe中,遇到mount函数则调用mount函数,当队列中的函数执行完毕时,start函数才算执行完毕. 在执行子应用代码时,有一个特殊的处理,为了保证队列执行的延续性,在一个子应用代码执行完毕后,会像iframe中插入一段辅助代码"if(window.__WUJIE.execQueue && window.__WUJIE.execQueue.length){ window.__WUJIE.execQueue.shift()()}";,这样就保证了队列中所有函数的的串行执行。如下图所示:

image.png

mount钩子函数

当我们的子应用代码执行完毕后,会调用mount函数,mount函数的定义如下:

class Wujie {
    public mount(): void {
        if (this.mountFlag) return;
        if (isFunction(this.iframe.contentWindow.__WUJIE_MOUNT)) {
            removeLoading(this.el);
            this.lifecycles?.beforeMount?.(this.iframe.contentWindow);
            this.iframe.contentWindow.__WUJIE_MOUNT();
            this.lifecycles?.afterMount?.(this.iframe.contentWindow);
            this.mountFlag = true;
        }
        this.execQueue.shift()?.();
    }
}

mount函数中,会调用子应用的__WUJIE_MOUNT函数,这个函数是子应用中定义的,如下我们在子应用中定义:

window.__WUJIE_MOUNT = () => {
    const router = new VueRouter({ base, routes });
    instance = new Vue({ router, render: (h) => h(App) }).$mount("#app");
};

所以当我们调用完mount函数后,子应用就会被挂载到我们的#app中,这样就完成了子应用的挂载

总结

本篇文章我们分析了无界的沙箱运行机制,无界的沙箱运行机制是通过一个队列来实现的, 当我们调用start函数时,会将子应用的js代码插入到iframe中,然后调用mount函数,这其中也有一些特殊的处理,比如在子应用代码执行完毕后,会像iframe中插入一段辅助代码来保证队列继续往后执行

最后的最后

关于无界的沙箱运行机制我们用了7篇文章来分析,从无界的整体架构,到适配层,再到核心层,在核心层中,我们又分析了沙箱的创建,激活,运行,这其中涉及到了很多的知识点,比如webComponentpxoxy等等,其中还有很多点我们没有深入分析, 但好在我们对整体的运行流程有了一个大概的了解,当我们以后想要分析某一块功能点时,也能够快速定位我们要分析的代码。

如果觉得本文有帮助 记得点赞三连哦 十分感谢!

系列链接