微前端 qiankun@2.10.5 源码分析(一)

news2024/12/26 23:34:02

微前端 qiankun@2.10.5 源码分析(一)

前言

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently. – Micro Frontends

微前端架构具备以下几个核心价值:

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级

    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。-- qiankun 官网

哈哈,其实目前我自己公司团队也存在上面说的一些问题,希望能够通过源码的分析研究从中得到一些灵感,对现有项目进行一些改造,打造符合自己的微前端生态。

安装

这里用的是 qiankun@2.10.5 版本。

执行以下命令安装 qiankun 源码:

$ git clone https://github.com/umijs/qiankun.git
$ cd qiankun

安装并运行:

$ yarn install
$ yarn examples:install
$ yarn examples:start

打开 http://localhost:7099 看效果:
在这里插入图片描述

开始

第一步:初始化应用

找到 examples/main/index.js 文件的第 15 行:

/**
 * Step1 初始化应用(可选)
 */
render({loading: true});
const loader = (loading) => render({loading});

可以看到,调用了 render 方法,然后创建了一个 loader,我们重点看一下 render 方法。

找到 examples/main/render/VueRender.js 文件:

import Vue from 'vue/dist/vue.esm';

function vueRender({ loading }) {
  return new Vue({
    template: `
      <div id="subapp-container">
        <h4 v-if="loading" class="subapp-loading">Loading...</h4>
        <div id="subapp-viewport"> Vue 应用挂载节点 </div>
      </div>
    `,
    el: '#subapp-container',
    data() {
      return {
        loading,
      };
    },
  });
}

let app = null;

export default function render({ loading }) {
  if (!app) {
    app = vueRender({ loading });
  } else {
    app.loading = loading;
  }
}

可以看到,导出了一个 render 方法,在 render 方法中创建了一个 Vue 实例,这里有一个 id="subapp-viewport"div 节点,这个就是应用的挂载节点,后面会用到。

如果这个时候我们执行 render 方法的话,页面会是一个 loading 状态,我们可以试试看。

修改一下 examples/main/index.js 文件:

import 'zone.js'; // for angular subapp
import './index.less';
/**
 * 主应用 **可以使用任意技术栈**
 * 以下分别是 React 和 Vue 的示例,可切换尝试
 */
import render from './render/VueRender';
//
/**
 * Step1 初始化应用(可选)
 */
render({loading: true});
const loader = (loading) => render({loading});

保存看效果:
在这里插入图片描述
很简单,就不具体解释啦!

第二步:注册子应用

找到 examples/main/index.js 文件的第 23 行:

registerMicroApps(
  [
    {
      name: 'react16', // 应用名称
      entry: '//localhost:7100', // 应用入口文件
      container: '#subapp-viewport', // 应用挂载节点
      loader, // 应用加载器 
      activeRule: '/react16', // 应用路由匹配规则
    },
    {
      name: 'vue',
      entry: '//localhost:7101',
      container: '#subapp-viewport',
      loader,
      activeRule: '/vue',
    },
    ...
  ],
  {
    beforeLoad: [
      (app) => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
      },
    ],
    beforeMount: [
      (app) => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
      },
    ],
    afterUnmount: [
      (app) => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
      },
    ],
  },
);

可以看到,这里注册了很多个子应用,我们重点看一下这个 registerMicroApps 方法。

找到 src/apis.ts 文件的第 59 行:

export function registerMicroApps<T extends ObjectType>(
  apps: Array<RegistrableApp<T>>,
  lifeCycles?: FrameworkLifeCycles<T>,
) {
  // 过滤未注册过的应用,防止多次注册
  const unregisteredApps = apps.filter((app) => !microApps.some((registeredApp) => registeredApp.name === app.name));

  microApps = [...microApps, ...unregisteredApps];
 // 遍历每一个未注册的应用
  unregisteredApps.forEach((app) => {
    const { name, activeRule, loader = noop, props, ...appConfig } = app;
   // 注册应用(SPA)
    registerApplication({
      name,
      app: async () => {
        loader(true);
        await frameworkStartedDefer.promise;

        const { mount, ...otherMicroAppConfigs } = (
          await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
        )();

        return {
          mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
          ...otherMicroAppConfigs,
        };
      },
      activeWhen: activeRule,
      customProps: props,
    });
  });
}

ok,其实我们可以看到,在 registerMicroApps 方法中主要就是调用 registerApplication 方法去注册了每一个应用,而这里的 registerApplication 方法是 single-spa 库的方法,先上一张 single-spa 库的流程图(没了解过 single-spa 库也没关系,后面我们会详细分析它的源码的):

