微前端实战

什么是微前端

微前端的概念:构建一个现代 Web 应用所需要的技术/策略/方法,具备多个团队独立开发、部署的特性

微前端的优势

  • 独立测试部署,各个模块相互独立,互不影响
  • 扩展性高
  • 技术兼容更好,各个模块可以使用不同的技术

微前端的缺点

  • 子应用之间共享资源能力较差
  • 需要对旧的代码改造升级才可以使用

目前主流的微前端解决方案

  • iframe (最大的问题是 刷新页面 路由会丢失 本质刷新的主应用 而不是路由应用 所以被放弃了)
  • single-spaqiankun(是基于single-spa)封装的 这是一种基座模式(通过搭建基座配置中心来管理子应用)
  • ESM 是 ES module 的缩写
  • EMP 这是一种去中心模式(脱离基座模式 每个应用之间可以彼此分享资源)
  • web Components

qiankun 和 single-spa 实战

single-spa

首先我们创建子应用,子应用我用的是 vue-cli,只需要简单的配置即可 路由和 babel

  • single-child1

安装npm i single-spa-vue

修改 main.js 文件

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import singleSpaVue from "single-spa-vue";

Vue.config.productionTip = false;

// new Vue({
//   router,
//   render: (h) => h(App),
// }).$mount("#app");
const appOptions = {
  el: "#vue", // 需要挂载的父应用的节点
  render(h) {
    return h(App);
  },
  router,
};

// 当父应用 调用我的时候,控制子应用路由跳转的资源引用路径
if (window.singleSpaNavigate) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = "http://localhost:1000/";
}
// 支持应用独立运行、部署,不依赖于基座应用
if (!window.singleSpaNavigate) {
  delete appOptions.el;
  new Vue(appOptions).$mount("#app");
}

const vueLifecycles = singleSpaVue({
  Vue,
  appOptions,
});

// 导出生命周期
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;

配置 vue.config.js 把项目打包成 umd 格式

module.exports = {
  configureWebpack: {
    devServer: {
      port: 1000,
    },
    output: {
      library: "app1",
      libraryTarget: "umd",
    },
  },
};

修改路由的 base

const router = new VueRouter({
  mode: "history",
  // 这个名字需要和 打包的名字  以及 主应用引用的名称一样
  base: "/app1",
  routes,
});
  • single-parent

然后创建基座应用 ,同样我们用 vue-cli 来创建一个简单的应用

然后安装 single-spa

修改 main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import { registerApplication, start } from "single-spa";

Vue.config.productionTip = false;

// 远程加载子应用
function createScript(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    const firstScript = document.getElementsByTagName("script")[0];
    firstScript.parentNode.insertBefore(script, firstScript);
  });
}

// 记载函数,返回一个 promise
function loadApp(url, globalVar) {
  // 支持远程加载子应用
  return async () => {
    await createScript(url + "/js/chunk-vendors.js");
    await createScript(url + "/js/app.js");
    // 这里的return很重要,需要从这个全局对象中拿到子应用暴露出来的生命周期函数
    return window[globalVar];
  };
}

const app = [
  {
    // 子应用名称
    name: "app1",
    // 子应用加载函数,是一个promise
    app: loadApp("http://localhost:1000", "app1"),
    // 当路由满足条件时(返回true),激活(挂载)子应用
    activeWhen: (location) => location.pathname.startsWith("/app1"),
    // 传递给子应用的对象
    customProps: {},
  },
  {
    // 子应用名称
    name: "app2",
    // 子应用加载函数,是一个promise
    app: loadApp("http://localhost:2000", "app2"),
    // 当路由满足条件时(返回true),激活(挂载)子应用
    activeWhen: (location) => location.pathname.startsWith("/app2"),
    // 传递给子应用的对象
    customProps: {},
  },
];
// 注册子应用
for (let i = app.length - 1; i >= 0; i--) {
  registerApplication(app[i]);
}
new Vue({
  router,
  mounted() {
    // 启动
    start();
  },
  render: (h) => h(App),
}).$mount("#app");

修改 app.vue 添加一个挂载的节点


然后运行即可

qiankun

qiankun 是基于 single-spa 的封装 ,qiankun 也是现在主流的微应用方案,

  • qiankun-child

修改 main.js

import Vue from "vue";
import App from "./App.vue";
import VueRouter from "vue-router";
import routes from "./router";
import "./public-path";
Vue.config.productionTip = false;
let router = null;
let instance = null;
// 渲染函数
function render(props = {}) {
  const { container } = props;
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? "/app1" : "/",
    mode: "history",
    routes: routes.options.routes,
  });

  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 = null;
}

添加一个 public-path.js 文件

