微前端之qiankun 分别引入两种子应用 -- react && vue + vite

3,856 阅读7分钟

前言

本文旨在教你如何使用 qiankun 搭建一个qiankun-demo,主应用为React 两个子应用分别是React && vue + vite ;如果有疑惑或文章有误-->♥♥♥留言♥♥♥ Thanks

阅读了解些微前端

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

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。 微前端架构具备以下几个核心价值:

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

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

  • 增量升级

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

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

快进👉 微前端可以解决 一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题。 目前市面上有许多微前端框架, 例如 京东MicroApp,腾讯 无界 。。。

作者本人现在也靠着微前端对项目进行增量升级,不然以前的老项目真的是太难改动了(类目)

业界大厂纷纷推出微前端技术,微前端在大厂内部已经孵化的很成熟了,以后也必当会成为面试必问了😶(bushi)

搭建主应用

主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可。

1、下载项目

// TS
npx create-react-app qk-main --template typescript
// JS
npx create-react-app qk-main

2、安装qiankun

yarn add qiankun 或者 npm i qiankun -S

3、基于路由配置自动注册微应用

// src/index.tsx
// 引入配置函数
import { registerMicroApps, start } from 'qiankun';

//配置两个子应用 
registerMicroApps([
  {
    // 子应用名字
    name: 'qk-micro-react',
    // 子应用容器id 需要在父应用中创建相关元素
    container: '#qk-micro-react',
    // 子应用进入地址
    entry: 'http://localhost:3011',
    // 激活子应用的路由
    activeRule: '/qk-micro-react',
  },
  {
    name: 'qk-micro-vue',
    container: '#qk-micro-vue',
    entry: 'http://localhost:3012/',
    activeRule: '/qk-micro-vue',
  }
]);

//启动 qiankun
start({
  sandbox: {
    // 样式隔离特性
    experimentalStyleIsolation: true,
  }
})

代码预览

image.png

4、添加子应用对应的容器 (仔细看注释 这里比较容易出错嘞)

// App.tsx
import React from 'react';
import './App.css';
import { Link, BrowserRouter } from 'react-router-dom'

function App() {
  return (
    <div className="App">
      // 先做一个简单的路由跳转
      <BrowserRouter>
        <h3>
          <Link to="/qk-micro-react">微前端React</Link>
        </h3>
        <h3>
          <Link to="/qk-micro-vue">微前端Vue</Link>
        </h3>
      </BrowserRouter>
      // id 要与 registerMicroApps 当中的 container 对齐
      // react子应用挂载容器 id=qk-micro-react
      <div id='qk-micro-react' />
      // vue子应用挂在容器 id=qk-micro-vue
      <div id='qk-micro-vue' />
    </div>
  );
}

export default App;

页面预览

image.png

👉到此为止主应用环境搭建完毕

搭建react子应用

第一步是增加public-path.js 文件,用于修改运行时的 publicPath,修改 history模式的路由,但是要区分独立运行和在qiankun中运行两种环境,在入口文件导出微应用的生命周期函数,最后修改 webpack 打包,允许开发环境跨域和 umd 打包

1、下载项目

// TS
npx create-react-app qk-main --template typescript
// JS
npx create-react-app qk-main

2、在src目录下面新增public-path.ts

如果不添加 可能会出现静态资源无法加载的问题,子应用在微服务中打开 它默认使用了父应用的前缀,导致子应用无法加载自身的静态资源

if (window.__POWERED_BY_QIANKUN__) {
  // @ts-ignore
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

export {};

3、导出生命周期函数,修改路由基础路径

如果对这几个生命周期函数不理解的 可以查阅 single-spa docs

// 入口文件 index.tsx
//引入新增的 public-path 文件
import './public-path'
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

let root: ReactDOM.Root;

function createRoot(props: Record<string, any>) {
  // container中包含了qiankun创建的dom,它会插入一个带有id为root的dom
  const { container } = props;
  root = ReactDOM.createRoot(container ? container.querySelector('#root') : document.querySelector('#root'));
}
// 独立运行,直接调用 createRoot函数 render
if (!window.__POWERED_BY_QIANKUN__) {
  createRoot({});
  // @ts-ignore
  root.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>
  );
}

// lifecycle => 初始化
export async function bootstrap(props: Record<string, any>) {
  console.log(props)
}