在这里插入图片描述

从上面流程图中我们可以知道,当 single-spa 匹配到路由信息后,会渲染对应的子应用,接着就会调用子应用的

app 方法对子应用进行渲染。

我们可以回到 src/apis.ts 文件的 registerApplication 方法:

// 注册应用
registerApplication({
  name,
  app: async () => {
    // 修改页面状态为 loading
    loader(true);
    // 等待 start 方法的调用
    await frameworkStartedDefer.promise;
    // 加载当前子应用,获取子应用的 mount 方法
    const { mount, ...otherMicroAppConfigs } = (
      // 调用 loadApp 加载子应用
      await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
    )();
    return {
      mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
      ...otherMicroAppConfigs,
    };
  },
  activeWhen: activeRule,
  customProps: props,
});

前面我们说了,当 single-spa 匹配到路由信息后,会渲染对应的子应用,接着就会调用子应用的

app 方法对子应用进行渲染。

可以看到,在 app 方法又调用了一个叫 loadApp 的方法,loadApp 很重要!!!我们后面用到的时候再具体分析。

第三步:设置默认进入的子应用

找到 examples/main/index.js 文件的第 103 行:

/**
 * Step3 设置默认进入的子应用
 */
setDefaultMountApp('/react16');

找到 src/effects.ts 文件的 setDefaultMountApp 方法:

export function setDefaultMountApp(defaultAppLink: string) {
  // 当调用 spa 的 start 方法后,如果没有匹配到任何子应用的话,会调用该事件
  window.addEventListener('single-spa:no-app-change', function listener() {
    // 获取 spa 的所有渲染过的应用
    const mountedApps = getMountedApps();
    // 如果从未渲染过任何子应用的话就将当前路径指向默认路径
    if (!mountedApps.length) {
      navigateToUrl(defaultAppLink);
    }

    window.removeEventListener('single-spa:no-app-change', listener);
  });
}

可以看到,如果从未渲染过任何子应用的话就将当前路径指向默认路径,我们这里传入的是 /react16,我们可以测试一下。

当我们访问 http://localhost:7099/ 地址的时候,qiankun 会自动的将我们的路径改为我们设置的默认路径 http://localhost:7099/react16
在这里插入图片描述

ok,我们继续往下看!

第四步:启动应用

找到 examples/main/index.js 文件的第 108 行:

/**
 * Step4 启动应用
 */
start();

找到 src/apis.ts 文件中的 start 方法:

export function start(opts: FrameworkConfiguration = {}) {
  frameworkConfiguration = { prefetch: true, singular: true, sandbox: true, ...opts };
  const { prefetch, urlRerouteOnly = defaultUrlRerouteOnly, ...importEntryOpts } = frameworkConfiguration;
  // 预加载所有子应用(默认开启)
  if (prefetch) {
    doPrefetchStrategy(microApps, prefetch, importEntryOpts);
  }
  // 根据当前浏览器环境判断是否是需要降级
  frameworkConfiguration = autoDowngradeForLowVersionBrowser(frameworkConfiguration);
  // 启动应用(urlRerouteOnly = true:仅路由发生变换的时候才触发自定义 popstate 事件)
  startSingleSpa({ urlRerouteOnly });
  // 已经调用了 started 标志
  started = true;
  // start 调用准备完毕回调
  frameworkStartedDefer.resolve();
}

可以看到,这里主要调用了 single-spa 库的 startSingleSpa 方法启动应用,最后一行有执行

准备完毕回调:

// start 调用准备完毕回调
frameworkStartedDefer.resolve();

ok,其实当我们调用了 single-spa 库的 startSingleSpa 方法的时候, single-spa 就会根据当前路由去匹配需要渲染的子应用,会调用子应用的 app 方法。

还记得我们在“第二步(注册子应用)”中的 registerMicroApps 方法?

找到 src/apis.ts 文件的第 59 行:

