微前端qiankun原理分析

主要研究对象qiankun,探究内部原理css

乾坤qiankun简介

1. 安装

$ yarn add qiankun # 或者 npm i qiankun -S
复制代码

2. 主应用注册

registerMicroApps([
    {
        name: 'reactApp',
        entry: '//localhost:3000',
        container: '#container',
        activeRule: '/views/app-react',
    },
    {
        name: 'vueApp',
        entry: '//localhost:8080',
        container: '#container',
        activeRule: '/views/app-vue',
    },
    {
        name: 'reactApp2',
        entry: 'http://localhost:9100/react-app2/template/index.html',
        container: '#container',
        activeRule: '/views/app-react2',
    }
]);
// 启动 qiankun
start();
复制代码

当微应用信息注册完以后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,全部 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。若是子应用还存在子路由,则须要配置子应用路由的basenamehtml

3. 子应用的改造

导出生命周期钩子

微应用须要在本身的入口 js (一般就是 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用前端

/** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用从新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 一般咱们能够在这里作一些全局变量的初始化,好比不会在 unmount 阶段被销毁的应用级别的缓存等。 */
export async function bootstrap() {
  console.log('react app bootstraped');
}

/** * 应用每次进入都会调用 mount 方法,一般咱们在这里触发应用的渲染方法 */
export async function mount(props) {
  ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

/** * 应用每次 切出/卸载 会调用的方法,一般在这里咱们会卸载微应用的应用实例 */
export async function unmount(props) {
  ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

/** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */
export async function update(props) {
  console.log('update props', props);
}
复制代码

修改Webpack配置

  1. 新增public-path.js文件,并在入口文件最顶部引用,用于修改运行时的 publicPath
  2. 容许开发环境跨域和 umd 打包,修改 webpack 打包:
const packageName = require('./package.json').name;

module.exports = {
  output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
  },
};
复制代码

关于public-path的描述能够参见此文档:webpack.docschina.org/guides/publ…vue

关于library的描述能够参见此文档:webpack.docschina.org/guides/auth…react

single-spa的使用

single-spa主要功能是实现了路由劫持与应用加载,但未实现js、css隔离webpack

乾坤qiankun的原理

在研究qiankun的原理以前,咱们应该试着提出一些问题,而后在阅读源码的过程当中去寻找答案。对于qiankun,咱们比较关心的问题有:git

  • qiankunsingle-spa是什么关系?
  • qiankun如何实现js沙箱?
  • qiankun如何实现css隔离?

registerMicroApps

配置微应用的第一步就是在主应用中注册子应用,配置好子应用的名称、入口、dom容器以及唤醒条件,以后qiankun会按照唤醒条件将子应用挂载到dom容器上。github

  1. 用法:registerMicroApps(apps, lifeCycles?)
    • 参数:
      • apps - Array<RegistrableApp> - 必选,微应用的一些注册信息
      • lifeCycles - LifeCycles - 可选,全局的微应用生命周期钩子
    • 详解
      • RegistrableApp
        • name - string - 必选,微应用的名称,微应用之间必须确保惟一
        • entry - string | { scripts?: string[]; styles?: string[]; html?: string } - 必选,微应用的入口
        • container - string | HTMLElement - 必选,微应用的容器节点的选择器或者 Element 实例。如container: '#root'container: document.querySelector('#root')
        • activeRule - string | (location: Location) => boolean | Array<string | (location: Location) => boolean> - 必选,微应用的激活规则
        • loader - (loading: boolean) => void - 可选,loading 状态发生变化时会调用的方法
        • props - object - 可选,主应用须要传递给微应用的数据
      • LifeCycles
        • beforeLoad - Lifecycle | Array<Lifecycle> - 可选
        • beforeMount - Lifecycle | Array<Lifecycle> - 可选
        • afterMount - Lifecycle | Array<Lifecycle> - 可选
        • beforeUnmount - Lifecycle | Array<Lifecycle> - 可选
        • afterUnmount - Lifecycle | Array<Lifecycle> - 可选
  2. 示例
import { registerMicroApps } from 'qiankun';

registerMicroApps([{
    name: 'app1',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/react',
    props: {
        name: 'kuitos'
    }
}], {
    beforeLoad: app => console.log('before load', app.name),
    beforeMount: [
        app => console.log('before mount', app.name)
    ]
});
复制代码
  1. 实现原理:背后调用single-sparegisterApplication方法,此处主要工做在registerApplicationapp参数中处理:
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,
});
复制代码

其中loadApp是核心,从新包装了single-spa所需applicatmountunmount方法,在这两个生命周期中分别加入了一些额外的操做。如在mount的时候,会执行beforeMountafterMount回调,以及mountSandbox开启沙箱。在unmount的时候,会执行beforeUnmountafterUnmount回调,以及unmountSandbox释放沙箱。web

  • qiankun是如何实现沙箱的?npm

    经过调用createSandboxContainer生成一个沙箱,若是当前浏览器环境支持Proxy则采用Proxy,反之则采用快照方式。对于Proxy模式来讲,首先调用createFakeWindow(rawWindow)生成一个原始window的复制版fakeWindow

loadApp函数原理

执行用户调用registerApplication时传入的app方法,加载应用

  1. 调用import-html-entry模块的importEntry函数,获取到对应子应用的html文件、可执行脚本文件以及publicpath
  2. 调用getDefaultTplWrapper将子应用的html内容用div标签包裹起来
  3. 调用createElement函数生成剔除html、body、head标签后的子应用html内容(经过innerHTML达到过滤效果)
  4. 调用getRender函数获得render函数
  5. 调用第4步获得的render,将container内部清空,并将子应用的dom元素渲染到指定的contanter元素上
  6. 调用getAppWrapperGetter函数,生成一个能够获取处理过的子应用dom元素的函数initialAppWrapperGetter,以备后续使用子应用dom元素
  7. 若是sandbox为true,则调用createSandboxContainer函数
  8. 执行execScripts函数,执行子应用脚本
  9. 执行getMicroAppStateActions函数,获取onGlobalStateChangesetGlobalStateoffGlobalStateChange,用于主子应用传递信息
  10. 执行parcelConfigGetter函数,包装mountunmount

createSandboxContainer函数原理

registerApplication函数原理

  1. 规则化参数,registerApplication参数有两种形式,首先将不一样的参数形式归结为同一种格式{name,loadApp,activeWhen,customProps}
  2. 检测注册的appName是否重名,重名则抛出异常
  3. application配置推入到全局数组apps中保存
  4. 若是是浏览器环境(以是否存在window变量做为依据),则:
    1. 确保在引入了jQuery的前提早下window.jQuery可以使用
    2. 执行reroute函数,并进行下列操做:
      1. 遍历apps数组,获取到NOT_MOUNTEDMOUNTEDLOADING_SOURCE_CODEapplication
      2. 检测是否执行了start方法,若是已经执行了(例如在页面上切换路由),则执行performAppChanges函数,反之执行loadApps函数(首次刷新页面时)

performAppChanges函数原理

利用Promise.resolve().then()将函数内的所有代码异步执行

  1. 利用window.dispatchEventCustomEvent触发single-spa:before-no-app-changesingle-spa:before-app-change以及single-spa:before-routing-event事件(这两个接口IE不支持)
  2. 执行队列中appmountunmount操做(具体操做有待进一步研究

start函数原理

启动 qiankun,能够传入一些资源获取方式以及是否采用沙箱等配置

  1. 用法参见:qiankun.umijs.org/zh/api#star…
  2. 实现原理:背后调用single-spastart方法,执行reroutesetUrlRerouteOnly函数

附录

参考资料

相关文章
相关标签/搜索