手把手教你使用 qiankun

11,449 阅读7分钟

qiankun 是由蚂蚁集团开源的一个基于 single-spa 的微前端实现库,它是目前主流的微前端解决方案之一。它对 single-spa 进行了大量的封装和加强,比如样式隔离。它让我们使用起来毫不费力。在本文中主要会讲解如何使用 qiankun 搭建微前端,以及它是如何工作的。

上手使用

此处我默认大家都有了一些前端基础,对于前端开发的必备环境,如:nodejsvscode 等此处就不在赘述。如果你还没有准备,建议先准备一下。因为本文依赖 node 来完成一些操作

👌不多说废话,直接上干货。对于任何微应用框架来说,都是由基座(即父应用,本文之后都称基座)和子应用组合构成的,所以第一步我们应该把基座搭建起来。

搭建基座

注:不限技术栈,但本文中的项目已 react 为主

我们使用 react 的脚手架创建一个项目,此项目将作为基座

npx create-react-app qiankun-base

项目创建后,我们根据 qiankun 的文档来走,首先我们需要使用 npm 或者是 yarn 来安装一下 qiankun,对于包管理工具各位自己选择自己喜欢的一个即可。本文就直接使用 npm

npm i qiankun -S

# or yarn add qiankun

根据文档可知,一个子应用想在基座中展示,需要在基座中进行注册。注册子应用需要使用 qiankun 提供的方法 registerMicroApps ,注册之后还需要调用 start 方法进行启动。

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'vueApp',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/app-vue',
  },
]);

// 启动 qiankun
start();

在 react 项目中使用 qiankun 只需将以上代码复制粘贴到 index.js 中即可

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'vueApp',
    entry: '//localhost:8080',
    container: '#container',
    activeRule: '/app-vue',
  },
  {
    name: 'reactApp',
    entry: '//localhost:4000',
    container: '#container',
    activeRule: '/app-react',
  },
]);

// 启动 qiankun
start();

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

/*
因为后续会加上 react-router-dom ,所以此处后续要记得加上BrowserRouter哦
<BrowserRouter>
 <App />
</BrowserRouter>
*/

为了更接近在实际中后台项目中的使用情况,我在此处引入 antDesign 使用它的 layout 组件搭建一个中后台的基本样子。老规矩我们先安装一下 antDesign

npm install antd

然后在 index.js 中导入 antDesign 的样式即可

import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'

antDesign的更多内容请查询官方文档

good! 接下来我们需要改造一下 App.js,此处我直接贴一下代码,因为只需要将 antDesign 文档的内容复制过来加以改动即可,无需多言。

import { useState } from 'react';
import { Layout, Menu } from 'antd';
import { DesktopOutlined, PieChartOutlined } from '@ant-design/icons';
import './App.css';

const { Header, Content, Footer, Sider } = Layout;

const App = () => {
  const [collapsed, setCollapsed] = useState(false);
  
  const onCollapse = collapsed => {
    setCollapsed(collapsed);
  };

  return (
    <Layout style={{ minHeight: '100vh' }}>
      <Sider collapsible collapsed={collapsed} onCollapse={onCollapse}>
        <div className="logo" />
        <Menu theme="dark" defaultSelectedKeys={['1']} mode="inline">
          <Menu.Item key="1" icon={<PieChartOutlined />}>
            Vue应用
          </Menu.Item>
          <Menu.Item key="2" icon={<DesktopOutlined />}>
            React应用
          </Menu.Item>
        </Menu>
      </Sider>
      <Layout className="site-layout">
        <Header className="site-layout-background" style={{ padding: 0 }} />
        <Content style={{ margin: '16px' }}>
          <div id="container" className="site-layout-background" style={{ minHeight: 360 }}></div>
        </Content>
        <Footer style={{ textAlign: 'center' }}>This Project ©2021 Created by DiDi</Footer>
      </Layout>
    </Layout>
  );
}

export default App;

// 别忘了在index.css或app.css中添加如下样式

#components-layout-demo-side .logo {
  height: 32px;
  margin: 16px;
  background: rgba(255, 255, 255, 0.3);
}

.site-layout .site-layout-background {
  background: #fff;
}

如果一切顺利你将看到如下界面

image.png

注:上边的代码是我修改后的代码,与文档代码有所出入。文档中的代码使用的是 class 组件,但是我更喜欢使用函数式组件 so!并且删除了一些不需要的代码

但是还不算完,因为 qiankun 是通过路由的变化来匹配微应用的,所以在基座中我们还应该加上路由,在 react 中,即 react-router-dom

