# 介绍

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

qiankun 孵化自蚂蚁金融科技基于微前端架构的云产品统一接入平台,在经过一批线上应用的充分检验及打磨后,我们将其微前端内核抽取出来并开源,希望能同时帮助社区有类似需求的系统更方便的构建自己的微前端系统,同时也希望通过社区的帮助将 qiankun 打磨的更加成熟完善。

目前 qiankun 已在蚂蚁内部服务了超过 200+ 线上应用,在易用性及完备性上,绝对是值得信赖的。

# 什么是微前端

TIP

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

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

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

  • 技术栈无关

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

  • 独立开发、独立部署

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

  • 增量升级

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

  • 独立运行时

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

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

更多关于微前端的相关介绍,推荐大家可以去看这几篇文章:

# qiankun 的核心设计理念

  • 🥄简单

由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。 为什么不用iframe

  • 🍡 解耦/技术栈无关

微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。

# 特性

  • 📦 基于 single-spa 封装,提供了更加开箱即用的 API。
  • 📱 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • 💪 HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
  • 🛡​ 样式隔离,确保微应用之间样式互相不干扰。
  • 🧳 JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • ⚡️ 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • 🔌 umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统。

# qiankun使用

在使用qiankun前我们先用single-spa这样更容易理解, 只用single-spa有很多缺陷不够灵活、不能动态加载js文件、样式不隔离、没有js沙箱的机制等,但我们可以先学习single-spa再去写qiankun会更简单

# single-spa使用

// parent应用
import { registerApplication, start} from 'single-spa'

registerApplication('myVueApp',
async ()=>{
  // await loadScript('http://localhost:10002/js/chunk-vendors.js')
    await loadScript('http://localhost:10001/app.js')
    return window.singleVue;
},
location => location.pathname.startsWith('/vue'), // 用户切换到/vue 的路径下,我需要加载刚才定的的子应用
// {a: 1, b:2}
)

start();
// npm install single-spa-vue --save
// 子应用
import singleSpaVue from 'single-spa-vue'

const appOptions = {
  el: '#vue', // 挂载到父应用中的id为vue的标签中
  router,
  render: h => h(App)
}

// 父应用加载子应用
const vueLifeCycle = singleSpaVue({
  Vue,
  appOptions
})

// 被父应用引用,修改publicPath
if(window.singleSpaNavigate){
  __webpack_public_path__ = 'http://localhost:10001/'
}
// 单独运行项目时
if(!window.singleSpaNavigate){ 
  delete appOptions.el;
  new Vue(appOptions).$mount('#app');
}

//  协议接入, 定义好协议,父应用会调用这些方法
export const bootstrap = vueLifeCycle.bootstrap;
export const mount = vueLifeCycle.mount;
export const unmount = vueLifeCycle.unmount;

TIP

父组件需要加载子应用 // bootstrap mount unmount // single-spa 可以实现路由劫持和应用加载 single-spa-vue single-spa-react

# 将子应用打包成lib提供给父应用

创建vue.config.js 配置webpack项

module.exports = {
  configureWebpack: {
    output:{
      library: 'singleVue',
      library: 'umd'
    },
    devServer:{
      port: 1000
    }
  }
}

打包后,是window.singleVue.bootstrap/mount/unmount 引用,如下所示

registerApplication('myVueApp',
async ()=>{
  // await loadScript('http://localhost:10002/js/chunk-vendors.js')
    await loadScript('http://localhost:10001/app.js')
    return window.singleVue;
},
location => location.pathname.startsWith('/vue'), // 用户切换到/vue 的路径下,我需要加载刚才定的的子应用
// {a: 1, b:2}
)

# qiankun使用(快速上手)

# 1. 安装 qiankun

$ yarn add qiankun # 或者 npm i qiankun -S
vue create qiankun-base // 主应用
vue create qiankun-vue // vue子应用
npx create-react-app qiankun-react // react 子应用
npm i element-ui -s // 可以在vue应用中加入element-ui试试

yarn add react-app-rewired --save // react子应用中加入react-app-rewired 来切换线路 安装这个工具后,在项目根目录中新建文件config-overrides.js文件。此时我们便可以在其中进行各种webpack的骚操作了~

添加文件config-overrides.js

.env 可配置react环境变量 ,我只配置了端口号

PORT=20000
WDS_SOCKET_PORT=20000

# 2. 在主应用中注册微应用

import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
  {
    name: 'react app', // app name registered
    entry: '//localhost:7100',
    container: '#yourContainer',
    activeRule: '/yourActiveRule',
  },
  {
    name: 'vue app',
    entry: { scripts: ['//localhost:7100/main.js'] },
    container: '#yourContainer2',
    activeRule: '/yourActiveRule2',
  },
]);
start();

当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

如果微应用不是直接跟路由关联的时候,你也可以选择手动加载微应用的方式:

import { loadMicroApp } from 'qiankun';
loadMicroApp(
  { 
    name: 'app', 
    entry: '//localhost:7100',
    container: '#yourContainer', 
  }
);

# 微应用

微应用不需要额外安装任何其他依赖即可接入 qiankun 主应用。

# 1. 导出相应的生命周期钩子

微应用需要在自己的入口 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) {
  console.log(props);
  ReactDOM.render(<App />, document.getElementById('react15Root'));
}
/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  ReactDOM.unmountComponentAtNode(document.getElementById('react15Root'));
}
/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

qiankun 基于 single-spa,所以你可以在这里找到更多关于微应用生命周期相关的文档说明。

无 webpack 等构建工具的应用接入方式请见这里

# 2. 配置微应用的打包工具

除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:

webpack:

const packageName = require('./package.json').name;
module.exports = {
  output: {
    library: `${packageName}-[name]`,
    libraryTarget: 'umd',
    jsonpFunction: `webpackJsonp_${packageName}`,
  },
};

相关配置介绍可以查看 webpack 相关文档