// lifecycle => 挂载
export async function mount(props: Record<string, any>) {
  createRoot(props);
  //qiankun环境中渲染
  root.render(
    <BrowserRouter
      // 对两种不同的环境分别给出不同的基础路径
      basename={window.__POWERED_BY_QIANKUN__ ? '/qk-micro-react' : '/'}
    >
      <App />
    </BrowserRouter>
  )
}

// lifecycle => 卸载
export async function unmount(_props: Record<string, any>) {
  root.unmount();
}
reportWebVitals();

4、修改 webpack 配置

我们需要借助react-app-rewired@rescripts/cli插件来完成对webpack配置的修改,我会分两种方法来描述

方法一:

安装插件 @rescripts/cli

yarn add @rescripts/cli 或 npm i -D @rescripts/cli

根目录新增 .rescriptsrc.js

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

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer: (_) => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    // config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

修改package.json文件

"scripts": {
    "start": "rescripts start",
    "build": "rescripts build",
    "test": "rescripts test",
    "eject": "rescripts eject"
  },

方法二:

安装插件react-app-rewired

yarn add react-app-rewired 或 npm i -D react-app-rewired

根目录下新增 config-overrides.js文件

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

module.exports = {
  webpack: (config) => {
    config.output.library = `${name}-[name]`;
    config.output.libraryTarget = 'umd';
    // config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';

    return config;
  },

  devServer: (_) => {
    const config = _;

    config.headers = {
      'Access-Control-Allow-Origin': '*',
    };
    config.historyApiFallback = true;
    config.hot = false;
    config.watchContentBase = false;
    config.liveReload = false;

    return config;
  },
};

修改package.json文件

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

启动项目时指定3011端口 或者 根目录添加.ENV文件指定3011启动端口

port=3011

项目独立运行时

image.png

项目在微服务中打开时

image.png

如果微服务中无法打开 仔细查阅前面的注册配置是否有问题 FAQ

👉到此 React子应用已经接入了主应用,接下来开始接入vue3子应用

搭建 vue3 + vite 子应用

目前 qiankun 还没有兼容 vite,还需借助第三方插件 vite-plugin-qiankun来实现

1、下载项目

自选配置,尽量带路由,方便后续验证

npm init vue@latest

2、vite.config.ts中添加插件,修改路由配置

import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import qiankun from "vite-plugin-qiankun";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // 添加qiankun插件
    qiankun("qk-micro-vue", {
      useDevMode: true,
    }),
  ],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
  server: {
    port: 3012,
    // 如果出现图片资源无法加载 请加上跨域
    origin: "http://localhost:3012",
    cors: true,
  },
});

修改路由配置,分两种情况.

1、独立运行. 2、微服务中运行.

image.png

3. main.ts文件添加生命周期

vite-plugin-qiankun插件中导出initQianKun函数,在函数中定义生命周期函数。

再分别对独立运行和微前端中运行做出不同的渲染

image.png

指定3012端口启动项目

单独运行:

image.png

微前端环境中运行:

image.png

路由切换:

image.png

关于experimentalStyleIsolation

官方文档中表明 experimentalStyleIsolation只是实验性的样式隔离特性,神说要有光的这篇文章给出的例子易懂,建议阅读

当 experimentalStyleIsolation 被设置为 true 时,qiankun 会改写子应用所添加的样式为所有样式规则增加一个特殊的选择器规则来限定其影响范围,因此改写后的代码会表达类似为如下结构:

// 假设应用名是 react16
.app-main {
  font-size: 14px;
}

div[data-qiankun-react16] .app-main {
  font-size: 14px;
}

image.png

image.png

👉之前两个子应用的选择器规则都发生了改变,均加上了 data-qiankun = 'yourself app name'

总结

当应用在微前端中运行的时候,它的window会变成一个经过Proxy代理的对象

image.png

应用通过判断window.__POWERED_BY_QIANKUN的布尔值来确定运行的环境

这篇文章主要是给对qiankun docs阅读不明白的jym出的一个小demo,特别是 vite这一块,网上完整的文章不算太多,官网又不兼容vite,但vite又是趋势。这种场景挺尴尬的,希望今年qiankun能对vite也做出点兼容性上的改变。

👋后续作者也会在微前端领域持续学习并做出新更,希望大家能多多支持我,多给我点技术上的指导和坑位点,防止作者通宵熬夜疯狂input。🤞