export function registerMicroApps<T extends ObjectType>(
  apps: Array<RegistrableApp<T>>,
  lifeCycles?: FrameworkLifeCycles<T>,
) {
  // 过滤未注册过的应用,防止多次注册
  const unregisteredApps = apps.filter((app) => !microApps.some((registeredApp) => registeredApp.name === app.name));

  microApps = [...microApps, ...unregisteredApps];
 // 遍历每一个未注册的应用
  unregisteredApps.forEach((app) => {
    const { name, activeRule, loader = noop, props, ...appConfig } = app;
   // 注册应用
    registerApplication({
      name,
      app: async () => {
        // 修改页面状态为 loading
        loader(true);
        // 等待 start 方法的调用
        await frameworkStartedDefer.promise;
        // 加载当前子应用,获取子应用的 mount 方法
        const { mount, ...otherMicroAppConfigs } = (
          await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
        )();
        return {
          mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
          ...otherMicroAppConfigs,
        };
      },
      activeWhen: activeRule,
      customProps: props,
    });
  });
}

可以看到,又回到了这里的 app 方法了,接着又调用了 loadApp 方法去加载子应用。

小伙伴们可以先停下来回顾一下 qiankun 的创建和启动步骤,下节见啦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/493057.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Figma转换为sketch,分享这3款工具

在我们的设计工作中&#xff0c;我们经常会遇到各种各样的设计文件相互转换的问题。 你经常为此头疼吗&#xff1f;当你遇到Figma转换Sketch文件的问题时&#xff0c;你是如何解决的&#xff1f;Figma转换Sketch文件有工具吗&#xff1f; 根据众多设计师的经验&#xff0c;本…

在竞争激烈的移动应用市场中获得成功,掌握决胜Framework技术

为何要学习framework&#xff1f; Framework&#xff0c;指的是对应用程序开发所需的核心工具和组件的封装和提供。在Android开发中&#xff0c;Framework是整个开发过程中的核心组成部分&#xff0c;提供了许多功能和服务&#xff0c;包括UI组件、数据存储、网络通信、多媒体…

第二十四章 策略模式

文章目录 前言传统方式解决鸭子问题完整代码抽象鸭子类野鸭子类北京鸭子类玩具鸭子类 一、策略模式基本介绍二、策略模式解决鸭子问题完整代码飞翔接口 FlyBehavior飞翔接口的子类实现飞翔技术高超 GoodFlyBehavior不会飞翔 NoFlyBehavior飞翔技术一般 BadFlyBehavior其他行为接…

文献阅读 Meta-SR: A Magnification-Arbitrary Network for Super-Resolution

题目 Meta-SR: A Magnification-Arbitrary Network for Super-Resolution Meta-SR: 用于超分辨率的任何放大网络 摘要 由于DCNN的发展&#xff0c;最近关于超分辨率的研究取得了巨大成功。然而&#xff0c;任意比例因子的超分辨率长期以来一直被忽视。以往的研究者大多将不同…

Stable-Diffusion AI画画本地搭建详细步骤

ChatGPT出来后&#xff0c;第一次感觉到人工智能真的可能要来了&#xff0c;因此也顺便尝试了下开源AI画画的搭建。网络上写的教程总是不那么面面俱到&#xff0c;因此本文参考了3篇文章才成功把Stable-Diffusion 本地搭建搭建了起来。参考教程在文末。 本文是本地搭建AI画画&a…

C/C++内存泄露检查利器—valgrind

1、Valgrind概述 Valgrind是一套Linux下&#xff0c;开放源代码&#xff08;GPL V2&#xff09;的仿真调试工具的集合。 Valgrind由内核&#xff08;core&#xff09;以及基于内核的其他调试工具组成。内核类似于一个框架&#xff08;framework&#xff09;&#xff0c;它模拟…

Android中的GPS开发

GPS简介 Gobal Positioning System&#xff0c;全球定位系统&#xff0c;是美国在20世纪70年代研制的一种以人造地球卫星为基础的高精度无线电导航的定位系统&#xff0c;它在全球任何地方以及近地空间都能够提供准确的地理位置、车行速度及精确的时间信息&#xff1b;它是具有…

2023年房地产抵押贷款研究报告

第一章 概述 房地产抵押贷款是一种以房地产为抵押品的贷款形式&#xff0c;包括个人和企业两种情况。个人房地产抵押贷款是指个人将名下房产作为抵押品向银行或其他金融机构申请贷款&#xff0c;而企业房地产抵押贷款则是指企业将自己名下的商业房产作为抵押品向金融机构申请贷…

202309读书笔记|《野性之美:非洲野生动物初窥》——走进自然界的野性之美

《野性之美: 非洲野生动物初窥》微读的一本书&#xff0c;图片居多&#xff0c;非常有视觉上的震撼。拍摄者也是我们孙姓的一员&#xff0c;孙长智。正如作者所说&#xff0c;与自然对话&#xff0c;你会感悟到生命之美、竞争之美、进化之美、和谐之美&#xff01; 我喜欢自然…

