前端微服务qiankun入门实践

完整代码地址在microfrontend-learning 

 

1. 创建项目

主应用是使用vue开发,两个子应用分别是vue、react, 创建命令如下:

# 创建主应用
vue create app-main
# 创建一个app-vue的子应用
vue create app-vue
# 创建一个app-react的子应用
npm install -g create-react-app
npx create-react-app app-react

2. 主应用改造

安装qiankun、ant-design-vue、vue-router

yarn add qiankun ant-design-vue vue-router # 或者 npm i qiankun  ant-design-vue vue-router -S

可编写配置文件.env.dev  用来方便配置子应用启动地址

# .env.dev 用于本地构建测试
VUE_APP_REACT_MICRO_APP=http://localhost:3000
VUE_APP_VUE_MICRO_APP=http://localhost:9000

新建micro.js

import {
  registerMicroApps,
  start,
} from "qiankun"

// 子应用注册信息
/**
 * name: 微应用名称 - 具有唯一性
 * entry: 微应用入口 - 通过该地址加载微应用
 * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
 * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
 */
const apps = [{
  name: "ReactMicroApp",
  entry: process.env.VUE_APP_REACT_MICRO_APP,
  container: "#frame",
  activeRule: "/react"
},
{
  name: "VueMicroApp",
  entry: process.env.VUE_APP_VUE_MICRO_APP,
  container: "#frame",
  activeRule: "/vue"
}]
/**
 * 注册子应用
 * 第一个参数 - 子应用的注册信息
 * 第二个参数 - 全局生命周期钩子
 */
registerMicroApps(apps, {
  // qiankun 生命周期钩子 - 加载前
  beforeLoad: (app) => {
    console.log("before load", app.name);
    return Promise.resolve();
  },
  // qiankun 生命周期钩子 - 挂载后
  afterMount: (app) => {
    console.log("after mount", app.name);
    return Promise.resolve();
  },
});

// 导出 qiankun 的启动函数
export default start;

在 app.vue 中用 v-if / v-show去控制是路由显示还是加载的子项目显示

<template>
  <div id="main-app">
     
    <section class="content-wrapper">
      <!-- 主应用渲染区,用于挂载主应用路由触发的组件 -->
      <router-view v-show="$route.name" />

      <!-- 子应用渲染区,用于挂载子应用节点 -->
      <section v-show="!$route.name" id="frame"></section>
    </section>
  </div>
</template>
// ...

入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,将id改为main-app 

配置vue.config.js

const path = require("path");

module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
  },
  devServer: {
    port: 8888,
    open: true,
    disableHostCheck: true,
  },
};

3. Vue子应用改造

在src下中新建public-path.js

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

修改main.js

import Vue from 'vue'
import VueRouter from "vue-router"
import Antd from "ant-design-vue"
import "ant-design-vue/dist/antd.css"
import "./public-path"
import App from './App.vue'
import routes from './router'

Vue.use(VueRouter)
Vue.use(Antd)
Vue.config.productionTip = false

let instance = null
let router = null

/**
 * 渲染函数
 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
 */
function render() {
  // 在 render 中创建 VueRouter,可以保证在卸载微应用时,移除 location 事件监听,防止事件污染
  router = new VueRouter({
    // 运行在主应用中时,添加路由命名空间 /vue
    base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/",
    mode: "history",
    routes,
  });

  // 挂载应用
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount("#app")
}

// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

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

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

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log("VueMicroApp unmount");
  instance.$destroy()
  instance = null
  router = null
}

打包配置修改(vue.config.js

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

module.exports = {
  devServer: {
    // 监听端口
    port: 9000,
    // 关闭主机检查,使微应用可以被 fetch
    disableHostCheck: true,
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    resolve: {
      alias: {
        '@': path.resolve(__dirname, 'src'),
      }
    },
    output: {
      // 微应用的包名,这里与主应用中注册的微应用名称一致
      library: 'VueMicroApp',
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      // 把微应用打包成 umd 库格式
      libraryTarget: 'umd',
      // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
      jsonpFunction: `webpackJsonp_VueMicroApp`,
    }
  }
}

 4. React子应用改造

在 src 目录新增 public-path.js

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

注意: 根据 qiankun 的文档进行配置后,启动 子应用vue 时报 __ webpack_public_path__ 未定义,是 eslint 的问题, webpack_public_path 不是全局变量所导致的, 需要在子应用 package.json 文件中 eslintConfig 配置全局变量后 重起服务 解决

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

 设置 history 模式路由的 base

<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'}>

入口文件 index.js 修改

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "antd/dist/antd.css";

import "./public-path";
import App from "./App";

/**
 * 渲染函数
 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行
 */
function render(props) {
  // const { container } = props;
  ReactDOM.render(<App />, document.querySelector("#root"));
}

// 独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

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

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log("ReactMicroApp 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-app-rewired

npm i -D react-app-rewired

根目录增加config-overrides.js

const path = require("path");
const name = "ReactMicroApp";
module.exports = {

  webpack: (config) => {
    // 微应用的包名,这里与主应用中注册的微应用名称一致
    config.output.library = `${name}`;
    // 将你的 library 暴露为所有的模块定义下都可运行的方式
    config.output.libraryTarget = "umd";
    // 按需加载相关
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = "window";
    config.resolve.alias = {
      ...config.resolve.alias,
      "@": path.resolve(__dirname, "src"),
    };
    return config;
  },

  devServer: function (configFunction) {
    return function (proxy, allowedHost) {
      const config = configFunction(proxy, allowedHost);
      // 关闭主机检查,使微应用可以被 fetch
      config.disableHostCheck = true;
      // 配置跨域请求头,解决开发环境的跨域问题
      config.headers = {
        "Access-Control-Allow-Origin": "*",
      };
      // 配置 history 模式
      // config.historyApiFallback = true;

      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"
  },

最后启动效果如下: 

参考资料:

https://qiankun.umijs.org/zh/guide/tutorial

https://juejin.cn/post/6844904158085021704

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值