最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 微前端 之 icestark 源码阅读

    正文概述 掘金(我是你的超级英雄)   2021-04-01   654

    一、前言

    在之前《微前端探索》文章中,我们分析了微前端框架 single-spa 和 qiankun 的一些源码、原理等,本文再对社区另外一个微前端框架 icestark 做一个简单整理分析,帮助各位想探索微前端的同学有个感性的认识。

    二、 源码目录

    icestark 源码的主要目录结构如下所示,主要包括 packages 和 src 两个目录,相关总结整理如下,我们不会一行一行去讲解代码,如果你对某一块很感兴趣,可以自己去 github 上面 clone 代码库,源码还是非常简单易懂的。

    微前端 之 icestark 源码阅读

    三、重要代码整理

    1、子应用状态管理

    我们学习 single-spa 的时候总结到 single-spa 是一个状态机,负责管理各个子应用的状态。所以 icestark 必然存在这个子应用的状态管理,相关的实现在 src/apps.ts 文件中,其包括以下几个主要过程/方法:

    • registerMicroApp:注册子应用的方法,将子应用添加到全局 microApps 数组变量中;
    • createMicroApp:创建子应用的方法,包括加载子应用的资源等;
    • mountMicroApp:挂载子应用的方法,将子应用挂载到容器中;
    • unmountMicroApp:卸载子应用的方法,将子应用从容器中卸载;
    • unloadMicroApp:unload 子应用的方法,包括移除子应用的资源等;
    • removeMicroApp:移除子应用的方法,将子应用从全局 microApps 数组中移除;

    以上方法实现的思路还是比较简单的,定义个全局 microApps 数组变量来保存子应用,然后每个子应用分别有个对应的 status 变量来表示该子应用的状态是怎样的,然后进行下一步操作时会根据当前的状态进行对应的下一步操作,以下我们挑选个最“复杂”的方法 createMicroApp 来看下,我们对代码进行了相关注释,还是比较简单的,就不再赘述了。

    export async function createMicroApp(app: string | AppConfig, appLifecyle?: AppLifecylceOptions) {
      const appConfig = getAppConfigForLoad(app, appLifecyle);
      const appName = appConfig && appConfig.name;
    
      if (appConfig && appName) {
        if (appConfig.status === NOT_LOADED || appConfig.status === LOAD_ERROR ) {
          // 如果该子应用的当前状态是未加载或加载失败,执行以下逻辑
          if (appConfig.title) document.title = appConfig.title;
          // 更新子应用的状态
          updateAppConfig(appName, { status: LOADING_ASSETS });
          let lifeCycle: ModuleLifeCycle = {};
          try {
            // 加载子应用资源
            lifeCycle = await loadAppModule(appConfig);
            // in case of app status modified by unload event
            if (getAppStatus(appName) === LOADING_ASSETS) {
              // 更新子应用配置
              updateAppConfig(appName, { ...lifeCycle, status: NOT_MOUNTED });
            }
          } catch (err){
            // 出错,更新子应用配置
            updateAppConfig(appName, { status: LOAD_ERROR });
          }
          if (lifeCycle.mount) {
            // 进行子应用挂载
            await mountMicroApp(appConfig.name);
          }
        } else if (appConfig.status === UNMOUNTED) {
          // 如果当前的子应用是卸载状态执行以下逻辑
          if (!appConfig.cached) {
            // 加载 js/css 资源
            await loadAndAppendCssAssets(appConfig.appAssets || { cssList: [], jsList: []});
          }
          // 进行挂载
          await mountMicroApp(appConfig.name);
        } else if (appConfig.status === NOT_MOUNTED) {
          // 如果当前的子应用是没有挂载状态,则进行挂载
          await mountMicroApp(appConfig.name);
        } else {
          console.info(`[icestark] current status of app ${appName} is ${appConfig.status}`);
        }
        // 返回创建的子应用的信息
        return getAppConfig(appName);
      } else {
        console.error(`[icestark] fail to get app config of ${appName}`);
      }
      return null;
    }
    

    2、路由劫持

    我们都知道 react、vue、angular 等单应用路由劫持的实现都是:history 路由通过监听 popstate 事件、hash 路由通过监听 hashchange 路由来实现的,那么 icestark 的路由劫持是怎么做的呢,嗯哼,他们也没有变出花来,也是一样的实现原理,代码位置在 src/start.js 文件中,相关源码如下所示

    const hijackHistory = (): void => {
      // 监听对应的路由事件,urlChange 为事件回调函数
      window.addEventListener('popstate', urlChange, false);
      window.addEventListener('hashchange', urlChange, false);
    };
    

    3、沙箱隔离

    在微前端容器中,存在多个子应用共有一个 window 对象的情况,如果不进行隔离,可能多个子应用之间会存在互相影响的情况。icestark 基于 Proxy 为每个子应用启用了一个沙箱环境,代码位置在 packages/icestark-sandbox/src/index.js 文件中,相关代码实现如下所示

      createProxySandbox(injection?: object) {
        const { propertyAdded, originalValues, multiMode } = this;
        const proxyWindow = Object.create(null) as Window;
        const originalWindow = window;
        const originalAddEventListener = window.addEventListener;
        const originalRemoveEventListener = window.removeEventListener;
        const originalSetInerval = window.setInterval;
        const originalSetTimeout = window.setTimeout;
    
        // hijack addEventListener
        proxyWindow.addEventListener = (eventName, fn, ...rest) => {
          const listeners = this.eventListeners[eventName] || [];
          listeners.push(fn);
          return originalAddEventListener.apply(originalWindow, [eventName, fn, ...rest]);
        };
        // hijack removeEventListener
        proxyWindow.removeEventListener = (eventName, fn, ...rest) => {
          const listeners = this.eventListeners[eventName] || [];
          if (listeners.includes(fn)) {
            listeners.splice(listeners.indexOf(fn), 1);
          }
          return originalRemoveEventListener.apply(originalWindow, [eventName, fn, ...rest]);
        };
        // hijack setTimeout
        proxyWindow.setTimeout = (...args) => {
          const timerId = originalSetTimeout(...args);
          this.timeoutIds.push(timerId);
          return timerId;
        };
        // hijack setInterval
        proxyWindow.setInterval = (...args) => {
          const intervalId = originalSetInerval(...args);
          this.intervalIds.push(intervalId);
          return intervalId;
        };
    
        const sandbox = new Proxy(proxyWindow, {
          set(target: Window, p: PropertyKey, value: any): boolean {
            target[p] = value;
          },
          get(target: Window, p: PropertyKey): any {
            const targetValue = target[p];
            if (targetValue) {
              // case of addEventListener, removeEventListener, setTimeout, setInterval setted in sandbox
              return targetValue;
            }
          },
          has(target: Window, p: PropertyKey): boolean {
            return p in target || p in originalWindow;
          },
        });
        this.sandbox = sandbox;
      }
    

    4、通信

    icestark 提供了 event/store 通信,其无非是实现了个简单的 EventEmit 实例,代码位置在 packages/icestark-data/src/event.js 文件中,相关源代码实现逻辑如下,我们进行了些代码注释,就不再赘述。

    class Event implements Hooks {
      eventEmitter: object;
    
      constructor() {
        this.eventEmitter = {};
      }
    
      // 事件触发
      emit(key: string, ...args) {
        const keyEmitter = this.eventEmitter[key];
        // 执行事件注册的回调方法
        keyEmitter.forEach(cb => {
          cb(...args);
        });
      }
    
      // 事件监听
      on(key: string, callback: (value: any) => void) {
        if (!this.eventEmitter[key]) {
          this.eventEmitter[key] = [];
        }
        // 将事件回调方法放入数组中
        this.eventEmitter[key].push(callback);
      }
      // 取消注册
      off(key: string, callback?: (value: any) => void) {
        if (callback === undefined) {
          this.eventEmitter[key] = undefined;
          return;
        }
        this.eventEmitter[key] = this.eventEmitter[key].filter(cb => cb !== callback);
      }
    
      has(key: string) {
        const keyEmitter = this.eventEmitter[key];
        return isArray(keyEmitter) && keyEmitter.length > 0;
      }
    }
    

    四、总结

    经过以上代码整理分析,我们可以看到,其实 icestark 跟 single-spa、qiankun 这些微前端框架做的事情/原理大同小异,icestark 自己去实现了子应用的状态管理,然后也去实现了沙箱、通信等这些辅助功能。

    辛苦整理良久,还望手动点赞鼓励~ 博客 github地址为:github.com/fengshi123/… ,汇总了作者的所有博客,欢迎关注及 star ~


    起源地下载网 » 微前端 之 icestark 源码阅读

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元