什么是微前端

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

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

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

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级
    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

qiankun - 一套完整的微前端解决方案

特性

微前端无界destroyApp销毁应用 微前端 qiankun_bootstrap

  • 基于 single-spa
  • 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
  • HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
  • 样式隔离,确保微应用之间样式互相不干扰。
  • JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。

搭建微前端项目

主应用

主应用与技术栈无关,我们可以使用Vue、React、Angular、JQuery甚至ES5语法进行搭建。主应用的目的如下:

  1. 注册微应用;
  2. 为每个微应用提供dom容器;
  3. 启动主应用;

下面我是以vue为主应用一步一步进行讲解,先使用vue-cli3构建项目。

1.安装qiankun

$ npm install qiankun

2.注册微应用
在入口文件main.js中添加如下代码:

import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'
//注册应用
registerMicroApps([
  {
    name: "vue2App",
    props: { age: 10 }, //给子应用传数据
    entry: "//localhost:3001", //默认会加载这个html,解析里面的js,动态执行(子应用必须支持跨域)里面,是用fetch去请求的数据
    container: "#out-main", //挂载到主应用的哪个元素下
    activeRule: "/vue2", //当我劫持到路由地址为/vue2时,我就把http://localhost:3000这个应用挂载到#app-main的元素下
  },
  {
    name: "vue3App",
    entry: "//localhost:3002",
    // entry: { scripts: ["//localhost:7100/main.js"] },
    container: "#out-main",
    activeRule: "/vue3",
  },
]);

//step3 设置默认进入微应用
//setDefaultMountApp('/vue3')

//开启
start();

微应用

微应用不用额外安装qiankun即可接入主应用,大致分为下面几个步骤:

  1. 入口js文件平级目录下增加public-path.js文件;
  2. 入口js文件中引入public-path.js,修改并导出qiankun定义的三个钩子函数:bootstrap、mount、unmount;
  3. 使用history路由,并且路由base值要和activeRule匹配规则保持一致;
  4. 修改webpack配置,允许开发环境跨域及umd打包;

下面我们以vue cli3作为技术栈构建微应用
在src下增加public-path.js
qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加如下代码

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

关于运行时 publicPath 的技术细节,可以参考 webpack 文档

runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。

修改入口文件main.js

let instance = null;
function render(props = {}) {
  const { container } = props;
  window.props = props;
  //会把这个应用(也就是这个界面),插入到主应用的container的元素中去
  instance = new Vue({
    router,
    render: (h) => h(App),
  }).$mount(container ? container.querySelector("#app") : "#app"); //这里是挂载到自己的html中,主应用会拿到这个挂载的html,将其插入到主应用中
}

// 独立运行时
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;
}

路由配置

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

Vue.use(VueRouter);

/* Layout */

export const routes = [
  {
    path: "/",
    redirect: "/list",
  },
  {
    path: "/list",
    component: () => import("@/views/protocol/index"),
  },
];
const router = new VueRouter({
  base: "vue2",
  mode: "history",
  routes,
});
export default router;

配置webpack 在vue.config.js

module.exports = {
  lintOnSave: false,
  devServer: {
    port: "3001",
    headers: {
      "Access-Control-Allow-Origin": "*", //所有人都可以访问我的服务器
    },
  },
  configureWebpack: {
    output: {
      // library: `${name}-[name]`,
      library: `vue2App`,
      libraryTarget: "umd", // 把微应用打包成 umd 库格式
      // jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

下面我们以vue3作为技术栈构建微应用
在src下增加public-path.js

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

修改入口文件main.js

let instance = null;
function render(props = {}) {
  if (instance) return;
  const { container } = props;
  instance = createApp(App)
    .use(store)
    .use(router)
    .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 = null;
}

路由配置

import { createRouter, createWebHistory } from "vue-router";

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

const router = createRouter({
  history: createWebHistory("/vue3"),
  routes,
});

export default router;

配置webpack 在vue.config.js

// const { name } = require("./package");
module.exports = {
  lintOnSave: false,
  devServer: {
    port: "3002",
    headers: {
      "Access-Control-Allow-Origin": "*", //所有人都可以访问我的服务器
    },
  },
  configureWebpack: {
    output: {
      // library: `${name}-[name]`,
      library: `vue3App`,
      libraryTarget: "umd", // 把微应用打包成 umd 库格式
      // jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};