玩命加载中 . . .

微前端qiankun实践


项目架构

  • 主应用:umi 搭建的框架
  • 子应用:react
  • 子应用:也是 umi 搭建的框架
  • 子应用:vue3

要达到的效果:

  • react 子应用
  • umi 子应用
  • vue 子应用

主应用配置

umi 搭建主应用

安装 qiankun

yarn add qiankun

安装 @umijs/plugin-qiankun

yarn add @umijs/plugin-qiankun

注册子应用

在项目根目录的 .umirc.ts 中配置:

{
  ...
  
  qiankun: {
    master: {
      // 注册子应用信息
      apps: [
        {
          name: 'micro-react', // 唯一 id ,必须与路由中的 microApp 保持一致
          entry: 'http://localhost:8001/', // html entry
        },
        {
          name: 'micro-umi', // 唯一 id ,必须与路由中的 microApp 保持一致
          entry: 'http://localhost:8002', // html entry
        },
      ]
    }
  }
}

配置路由

我这里是在 config/routes.ts 进行路由的统一管理的,然后在 .umirc.ts 引入:

import { defineConfig } from 'umi';
import routes from './config/routes';

export default defineConfig({
  routes,
  qiankun: {
    ...
  },
});

config/routes.ts

{
  name: "react 子应用",      // 侧边栏的菜单名
  path: "/micro-react",     // 跳转的路径
  icon: 'smile',            // umi 中默认使用 antd 的 icon 名
  microApp: "micro-react",  // 注册子应用的名称,与 .umirc.ts 中的 qiankun 里 的 name 保持一致
},
{
  name: 'umi 子应用', 
  path: '/micro-umi',
  icon: 'smile', 
  microApp: 'micro-umi' 
},

子应用配置

react 子应用

搭建 react 项目

确保项目能正常启动:

npx create-react-app micro-react
cd micro-react
yarn start

安装 react-app-rewired

yarn add react-app-rewired -D

修改运行时的 publicPath

src/ 下新建 public-path.js ,内容如下:

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
/**
 * 坑
 * 直接放index.js import 下面:微应用中静态资源(图片)不显示
 * 直接放index.js 最上方 运行报错
 */

引入 public-path.js 并导出生命周期

将入口文件 src/index.js 的所有代码替换为:

import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

function render(props) {
  const { container } = props;
  ReactDOM.render(<App />, 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'));
}

export async function update(props) {
  console.log('update props', props);
}

修改 webpack 配置

我们已经安装了 react-app-rewired ,因此在根目录下新建 config-overrides.js 文件,添加下面的代码

如果安装的是 @rescripts/cli ,则在根目录新增 .rescriptsrc.js

const { name } = require('./package');

module.exports = {
  webpack: (config, env) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    config.output.globalObject = 'window';
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
    return config;
  },
  devServer: (_) => {
    const config = _;
    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;
    config.injectClient = false;
    return config;
  }
}

修改 package.json 的配置

package.json 下的 script 修改为:

"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
},

管理端口

在项目根目录下新建 .env 文件,便于管理端口号:

PORT=8001

启动子应用服务

yarn start

umi 子应用

安装 @umijs/plugin-qiankun

yarn add @umijs/plugin-qiankun -D

注册刚安装的插件

跟主应用一样,在项目根目录的 .umirc.ts 中配置:

qiankun: {
  slave: {}
}

添加后如下所示:

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/', component: '@/pages/index' },
  ],
  fastRefresh: {},
  qiankun: {
    slave: {}
  }
});

导出相应生命周期钩子

src/app.ts 中添加如下代码,如果 app.ts 不存在则新建一个即可:

export const qiankun = {
  // 应用加载之前
  async bootstrap(props) {
    console.log('app1 bootstrap', props);
  },
  // 应用 render 之前触发
  async mount(props) {
    console.log('app1 mount', props);
  },
  // 应用卸载之后触发
  async unmount(props) {
    console.log('app1 unmount', props);
  },
};

管理端口

在项目根目录下新建 .env 文件,便于管理端口号:

PORT=8002

启动子应用服务

yarn start

vue 子应用

搭建 vue 项目

确保项目能正常启动:

vue create micro-vue
cd micro-vue
yarn serve

