Skip to content

Wujie 主流程源码阅读记录

1. 主应用 index.js 执行 npm 包 wujie-react,内部执行 npm 包 wujie:
2. wujie 执行
   1. new EventBus
   2. 处理子应用链接跳转 processAppForHrefJump,即监听 popstate 处理 href 的跳转,前进则 renderIframeReplaceApp,后退则 renderElementToContainer;
   3. 定义 webComponent 容器 defineWujieWebComponent:<wujie-app>
3. 回到主应用开始执行 setupApp->addSandboxCacheWithOptions->存入 idToSandboxCacheMap(Wujie 实例里的 inject.idToSandboxMap)
4. 主应用继续执行 preloadApp,内部执行:【异步 1】requestIdleCallback;
5. 主应用执行正常渲染页面逻辑,本轮结束;

执行异步 1,requestIdleCallback 里

1. 合并配置
2. 执行 new WuJie({}),constructor 执行:
   1. 创建目标地址的解析:appRouteParse(url),得到 urlElement, appHostPath, appRoutePath;
   2. 创建 iframe(与主应用同域)【js 沙箱】:iframeGenerator(this, attrs, mainHostPath, appHostPath, appRoutePath);
      1. 创建同域 iframe 并 appendChild 到 body 上
      2. 变量需要提前注入,在入口函数通过变量防止死循环 patchIframeVariable
      3. 【异步 2】停止加载 iframe:stopIframeLoading,内部通过 loop 递归比较两者 document 是否一致从而判断 iframe 是否已经 append 了,如果 append 则执行 stop;停止加载完成后执行 initIframeDom 初始化 iframe 的 dom 结构:
         1. initBase:初始化 base 标签
         2. patchIframeHistory:重写 pushState 和 replaceState,内部会执行 updateBase 和 syncUrlToWindow
         3. patchIframeEvents:iframewindow 新增 addEventListener 和 removeEventListener 事件,分别处理事件挂载到 iframe 还是 window 下
         4. syncIframeUrlToWindow:增加事件 hashchange、popstate,内执行 syncUrlToWindow。子应用前进后退,同步路由到主应用
         5. patchWindowEffect:遍历 window 的所有属性并处理,部分属性被主 window 的属性覆写
         6. patchDocumentEffect:! addEventListener/removeEventListener、document 的重写;
         7. patchNodeEffect:重写 getRootNode、appendChild、insertBefore;
         8. patchRelativeUrlEffect:执行 fixElementCtrSrcOrHref 对接口的 prototype 进行重写 set,设置正确的路径
   3. 执行 window、document、location 代理:proxyGenerator(),供 initIframeDom 里或代码注入时使用;
   4. 更新 idToSandboxCacheMap: addSandboxCacheWithWujie(),new Wujie 执行完毕。
   5. 《总结:new Wujie 主要是创建了同域 iframe 后立即挟持 web api,并搞了一套 proxy》
3. 执行 runPreload
   1. 执行主应用 beforeLoad 钩子
   2. 【异步 3】【此时会先执行异步 2 的回调】执行 importHTML 得到 template, getExternalScripts, getExternalStyleSheets
   3. 【异步 4】在异步 3 的回调里执行,执行 processCssLoader:处理 css-loader
   4. 【异步 5】在异步 4 的回调里执行,执行 sandbox.active:激活子应用
      1. await this.iframeReady;确保 iframe 加载完毕【即异步 2】且 initIframeDom 执行完毕;
      2. 处理子应用自定义 fetch;
      3. 同步路由 syncUrlToIframe,syncUrlToWindow;
      4. 创建自定义元素<wujie-app/> :createWujieWebComponent
      5. 将<wujie-app />插入到 iframe 里的 document.body 里
         1. renderElementToContainer,插入后执行 defineWujieWebComponent->connectedCallback 里的回调:初始化 shadowDom 得到 shadowRoot,并执行 patchElementEffect 修正 shadowRoot element
      6. 将 template 渲染到 shadowRoot,renderTemplateToShadowRoot
         1. 将 template 渲染为 html 元素: renderTemplateToHtml,内部通过 createTreeWalker 遍历 html 树,并对每个 element 执行 patchElementEffect 修正,同时将 IMG、A、SOURCE 的相对路径修正为绝对路径;
         2. 执行 processCssLoaderForTemplate,处理 css-before-loader 以及 css-after-loader,得到处理后的 html;
         3. 将该 html 挂载到 shadowRoot 节点上即<wujie-app />里
         4. html 插入一个 div;
         5. 修复 html parentNode:
         6. 执行 patchRenderEffect:内部执行 patchEventListener 记录 shadowRoot 的 head 和 body 的事件,等重新渲染复用 head 和 body 时需要清空事件;重写 shadowRoot 的 contains、shadowRoot 的 head 的 appendChild、insertBefore、removeChild、contains,body 的 appendChild、insertBefore 方法:rewriteAppendOrInsertChild。内部对 LINK、STYLE、SCRIPT、IFRAME 做了处理; 配合 proxy 使用
         7. 执行 patchCssRules:子应用样式打补丁;sandbox.active 执行完毕;
         8. 《总结:sandbox.active 同步路由后,创建<wujie-app>插入对应 dom(iframe document body 或自定义 dom)并将 template 渲染到 shadowRoot 后,对 shadowRoot 上的 dom 操作方法进行挟持以特殊处理 LINK、STYLE、SCRIPT、IFRAME》
   5. 【异步 6】在异步 5 的回调里执行,因 exec 为 true,则执行 sandbox.start:启动子应用运行 js,处理兼容模式
      1. 执行 beforeScriptResultList,内部执行 insertScriptToIframe
      2. 执行 syncScriptResultList 及 deferScriptResultList,内部执行 insertScriptToIframe
      3. 执行 wujie 实例方法 mount()
      4. 执行 domContentLoadedTrigger,触发 DOMContentLoaded 事件
      5. 执行 afterScriptResultList,内部执行 insertScriptToIframe
      6. 执行 domLoadedTrigger,触发 loaded 事件
      7. sandbox.start 执行完毕,即 runPreload 执行完毕,即 requestIdleCallback 执行完毕,即 preloadApp 执行完毕。回头说明两个方法:
         1. insertScriptToIframe:通过 scriptResult 获取 script 脚本,对于内联脚本进行包裹后 appendChild 到 el 上,然后执行 afterExecScript 执行下一个 script;
         2. mount:
            1. beforeMount 钩子执行
            2. 执行 iframeWindow.\_\_WUJIE_MOUNT();即子应用渲染流程;
            3. afterMount 钩子执行
            4. 执行下一个 script;

—————————
点击左侧导航,跳转 react16,即渲染 WujieReact react 组件,内部执行 componentDidMount->startApp->wujie startApp->

1. 合并配置
2. 执行 sandbox.unmount()清理;
3. 执行 sandbox.active 激活子应用
4. 重建 css,sandbox.rebuildStyleSheets();
5. beforeMount 钩子执行
6. 执行 iframeWindow.\_\_WUJIE_MOUNT();即子应用渲染流程;
7. afterMount 钩子执行;完毕。
   —————————