SPSS如何绘制常用统计图之案例实训?

文章目录 0.引言1.绘制简单条形图2.绘制分类条形图3.绘制分段条形图4.绘制简单线图5.绘制多重线图6.绘制垂直线图7.绘制简单面积图8.绘制堆积面积图9.绘制饼图10.绘制直方图11.绘制简单散点图12.绘制重叠散点图13.绘制矩阵散点图14.绘制三维散点图15.绘制简单箱图16.绘制分类箱…

【markdown工具配合图床】PicGo图床配置教程,一秒读懂配置

前言 看到这篇文章的大佬&#xff0c;我默认大家都会配置git&#xff0c;已经配置好ssh公钥。 此时你看到的这篇文章就是基于markdown工具&#xff08;VSCode&#xff0c;Typora&#xff09;编写的。 PicGo作为图床转换工具&#xff0c;并配合gitee作为图片服务器&#xff0…

java元注解和自定义注解的区别

Java的元注解和自定义注解是两个不同的概念。 元注解是Java内置的一组用于修饰其他注解的注解&#xff0c;包括Retention、Target、Inherited和Documented。它们可以控制被修饰的注解的保留策略、目标范围、是否继承等属性&#xff0c;并且可以在编写自定义注解时使用。 Retent…

国考省考结构化面试:综合分析题,社会现象(积极消极政策)、名言哲理(警句观点启示)、漫画反驳题等

国考省考结构化面试&#xff1a;综合分析题&#xff0c;社会现象&#xff08;积极消极政策&#xff09;、名言哲理&#xff08;警句观点启示&#xff09;、漫画反驳题等 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&…

【Java数据结构】优先级队列(堆)

优先级队列&#xff08;堆&#xff09; 概念模拟实现堆的概念堆的存储方式堆的创建向下调整堆的创建建堆的时间复杂度 堆的插入和删除堆的插入堆的删除 用堆模拟实现优先级队列 常用接口PriorityQueue的特性PriorityQueue常用接口介绍构造方法插入/删除/获取优先级最高的元素 P…

孙溟㠭篆刻,红木上的‘’椎凿稚趣‘’

了解中国传统篆刻的人&#xff0c;一定知道篆刻作品中追求的“金石气”。作为拥有3700多年历史的中国传统艺术&#xff0c;篆刻艺术是将书法&#xff08;主要是篆书&#xff09;和镌刻&#xff08;包括凿、铸&#xff09;相结合&#xff0c;制作印章&#xff0c;亦是汉字独有的…

Vivado 仿真器中以批处理或脚本模式(Batch or Scripted Mode)进行仿真

以下说明来自ug900:在 Vivado 仿真器中以批处理或脚本模式进行仿真 具体可以内容可自行查找 其中代码运行截图为自己实践的实例 Note: xelab, xvlog and xvhdl are not Tcl commands. The xvlog, xvhdl, xelab are Vivado-independent compiler executables. Hence, there is…

20230505使用amazon来批量翻译SRT格式的日语字幕为简体中文

20230505使用amazon来批量翻译SRT格式的日语字幕为简体中文 2023/5/5 19:03 百度搜索&#xff1a;使用 amazon 批量翻译 请严重注意&#xff1a;可能会扣费的&#xff01; https://aws.amazon.com/cn/blogs/china/translating-documents-with-amazon-translate-aws-lambda-and…

线性判别分析LDA计算例题详解

线性判别分析(Linear Discriminant Analysis, LDA)的核心思想是&#xff1a;将给定训练集投影到特征空间的一个超平面上&#xff0c;并设法使同类样本投影点尽可能接近&#xff0c;异类样本投影点尽可能远离 由于做题时针对的是解题过程&#xff0c;因此原理相关方面省略&#…

回文数:探索数字世界中的对称美学

本篇博客我会讲解力扣中的“9. 回文数”这道题&#xff0c;大家重点理解判断回文数的方法。 先来审题&#xff1a;这是题目链接。 来看几个输出示例&#xff1a; 还有一些条件&#xff1a; 第一反应是&#xff1a;为啥是个整数呢&#xff1f;万一是个字符串&#xff0c;那不…

windows下安装OpenCL

由于我的电脑是windows10&#xff0c;显卡是集显Intel UHD Graphics 630。 下载Intel的SDK for OpenCL&#xff0c;下载地址https://software.intel.com/en-us/opencl-sdk/choose-download&#xff0c;也可以在我的资源里面直接下载https://download.csdn.net/download/qq_363…