npm install react-router-dom

安装完成之后,只需要在 App.js 中引入 Link 组件,然后将侧边栏的文字替换成 Link 即可

import { Link } from 'react-router-dom'
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline">
  <Menu.Item key="1" icon={<PieChartOutlined />}>
    <Link to="/app-vue">Vue应用</Link>
  </Menu.Item>
  <Menu.Item key="2" icon={<DesktopOutlined />}>
    <Link to="/app-react">React应用</Link>
  </Menu.Item>
</Menu>

// 别忘了使用 BrowserRouter 包一下 <App> 组件哦

创建子应用

本文将分别使用两个主流的前端框架 reactvue 来创建子应用,在子应用中要做的事情其实很简单,只需要导出子应用的生命周期即可,注意此处的生命周期并非指的是框架的生命周期。而是由 qiankun 规定的三种生命周期,分别是:bootstrapmountunmount。以下引用文档的描述

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {

}


/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {

}


/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props) {

}


/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {

}

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

vue

对于 vue 创建子应用,我这边没有完全安装 qiankun 的文档来,按照文档来可能有一些小坑。各位可以进行参考文档进行对比。

首先使用脚手架创建一个 vue2.x 的项目,不再赘述详细内容

vue create qiankun-mirco-vue

然后修改 main.js,导出三个生命周期,并修改运行时 publicPath什么是运行时的 publicPath ? 简单的理解就是,这个变量可以指定微应用资源加载的基础路径。整体代码如下:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";

Vue.config.productionTip = false;

// 文档中将此代码单独放到了一个文件中,此处是直接写在了 main.js 中,两种都可。但是 eslint-disable 需要加上
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

let instance = null;
function render(props = {}) {
  const { container } = props;
  // 文档中使用store,此处没有便删除了。
  // 文档中的router对象是在此处创建的,但是在router文件夹的index.js中已经创建好了,所以稍加改造直接导入就好,下方贴了代码
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector("#app") : "#app");
}

// 独立运行时 直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

export async function bootstrap() {
  console.log("[vue] vue app bootstraped");
}

export async function mount(props) {
  console.log("[vue] props from main framework", props);
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = "";
  instance = null;
}

router配置

为什么这要加一个 router 配置,上面的代码注释中有过解释,没有看到的小伙伴可以去瞄一眼。可以和文档进行一个对比

import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: () => import("../views/Home.vue"),
  },
  {
    path: "/about",
    name: "About",
    component: () => import("../views/About.vue"),
  },
];

const router = new VueRouter({
  base: window.__POWERED_BY_QIANKUN__ ? "/app-vue/" : "/",
  mode: "history",
  routes,
});

export default router;

然后还要修改 webpack 的配置,添加 vue.config.js 文件,添加以下内容即可。主要做了两件事,一是运行跨域,二是将微应用打包成一个 library

const { name } = require("./package");
module.exports = {
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: "umd", // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

完成以上两步之后,vue 子应用基本搭建完成。

react

对于 react 创建子应用,文档描述的很详细,但是还是有一点点小坑,具体内容如下,可以和文档进行对比:

创建一个 react 子应用,并安装 react-router-dom

npx create-react-app qiankun-mirco-react

npm install react-router-dom

然后修改 index.js,此处文档有坑,即在使用 react-router-dom 的时候应该将 BrowserRouter 包在 App 组件的外层。具体代码如下

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom'
import './index.css';
import App from './App';

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

function render(props) {
  const { container } = props;
  ReactDOM.render(
    <React.StrictMode>
      <Router basename={window.__POWERED_BY_QIANKUN__ ? '/app-react' : '/'}>
        <App />
      </Router>
    </React.StrictMode>,
    container ? container.querySelector('#root') : document.querySelector('#root')
  );
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

然后便是修改 webpack 的配置啦,如何修改,此处只需要完全按照 文档 的描述来即可。

细心的你可能发现了,此时 react 子应用的端口号和 react 基座的端口号是一样的?此时会出现问题,那么需要我们改一个子应用的端口号了。方法如下:

  1. 如果你的脚手架很新,在 node_modules 下找到 react-scripts 目录修改 scripts 目录下的 start.js 的64行将3000,改为4000即可(端口随便,但是要和注册的时候保持一致)
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 30004000;
  1. 如果你的脚手架很老,老脚手架创建的项目 scripts 目录是直接暴露出来的,所以直接找到文件修改即可

如果你有更好的办法,请指正

至此react子应用也已经搭建成功!!!那么看看效果如何,如果不出意外,你可以看到如下效果

image.png

image.png