快速入门微前端

234 阅读3分钟

概念

微前端是指存在于浏览器中的微服务。它是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。

每个微前端都拥有独立的git仓库、package.json和构建工具配置。因此,每个微前端都拥有独立的构建进程和独立的部署/CI。这意味着,每个仓库能快速构建。

本文选取市面上主流微前端框架,对比分析选择一款合适的微前端框架,最后以 qiankun 为例,构建基础的微前端框架

技术方案

  • 1、接入成本
  • 2、隔离 (元素隔离、样式隔离、JS隔离)
  • 3、多实例
  • 4、性能(预加载、公共依赖服用)
  • 5、子应用保活
  • 6、子应用嵌套
  • 7、通信(父子、子子通信)
  • 8、技术栈无关
  • 9、社区活跃程度
  • 10、支持 esm
  • 11、微组件
框架实现原理维护者劣势
iframe原生沙箱浏览器原生1、浏览器前进后退状态 2、通信麻烦 3、全屏弹窗 4、性能问题 5、无法做 seo
single-spa路由监听国外1、无通信 2、不支持 js 沙箱 3、样式冲突 4、性能问题
qiankun继承 single-spa + 沙箱阿里1、样式隔离 2、微组件能力缺失
无界Iframe + WebComponent腾讯1、不稳定
empmodule-federation百度1、不能支持多框架 2、社区不活跃
micro-appWebComponent + 沙箱京东1、社区不活跃
garfishjs路由监听 + 沙箱字节1、社区不活跃

实践

主应用

import { registerMicroApps, start, initGlobalState } from 'qiankun';
const apps = [
    {
      "name": "star-demo",
      "entry": "//localhost:9528",
      "container": "#subapp-viewport",
      "activeRule": ["/star"]
    },
    {
      "name": "ghss-demo",
      "entry": "//localhost:8081",
      "container": "#subapp-viewport",
      "activeRule": ["/ghss"]
    }
];
const state = {
  vue: "vuepath",
  utils: "utilspath"
};

// 注册子应用
registerMicroApps(
  apps,
  {
    beforeLoad: app => console.log('before load', app.name),
    beforeMount: [
      app => console.log('before mount', app.name),
    ],
  },
);
const actions = initGlobalState(state)
// 监听state钩子
actions.onGlobalStateChange((state, prev) => {
  console.log(state, prev);
});
// 重新设置state
actions.setGlobalState(state);
// 卸载监听state钩子
actions.offGlobalStateChange();

// 启动微前端
start({
  prefetch: "all",
  sandbox: {
    strictStyleIsolation: true
  }
})

子应用

// 以vue为例
// vue.config.js
{
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
  },
	configureWebpack: {
    output: {
      // 以package.json的name作为应用名称,需要唯一,如有问题,需要改成和主应用注册名字一致
      library: `${name}-[name]`,
      // 把子应用打包成 umd 库格式
      libraryTarget: "umd",
      jsonpFunction: `webpackJsonp_${name}`
    }
  },
}

// main.js 入口文件
const getBaseRoute = pathname => {
  let baseRoute = pathname;
  if (Array.isArray(pathname)) {
    pathname.forEach(item => {
      if (location.pathname.indexOf(item) > -1) {
        baseRoute = item;
      }
    });
  }
  return baseRoute;
};

if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

// 渲染页面
const render = props => {
  const { pathname, container } = props || {};
	instance = new Vue({
    router, // router base设置
    store,
    render: h => h(App)
  }).$mount(container ? container.querySelector("#app") : "#app");
}
// 生命周期钩子
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子
export async function bootstrap() {
  console.log("app bootstraped");
}
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
  isUnMount = false;
  props.onGlobalStateChange((data) => {
    console.log(data);
  }, true);
  render(props);
}
// 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
export async function unmount() {
  console.log("app unmount");
  isUnMount = true;
  if (instance && isUnMount) {
    instance.$destroy();
    instance = null;
  }
}


// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

问题

样式隔离

  • 严格沙箱隔离
  • 命名空间隔离
  • 自行隔离

svg 问题

在 vue 项目中,svg 注入目标为 body,需要更换插件

font-face 问题

开启 shadow dom模式不支持 font-face,所以需要在父应用加载

动态脚本加载问题

子应用资源 url 没有被正确解析,需要手动拼接 publicPath

全局弹窗问题

开启shadow dom模式,全局弹窗不能显示,需要添加劫持脚本,代理到子应用根节点

原理

JS 沙箱

  • SnapshotSandbox 快照沙箱,只支持单实例
  • LegacySandbox 遗留沙箱,只支持单实例
  • ProxySandbox 代理沙箱,默认使用代理沙箱,支持多实例代理

样式隔离

  • css module
  • css namespace
  • css scoped
  • shadow dom

通信

  • EventBus(window 挂载对象)
  • url 传参
  • props 传参
  • 浏览器原生通信方式

登录

  • 单点登录

总结

本次探讨了现有的微前端方案,通过比较各方优缺点,以 qiankun 为例进行实践,并对一些常见的问题分析解决,并了解微前端的核心原理沙箱、通信部分,并对微前端单点登录方案进行了探讨。