如何利用 `Vite` 的 `module federation` 插件,实现 Vue 微前端架构下的代码共享和版本管理?

各位听众,大家好!我是你们今天的微前端架构师——代码魔术师!今天咱们来聊聊用 Vite 的 Module Federation 插件,玩转 Vue 微前端,实现代码共享和版本管理。保证让你听完之后,感觉自己也成了微前端架构大师!

开场白:微前端,不再是镜中花、水中月

微前端这玩意儿,听起来高大上,但其实没那么神秘。你可以把它想象成把一个巨大的披萨,切成几块,每一块都由不同的团队负责制作。最后把这些小披萨拼起来,就成了一个完整的、美味的大披萨了。

而 Module Federation,就是这切披萨的刀!它可以让不同的微前端应用之间共享代码,就像是你可以从邻居那儿借点面粉,省得自己再去超市买一袋。

第一部分:Vite + Module Federation:天生一对

Vite 以其闪电般的速度和简洁的配置,成为了现代前端开发的宠儿。而 Module Federation,又让 Vite 如虎添翼。

  • Vite 的优势:

    • 启动速度快: 基于 ES Modules,无需打包,即时编译。
    • 热更新快: 修改代码,页面瞬间刷新,告别漫长的等待。
    • 配置简单: 告别 Webpack 复杂的配置,轻松上手。
  • Module Federation 的优势:

    • 代码共享: 不同的微前端应用可以共享组件、工具函数等代码,避免重复造轮子。
    • 独立部署: 每个微前端应用可以独立部署,互不影响。
    • 版本隔离: 每个微前端应用可以使用不同的技术栈和版本,灵活应对变化。

第二部分:Module Federation 的基本概念

在深入代码之前,先来了解一下 Module Federation 的几个核心概念:

  • Host: 主应用,负责加载和渲染远程模块。就像是披萨店的店面,负责把各个小披萨组合起来。
  • Remote: 远程应用,对外暴露模块,供其他应用使用。就像是制作小披萨的各个厨房。
  • Shared Modules: 共享模块,可以在 Host 和 Remote 之间共享的依赖。就像是制作披萨的面粉、奶酪等原材料。

第三部分:实战演练:搭建一个简单的微前端架构

接下来,咱们通过一个简单的例子,来演示如何使用 Vite + Module Federation 搭建一个微前端架构。

场景: 我们有两个 Vue 应用:

  • App1 (Host): 主应用,负责显示一个欢迎信息和一个按钮。
  • App2 (Remote): 远程应用,提供一个计数器组件。

步骤 1:创建两个 Vue 项目

npm create vite app1 --template vue
npm create vite app2 --template vue

步骤 2:配置 App1 (Host)

  1. 安装 @originjs/vite-plugin-federation:

    cd app1
    npm install @originjs/vite-plugin-federation --save-dev
  2. 修改 vite.config.js:

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import federation from '@originjs/vite-plugin-federation'
    
    export default defineConfig({
      plugins: [
        vue(),
        federation({
          name: 'app1',
          remotes: {
            app2: 'http://localhost:5001/assets/remoteEntry.js', // App2 的地址
          },
          shared: ['vue'] // 共享的依赖
        })
      ],
      server: {
        port: 5000,
      }
    })
    • name: 应用的名称,必须唯一。
    • remotes: 远程应用的配置,指定远程应用的名称和地址。
    • shared: 共享的依赖,这里指定了 vue,表示 Host 和 Remote 共享 Vue 依赖,避免重复加载。
  3. 修改 src/App.vue:

    <template>
      <h1>Welcome to App1!</h1>
      <button @click="loadCounter">Load Counter</button>
      <div id="counter-container"></div>
    </template>
    
    <script setup>
    import { onMounted } from 'vue';
    
    const loadCounter = async () => {
      try {
        const Counter = await import('app2/Counter'); // 动态导入 App2 的 Counter 组件
        const counterContainer = document.getElementById('counter-container');
        const app = Counter.default({ el: counterContainer }); // 渲染 Counter 组件
      } catch (error) {
        console.error('Failed to load Counter:', error);
      }
    };
    
    onMounted(() => {
      // 可以选择在这里预加载,或者在点击按钮时加载
    });
    </script>
    • 使用 import('app2/Counter') 动态导入 App2 的 Counter 组件。
    • app2 是在 vite.config.js 中配置的远程应用的名称。
    • Counter 是 App2 中暴露的组件的名称。

步骤 3:配置 App2 (Remote)

  1. 安装 @originjs/vite-plugin-federation:

    cd app2
    npm install @originjs/vite-plugin-federation --save-dev
  2. 修改 vite.config.js:

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import federation from '@originjs/vite-plugin-federation'
    
    export default defineConfig({
      plugins: [
        vue(),
        federation({
          name: 'app2',
          filename: 'remoteEntry.js', // 暴露的文件名
          exposes: {
            './Counter': './src/components/Counter.vue', // 暴露的组件
          },
          shared: ['vue'] // 共享的依赖
        })
      ],
      server: {
        port: 5001,
      }
    })
    • filename: 暴露的文件名,默认为 remoteEntry.js
    • exposes: 暴露的模块,指定哪些模块可以被其他应用使用。
    • './Counter': './src/components/Counter.vue' 表示将 src/components/Counter.vue 组件暴露为 Counter
  3. 创建 src/components/Counter.vue:

    <template>
      <div>
        <h2>Counter from App2</h2>
        <p>Count: {{ count }}</p>
        <button @click="increment">Increment</button>
      </div>
    </template>
    
    <script setup>
    import { ref } from 'vue';
    
    const count = ref(0);
    
    const increment = () => {
      count.value++;
    };
    </script>