修改运行时的 publicPath

src/ 下新建 public-path.js ,写入如下代码:

if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

引入 public-path.js 并导出生命周期

import "./public-path"
import { createApp } from "vue"
import App from "./App.vue"

let app = createApp(App)
function render(props = {}) {
  const { container } = props
  app.mount(container ? container.querySelector("#vueApp3") : "#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() {
  app.unmount()
  app = null
}

修改 webpack 配置

将根目录下的 vue.config.js 的代码全部替换为:

// 注意:这里的 name 要与主应用中注册的子应用名称保持一致
const { name } = require('./package');
module.exports = {
  devServer: {
    port: 8003, // 监听端口
    // 关闭主机检查,使微应用可以被 fetch
    // disableHostCheck: true,
    headers: {  // 配置跨域请求头,解决开发环境的跨域问题
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      // 子应用的名称
      library: `${name}-[name]`,
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      // 按需加载相关,设置为 webpackJsonp_微应用名称 即可
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
};

启动子应用服务

yarn serve

小 tip : 可以通过 yarn serve -- --port 端口号 的方式指定服务端口号

遇到的问题

  1. 主应用连接子应用时:Unhandled Rejection (TypeError): Failed to fetch

![Failed to fetch](./微前端qiankun实践/Failed to fetch.png)

  1. ValidationError: Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.

![Invalid configuration object](./微前端qiankun实践/Invalid configuration object.png)
这里报错,是因为是 webpack5.x 不兼容 jsonpFunction 的写法,需要替换成 chunkLoadingGlobal

  1. 启动 vue 子应用服务时:'__webpack_public_path__' is not defined

![‘__webpack_public_path__’ is not defined](./微前端qiankun实践/webpack_public_path is not defined.png)

这个报错是 eslint 的问题, __webpack_public_path__ 不是全局变量所导致的。在子应用 package.json 文件中 eslintConfig 配置全局变量后 重起服务 解决:

"eslintConfig": {
  ...,
  "globals": {
    "__webpack_public_path__": true
  }
}

动态注册子应用

删除子应用注册的配置

删除 .umirc.ts 中的下面代码中带 ‘-‘ 的行,其余保持不变:

qiankun: {
    master: {
      // 注册子应用信息
    -  apps: [
    -    {
    -      name: 'micro-react', // 唯一 id
    -      entry: 'http://localhost:8001/', // html entry
    -    },
    -    {
    -      name: 'micro-umi', // 唯一 id
    -      entry: 'http://localhost:8002', // html entry
    -    },
    -    {
    -      name: 'micro-vue', // 唯一 id
    -      entry: 'http://localhost:8003', // html entry
    -    },
    -  ],
    },
  },

配置 mock

mock 文件夹下新建 config.ts ,写入以下代码:

export default {
  '/api/getMicro': {
    apps: [
      {
        name: 'micro-react', // 唯一 id
        entry: 'http://localhost:8001/', // html entry
      },
      {
        name: 'micro-umi', // 唯一 id
        entry: 'http://localhost:8002', // html entry
      },
      {
        name: 'micro-vue', // 唯一 id
        entry: 'http://localhost:8003', // html entry
      },
    ]
  }
}

运行时动态配置子应用

根据 官网 所说,该配置是在 src/app.ts 中开启的,因此:

src/ 新建 app.ts ,写入以下代码:

// 从接口中获取子应用配置,export 出的 qiankun 变量是一个 promise
export const qiankun = fetch('/api/getMicro') // 这里的接口为上文中 mock/config.ts 配置的接口
  .then(res => {
    return res.json();
  })
  .then(({ apps }) => ({
    // 注册子应用信息
    apps,
    // 完整生命周期钩子请看 https://qiankun.umijs.org/zh/api/#registermicroapps-apps-lifecycles
    lifeCycles: {
      afterMount: (props) => {
        console.log(props);
      },
    },
    // 支持更多的其他配置,详细看这里 https://qiankun.umijs.org/zh/api/#start-opts
  }));

效果

react 子应用

可以看到,主应用依然能正确加载子应用。

主应用动态装载子应用路由

TODO

  • 非 umi 搭建主应用
  • 主应用动态装载子应用(路由)
  • 父子应用通讯

相关链接


文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录