初识微前端

216 阅读3分钟

由于最近项目中有可能会使用其他前端架构,因此提前了解了些跟这方面挂钩的一些东西,如何兼容使用多个前端架构,这就出现了一种新的架构方法:微前端。有点类似后端的微服务一样,把一个大型项目拆开成个各个服务去运行

1、背景

微前端(Micro Frontends)是一种将前端应用划分为独立、可组合的子应用的架构方法。这种方法允许开发团队采用不同的技术栈、框架和工具,同时保持应用的整体一致性。微前端的核心理念是将微服务概念应用于前端开发,实现对前端应用模块化和解耦的优势。简单理解可以合在一起组合一个大型综合应用,也可以拆开后单独应用

2、优点

  • 提高开发效率:团队可以独立开发、测试和部署各个子应用。
  • 技术栈无关:每个子应用可以使用适合其需求的技术栈,无需与其他子应用保持一致。
  • 可扩展性:子应用可以轻松添加或删除,整个系统的复杂性得到有效控制。

3、常用解决方案

  • Single-spa: 是一个用于将多个独立的应用组合成一个整体应用的JavaScript框架。它允许开发者使用不同的技术栈创建子应用,并将它们组合成完整的应用。
    Github仓库: single-spa
  • 乾坤(qiankun) : 由蚂蚁金服开源的一个基于single-spa的微前端实现方案。它提供了更丰富的功能,包括沙箱隔离、样式隔离等。
    Github仓库: qiankun
  • 代理方案: 可以使用相关的代理方案,通过路由进行区分不同的应用。但是不同应用中的切换,会有一个闪一下的效果。 文档地址: Webpack Module Federation 文档地址: nginx
  • iframe:如果是不同源的子应用的话,就需要配置代理,否则消息通信就存在问题
  • 以及其他大厂的微前端方案:京东的Micro App、字节跳动Garfish、SAP的Liugi

4、入门案列

  • 1、Single-spa

文档地址: # single-spa-vue

$ git clone https://github.com/joeldenning/coexisting-vue-microfrontends.git

# First terminal tab
$ cd root-html-file
$ npm install
$ npm run serve

# Second terminal tab
$ cd app1
$ npm install
$ npm run serve

# Third terminal tab
$ cd app2
$ npm install
$ npm run serve

# Fourth terminal tab
$ cd navbar
$ npm install
$ npm run serve

关键主应用代码:

{
"imports": {
  "navbar": "http://localhost:8080/js/app.js",
  "app1": "http://localhost:8081/js/app.js",
  "app2": "http://localhost:8082/js/app.js",
  "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js",
  "vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
  "vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js"
    }
}

singleSpa.registerApplication(
'navbar',
() => System.import('navbar'),
location => true
);

singleSpa.registerApplication(
'app1',
() => System.import('app1'),
location => location.pathname.startsWith('/app1')
)

singleSpa.registerApplication(
'app2',
() => System.import('app2'),
location => location.pathname.startsWith('/app2')
)

singleSpa.start();

关键子应用代码:

//app2/src/main.js
import './set-public-path'
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import singleSpaVue from 'single-spa-vue';

const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: {
    render: (h) => h(App),
    router,
  },
});

export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;

3in2m-7zs6r.gif

其他案例:single-spa其他例子

  • 2、乾坤(qiankun)
$ git clone https://github.com/umijs/qiankun.git
$ cd qiankun
$ yarn install
$ yarn examples:install
$ yarn examples:start

关键主应用代码:

//在主应用入口:qiankun/examples/main/index.js下设置,主应用可以是react、vue或者其他第三方架构
//通过对registerMicroApps,对各种框架应用进行设置入口和拦截
/**
 * Step1 初始化应用(可选)
 */
render({ loading: true });

const loader = (loading) => render({ loading });

/**
 * Step2 注册子应用
 */

registerMicroApps(
  [
    {
      name: 'react16',
      entry: '//localhost:7100',
      container: '#subapp-viewport',
      loader,
      activeRule: '/react16',
    },
    {
      name: 'react15',
      entry: '//localhost:7102',
      container: '#subapp-viewport',
      loader,
      activeRule: '/react15',
    },
    {
      name: 'vue',
      entry: '//localhost:7101',
      container: '#subapp-viewport',
      loader,
      activeRule: '/vue',
    },
    {
      name: 'angular9',
      entry: '//localhost:7103',
      container: '#subapp-viewport',
      loader,
      activeRule: '/angular9',
    },
    {
      name: 'purehtml',
      entry: '//localhost:7104',
      container: '#subapp-viewport',
      loader,
      activeRule: '/purehtml',
    },
    {
      name: 'vue3',
      entry: '//localhost:7105',
      container: '#subapp-viewport',
      loader,
      activeRule: '/vue3',
    },
  ],
  {
    beforeLoad: [
      (app) => {
        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
      },
    ],
    beforeMount: [
      (app) => {
        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
      },
    ],
    afterUnmount: [
      (app) => {
        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
      },
    ],
  },
);

const { onGlobalStateChange, setGlobalState } = initGlobalState({
  user: 'qiankun',
});

onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev));

setGlobalState({
  ignore: 'master',
  user: {
    name: 'master',
  },
});

/**
 * Step3 设置默认进入的子应用
 */
setDefaultMountApp('/react16');

/**
 * Step4 启动应用
 */
start();

runAfterFirstMounted(() => {
  console.log('[MainApp] first app mounted');
});


关键子应用代码:

// 在子应用的入口qiankun/examples/vue/src/main.js
export async function bootstrap() {
  console.log('[vue] vue app bootstraped');
}

export async function mount(props) {
  console.log('[vue] props from main framework', props);
  storeTest(props);
  render(props);
}

export async function unmount() {
  instance.$destroy();
  instance.$el.innerHTML = '';
  instance = null;
  router = null;
}

example.gif

5、qiankunsingle-spa 差异性

qiankun是基于single-spa封装,在single-spa基础上,又提供了一些额外的功能:

  • 样式隔离,确保微应用之间样式互相不干扰。
  • JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。