微前端理论和实践

微服务的主要思路

  • 将应用分解为小的、互相连接的微服务,一个微服务完成某个特定功能 。
  • 每个微服务都有自己的业务逻辑和适配器,不同的微服务,可以使用不同的技术去实现。
  • 使用统一的网关进行调用。

微服务的主要思路是化繁为简 ,通过更加细致的划分,使得服务内部更加内聚,服务之间耦合性降低,有利于项目的团队开发和后期维护。把微服务的概念应用到前端, 前端微服务/微前端服务就诞生了,简称其为微前端。

微前端是什么

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

WEB应用面临的问题

  • DX(developer experience)
    • 业务领域的代码库不够独立和高度可重用
    • 相同的产品功能由多个团队开发 / 产品功能难以保持统一
    • 新的产品理念无法在不同的应用中快速复用 / 实现
    • 快速迭代新子业务 / 干净移除将被淘汰的子业务
  • UX(user experience)
    • 性能体验
    • 页面跳转和用户体验问题
  • 多个系统在一个仓库应用中,不同子应用独立SPA模式
  • 系统分为多个仓库,独立上线部署,采用MPA模式

任何新技术的产生都是为了解决现有场景和需求下的技术痛点,微前端也不例外:

  1. 拆分和细化:当下前端领域,单页面应用(SPA)是非常流行的项目形态之一,而随着时间的推移以及应用功能的丰富,单页应用变得不再单一而是越来越庞大也越来越难以维护,往往是改一处而动全身,由此带来的发版成本也越来越高。微前端的意义就是将这些庞大应用进行拆分,并随之解耦,每个部分可以单独进行维护和部署,提升效率。
  2. 整合历史系统:在不少的业务中,或多或少会存在一些历史项目,这些项目大多以采用老框架类似(Backbone.js,Angular.js 1)的B端管理系统为主,介于日常运营,这些系统需要结合到新框架中来使用还不能抛弃,对此我们也没有理由浪费时间和精力重写旧的逻辑。而微前端可以将这些系统进行整合,在基本不修改来逻辑的同时来同时兼容新老两套系统并行运行。

微前端核心价值

  • 技术栈无关 主框架不限制接入应用的技术栈,子应用可自主选择技术栈
  • 独立开发/部署 各个团队之间仓库独立,单独部署,互不依赖
  • 增量升级 当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
  • 独立运行时 微应用之间运行时互不依赖,有独立的状态管理
  • 提升效率 应用越庞大,越难以维护,协作效率越低下。微应用可以很好拆分,提升效率

微前端应用具备的能力

常见的微前端方案

iframe

优点

  1. 非常简单,无需任何改造
  2. 完美隔离,JS、CSS 都是独立的运行环境
  3. 不限制使用,页面上可以放多个 iframe 来组合业务

iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。

缺点

  1. url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  2. UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
  3. 全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
  4. 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

Web Components

很多小伙伴对 Web Components不是很了解,它是由google推出的浏览器的原生组件,具体内容查看 链接 了解

优点

  • 复用性:不需要对外抛出加载和卸载的全局 API,可复用能力更强
  • 标准化:W3C 的标准,未来能力会得到持续升级
  • 插拔性:可以非常便捷的进行移植和组件替换

缺点

  • 兼容性:对于 IE 浏览器不兼容,需要通过 Polyfill 的方式进行处理
  • 学习曲线:相对于传统的 Web 开发,需要掌握新的概念和技术

基于webpack module federation

模块联邦主要是一种去中心化的思想,也可以用来做服务拆分, 实现原理比较复杂,主要涉及到以下几个方面:

模块接口定义

在需要共享的模块中,通过 module.exports 或 export 将需要共享的模块封装成一个模块接口,并将其在模块系统中注册。

共享模块的描述信息

在需要共享模块的应用程序中,通过使用 ModuleFederationPlugin 插件,将需要共享的模块的描述信息以 JSON 格式写入配置中。描述信息包括需要共享的模块名称、模块接口、提供共享模块的应用程序的 URL 等。

共享模块的加载

在需要使用共享模块的应用程序中,通过 webpack 的 container 远程加载共享模块的代码,并将其封装成一个容器。容器在当前应用程序中的作用是在容器中运行共享模块的代码,并按照描述信息将导出的模块接口暴露出来。容器本身是一个 JavaScript 运行时环境,它可以在需要使用共享模块的应用程序中被动或主动加载。

远程模块的执行

在容器中加载共享模块的代码后,容器需要将其执行,并将执行过程中产生的模块接口导出。为了实现这个目的,容器会利用 webpack 打包时在编译过程中生成的一个特殊的运行时代码,即 remoteEntry.js,通过 script 标签远程加载到当前应用程序中。在这个特殊的运行时代码中,会封装一些与容器通信的方法,例如 remote 方法,可以用于按需加载模块、获取模块接口等。 综上,webpack-module-federation 基于这些原理,实现了多个独立的应用程序之间的模块共享和远程加载,从而可以实现高度解耦、可扩展的架构。

主流微前端框架

single-spa

single-spa只是实现了加载器、路由托管。沙箱隔离并没有实现。single-spa只做了两件事情:

  • 加载微应用(加载方法还得用户自己来实现)
  • 管理微应用的状态(初始化、挂载、卸载)

qiankun

qiankun 是一个基于 single-spa微前端 实现库,阿里系开源的微前端框架。 qiankun对single-spa方案进行完善,主要的完善点:

  • 子应用资源由 js 列表修改进为一个url,大大减轻注册子应用的复杂度
  • 实现应用隔离,完成js隔离方案 (window工厂) 和css隔离方案 (类vue的scoped)
  • 增加资源预加载能力,预先子应用html、js、css资源缓存下来,加快子应用的打开速度,通过 import-html-entry 包解析 HTML 获取资源路径,然后对资源进行解析、加载