步骤 4:运行两个应用

# 在 app1 目录下
npm run dev

# 在 app2 目录下
npm run dev

现在,打开 http://localhost:5000,你应该能看到 App1 的欢迎信息和一个按钮。点击按钮,App2 的计数器组件就会被加载并渲染到页面上。

第四部分:代码共享:不仅仅是组件

Module Federation 不仅仅可以共享组件,还可以共享:

  • 工具函数: 比如日期格式化、字符串处理等。
  • Vuex Store: 共享状态管理,实现微前端应用之间的数据同步。
  • UI 组件库: 统一 UI 风格,提升用户体验。

示例:共享工具函数

  1. 在 App2 中创建一个工具函数 src/utils/formatDate.js:

    export const formatDate = (date) => {
      const options = { year: 'numeric', month: 'long', day: 'numeric' };
      return new Date(date).toLocaleDateString(undefined, options);
    };
  2. 在 App2 的 vite.config.js 中暴露该函数:

    exposes: {
      './Counter': './src/components/Counter.vue',
      './formatDate': './src/utils/formatDate.js', // 暴露工具函数
    },
  3. 在 App1 中使用该函数:

    <template>
      <h1>Welcome to App1!</h1>
      <p>Today is: {{ formattedDate }}</p>
      <button @click="loadCounter">Load Counter</button>
      <div id="counter-container"></div>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue';
    
    const formattedDate = ref('');
    
    const loadCounter = async () => {
      // ... (之前的代码)
    };
    
    onMounted(async () => {
      try {
        const { formatDate } = await import('app2/formatDate'); // 导入工具函数
        formattedDate.value = formatDate(new Date());
      } catch (error) {
        console.error('Failed to load formatDate:', error);
      }
    });
    </script>

第五部分:版本管理:让你的微前端架构更健壮

Module Federation 提供了多种版本管理策略,确保你的微前端架构能够应对各种变化。

  • 精确版本: 指定依赖的具体版本,确保 Host 和 Remote 使用相同的版本。
  • 版本范围: 指定依赖的版本范围,允许 Host 和 Remote 使用兼容的版本。
  • 单例模式: 确保依赖只被加载一次,避免重复加载和冲突。

示例:使用版本范围

vite.config.js 中,可以使用 ~^ 来指定版本范围:

shared: {
  vue: {
    requiredVersion: '^3.0.0', // 允许使用 3.x.x 版本的 Vue
  },
},
  • ^: 兼容升级,允许升级到不改变最左边非零数字的版本。例如,^3.2.0 允许升级到 3.9.0,但不允许升级到 4.0.0
  • ~: 小版本升级,允许升级到同一个主版本号下的最新版本。例如,~3.2.0 允许升级到 3.2.x,但不允许升级到 3.3.0

表格总结:Module Federation 的配置选项

配置选项 类型 描述
name string 应用的名称,必须唯一。
filename string 暴露的文件名,默认为 remoteEntry.js
remotes object 远程应用的配置,指定远程应用的名称和地址。
exposes object 暴露的模块,指定哪些模块可以被其他应用使用。
shared object 共享的依赖,可以在 Host 和 Remote 之间共享的依赖。可以配置版本范围、单例模式等。
library object 配置 library 的 name 和 type。默认为 umd 格式。某些场景下可能需要修改,例如在 Next.js 项目中需要设置为 var

第六部分:最佳实践和注意事项

  • 合理划分微前端应用: 根据业务领域或团队职责,合理划分微前端应用,避免过度拆分或职责不清。
  • 统一技术栈: 尽量保持微前端应用的技术栈一致,降低维护成本和学习曲线。
  • 使用 CI/CD 工具: 自动化部署和发布流程,提高效率和可靠性。
  • 监控和日志: 监控微前端应用的性能和错误,及时发现和解决问题。
  • 考虑安全性: 注意跨域安全问题,避免恶意代码注入。

第七部分:Module Federation 的高级用法

  • 动态 Remotes: 根据不同的环境或配置,动态加载不同的 Remote 应用。
  • 自定义加载器: 自定义 Remote 模块的加载方式,例如从 CDN 加载。
  • 插件扩展: 通过插件扩展 Module Federation 的功能,例如支持 TypeScript、CSS Modules 等。

总结:Module Federation,让你的微前端架构更上一层楼

Module Federation 是一个强大的工具,可以帮助你构建灵活、可维护的微前端架构。通过代码共享、版本管理和独立部署,你的微前端应用将更加健壮、高效。

记住,微前端不是银弹,不要为了微前端而微前端。只有在合适的场景下,才能发挥其最大的价值。

希望今天的讲座对你有所帮助。祝你在微前端的世界里,玩得开心!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注