if (window.__POWERED_BY_QIANKUN__) {
  // 这里是支持修改的 原理和single-spa是一样的
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

添加 vue.config.js

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

安装 qiankun npm i qiankun -S

修改 main.js

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import { registerMicroApps, start } from "qiankun";
Vue.config.productionTip = false;

registerMicroApps([
  {
    name: "app1", // app name registered
    entry: "http://localhost:1000/app1",
    container: "#vue",
    activeRule: "/app1",
  },
  {
    name: "app2",
    entry: "http://localhost:2000/app2",
    container: "#vue",
    activeRule: "/app2",
  },
]);

new Vue({
  router,
  mounted() {
    start();
  },
  render: (h) => h(App),
}).$mount("#app");

APP.vue 中要添加一个挂载的节点


想要熟悉 ,可以自己实战一下,然后参照官方文档,相信会有一定的理解,之后再学习原理

single-spa 和 qiankun

  • single-spa
    single-spa 只是解决了应用加载和切换问题,例如 css 隔离和 js 隔离这个都没有解决,而是留给开发者自己去处理

  • qiankun 帮我们解决了 js 隔离和 css 隔离,让开发者开箱即用,

js 隔离

qiankun 的隔离主要是基于 快照沙箱 和 代理沙箱实现的

js 沙箱:主应用有自己的一套 window,子应用拥有另一套私有的 window,子应用所有的操作都只在自己的上下文中进行,这样一个个的子应用就和主应用隔离起来,
因此主应用的加载不会和子应用相互污染,每个子应用都是独立的

  • 快照沙箱(SnapshotSandbox)
    只有当浏览器不支持 proxy 的时候 才使用快照沙箱 (在沙箱挂载和卸载的时候记录快照,在应用切换的时候恢复环境)
    优点:兼容性好
    缺点:无法同时运行多个沙箱

  • 代理沙箱
    代理沙箱分为 ProxySandbox (多例)和 LegacySandbox(单例)
    当有多个实例的时候,比如有 A、B 两个应用,A 应用就活在 A 应用的沙箱里面,B 应用就活在 B 应用的沙箱里面,A 和 B 无法互相干扰,这样的沙箱就是代理沙箱 主要是通过 es6 的 proxy 实现

    Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
    简单来说就是,可以在对目标对象设置一层拦截。无论对目标对象进行什么操作,都要经过这层拦截

    优点:可以同时运行多个沙箱

    缺点:兼容性较差

css 隔离

  • 动态样式表 (a 应用激活 加载 a 应用的样式表 a 应用激活 加载 a 应用的样式表)
  • 工程化手段 - BEM、CSS Modules、CSS in JS(通过一系列约束和编译时生成不同类名、JS 中处理 CSS 生成不同类名来解决隔离问题)
  • Shadow DOM

为子应用的根节点创建一个 shadow root ,整个应用所有的 dom 将形成一颗 shadow tree,shadowDom 的特点是,它内部所有节点的样式对树外面的节点无效,因此自然就实现了样式隔离。

优点:完全隔离

缺点:有一些弹窗组件时挂载到 body 下面的,所以弹窗样式可以能会有问题,需要单独处理

应用通信

qiankun 的通知本质就是一个发布订阅模式

  • 主应用
    新建 src->actions.js
import { initGlobalState } from "qiankun";
import store from "./store";

const initialState = {
  //这里写初始化数据
};

// 初始化 state
const actions = initGlobalState(initialState);
actions.onGlobalStateChange((state, prev) => {
  //监听公共状态的变化
  console.log("主应用: 变更前");
  console.log(prev);
  console.log("主应用: 变更后");
  console.log(state);
  store.commit("setProject", state);
});

export default actions;

在 vue 文件中使用

method:{
  add(){
     actions.setGlobalState('0');//通过setGlobalState改变全局状态
  }
}

微应用
新建 src->actions.js

function emptyAction() {
  //设置一个actions实例
  // 提示当前使用的是空 Action
  console.warn("Current execute action is empty!");
}

class Actions {
  // 默认值为空 Action
  actions = {
    onGlobalStateChange: emptyAction,
    setGlobalState: emptyAction,
  };

  //设置 actions
  setActions(actions) {
    this.actions = actions;
  }

  /**
   * 映射
   */
  onGlobalStateChange(...args) {
    return this.actions.onGlobalStateChange(...args);
  }
  //映射
  setGlobalState(...args) {
    return this.actions.setGlobalState(...args);
  }
}
const actions = new Actions();
export default actions;

在 main.js 注入 action

export async function mount(props) {
  actions.setActions(props); //注入actions实例
  render(props);
}

在.vue 文件中使用

import actions from '../actions'//导入实例
mounted() {
    actions.onGlobalStateChange((state) => { //监听全局状态
      this.a = state
    }, true);
  },
  methods:{
      butClick(){
          actions.setGlobalState({ id: '11'})//改变全局状态
      }
  }

参考文章

30 分钟掌握微前端

微前端框架 qiankun 之原理与实战

qiankun 官网

你可能感兴趣的:(微前端实战)