Mirco-App

Micro App 是京东出的一款基于 Web Component 原生组件进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度、提升工作效率。

使用qiankun搭建微前端项目实战

采用 React 作为主应用基座,接入 Vue 技术栈的子应用和 React 技术栈的完整项目 tdesign-react-starter

主应用

使用 create-react-app 生成一个 React 的项目,初始化主应用,将它作为 qiankun 主应用基座。

  1. 创建微应用容器 - 用于承载微应用,渲染显示微应用;
  2. 注册微应用 - 设置微应用激活条件,微应用地址等等;
  3. 启动 qiankun;

安装依赖

bash 复制代码
npm i react-router-dom -S
npm i antd -S
npm i qiankun -S

注册微应用

tsx 复制代码
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

import {
  registerMicroApps,
  start,
  initGlobalState,
} from 'qiankun';

// 传值相关逻辑
const state = { id: 1 };
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
  console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();

registerMicroApps([
  {
    name: 'react1App',
    entry: '//localhost:3003',
    container: '#micro-box',
    activeRule: '/app-react1',
  },
  {
    name: 'vue2App',
    entry: '//localhost:5173',
    container: '#micro-box',
    activeRule: '/app-vue2',
  },
]);
start();

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

菜单逻辑代码

tsx 复制代码
// src/app.js
import { BrowserRouter as Router, Link } from 'react-router-dom';
import { Menu } from 'antd';

const Menus = [
  {
    key: 'app-vue1',
    label: <Link to='/app-react1'>react微应用1</Link>,
  },
  {
    key: 'app-vue2',
    label: <Link to='/app-vue2'>vue微应用2</Link>,
  },
];

function App() {
  return (
    <Router>
      <div style={{ display: 'flex' }}>
        <Menu
          style={{
            width: 200,
            height: '100vh',
          }}
          theme='dark'
          mode='inline'
          items={Menus}
        />
        <div
          id='micro-box'
          style={{ flex: 1 }}
        />
      </div>
    </Router>
  );
}

export default App;

实现效果

微应用react1App

可以按照 官网 安装项目。

安装依赖

bash 复制代码
npm i vite-plugin-qiankun -S

配置微应用

在主应用注册好了微应用后,我们还需要对微应用进行一系列的配置。首先,我们在入口文件 main.js 中,导出 qiankun 主应用所需要的三个生命周期钩子函数(这里需要注意的是React18版本的卸载函数和vite的qiankun插件):

tsx 复制代码
// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import store from 'modules/store';
import App from 'layouts/index';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';

import 'tdesign-react/es/style/index.css';

import './styles/index.less';

const env = import.meta.env.MODE || 'development';
const baseRouterName = env === 'site' ? '/starter/react/' : '';

let root: ReactDOM.Root | null = null;

const renderApp = (props) => {
  const container = props?.container;
  const app = container ? container?.querySelector('#app')! : document.getElementById('app')!;
  root = ReactDOM.createRoot(app);
  root.render(
    <Provider store={store}>
      <BrowserRouter basename={baseRouterName}>
        <App />
      </BrowserRouter>
    </Provider>,
  );
};
renderWithQiankun({
  mount(props) {
    renderApp(props);
  },
  bootstrap() {
    console.log('bootstrap');
  },
  unmount() {
    root.unmount();
  },
});

if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
  renderApp();
}

配置文件

加入vite-plugin-qiankun的配置,注释掉react方法。

javascript 复制代码
import path from 'path';
import { loadEnv } from 'vite';
import { viteMockServe } from 'vite-plugin-mock';
import react from '@vitejs/plugin-react';
import svgr from '@honkhonk/vite-plugin-svgr';
import qiankun from 'vite-plugin-qiankun';

const CWD = process.cwd();

export default (params) => {
  const { mode } = params;
  const { VITE_BASE_URL } = loadEnv(mode, CWD);

  return {

    /**
     * 省略其他配置文件
    **/

    base: VITE_BASE_URL,
    devServer: {
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
    },
    plugins: [
      svgr(),
      // react(), // 这里一定要注释,否则会报错
      qiankun('react1App', {
        useDevMode: true,
      }),
      mode === 'mock' &&
      viteMockServe({
        mockPath: './mock',
        localEnabled: true,
      }),
    ]
  }
}

实现效果

注意:里面的配置需要根据自己的需求改一下。

微应用vue2App

vue这个就简单用vite初始化搭建一下。

安装依赖

bash 复制代码
npm i vite-plugin-qiankun -S

注册微应用

和之前的react微应用一样,导出 qiankun 主应用所需要的三个生命周期钩子函数:

tsx 复制代码
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import {
  renderWithQiankun,
  qiankunWindow,
} from 'vite-plugin-qiankun/dist/helper';

let app;

const render = () => {
  app = createApp(App);
  app.mount('#box');
};

const initQianKun = () => {
  renderWithQiankun({
    mount(props) {
      props.onGlobalStateChange((state, prev) => {
        // state: 变更后的状态; prev 变更前的状态
        console.log(state, prev);
      });
      props.setGlobalState({ id: 2 });
      const { container } = props;
      render(container);
    },
    bootstrap() {},
    unmount() {
      app.unmount();
    },
  });
};

qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render();

配置文件

javascript 复制代码
import { fileURLToPath, URL } from 'node:url';

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
import qiankun from 'vite-plugin-qiankun'

export default defineConfig({
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  plugins: [
    vue(),
    Components({
      resolvers: [VantResolver()],
    }),
    qiankun('vue2App', {
      useDevMode: true
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
});

实现效果

参考链接

相关推荐
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空2 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_2 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus2 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空2 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范