vite 模块联邦 vite-plugin-federation

vite-plugin-federation

原文

安装

sql 复制代码
npm install @originjs/vite-plugin-federation --save-dev

或者

sql 复制代码
yarn add @originjs/vite-plugin-federation --dev

使用

使用Module Federation通常需要2个以上的工程,一个作为Host端,一个作为Remote端。

步骤一:Remote端配置暴露的模块
  • 使用Vite构建的项目,修改vite.config.js
js 复制代码
// vite.config.js
import federation from "@originjs/vite-plugin-federation";
export default {
    plugins: [
        federation({
            name: 'remote-app',
            filename: 'remoteEntry.js',
            // 需要暴露的模块
            exposes: {
                './Button': './src/Button.vue',
            },
            shared: ['vue']
        })
    ]
}
  • 使用Rollup构建的项目,修改rollup.config.js
js 复制代码
// rollup.config.js
import federation from '@originjs/vite-plugin-federation'
export default {
    input: 'src/index.js',
    plugins: [
        federation({
            name: 'remote-app',
            filename: 'remoteEntry.js',
            // 需要暴露的模块
            exposes: {
                './Button': './src/button'
            },
            shared: ['vue']
        })
    ]
}
步骤二:Host端配置远程模块入口
  • 使用Vite构建的项目,修改vite.config.js
js 复制代码
// vite.config.js
import federation from "@originjs/vite-plugin-federation";
export default {
    plugins: [
        federation({
            name: 'host-app',
            remotes: {
                remote_app: "http://localhost:5001/assets/remoteEntry.js",
            },
            shared: ['vue']
        })
    ]
}
  • 使用Rollup构建的项目,修改rollup.config.js
js 复制代码
// rollup.config.js
import federation from '@originjs/vite-plugin-federation'
export default {
    input: 'src/index.js',
    plugins: [
        federation({
            name: 'host-app',
            remotes: {
                remote_app: "http://localhost:5001/remoteEntry.js",
            },
            shared: ['vue']
        })
    ]
}
步骤三:Host端使用远程模块

以Vue项目为例

js 复制代码
import { createApp, defineAsyncComponent } from "vue";
const app = createApp(Layout);
...
const RemoteButton = defineAsyncComponent(() => import("remote_app/Button"));
app.component("RemoteButton", RemoteButton);
app.mount("#root");

在模板中使用远程组件

vue 复制代码
<template>
    <div>
        <RemoteButton />
    </div>
</template>

示例工程

Examples Host Remote
basic-host-remote rollup+esm rollup+esm
react-in-vue vite+esm vite+esm
simple-react-esm rollup+esm rollup+esm
simple-react-systemjs rollup+systemjs rollup+systemjs
simple-react-webpack rollup+systemjs webpack+systemjs
vue2-demo vite+esm vite+esm
vue3-advanced-demo vite+esm vue-router/pinia vite+esm vue-router/pinia
vue3-demo-esm vite+esm vite+esm
vue3-demo-systemjs vite+systemjs vite+systemjs
vue3-demo-webpack-esm-esm vite/webpack+esm vite/webpack+esm
vue3-demo-webpack-esm-var vite+esm webpack+var
vue3-demo-webpack-systemjs vite+systemjs webpack+systemjs

特性

与 Webpack 集成

现在可以不受 ViteWebpack 的限制而使用Module Federation了! 也就是说,你可以选择在 Webpack 中使用 vite-plugin-federation 暴露的组件,也可以选择在 Vite 中使用 Webpack ModuleFederationPlugin 暴露的组件。但需要注意对于不同的打包框架,你需要指定 remotes.fromremotes.format属性以便使它们更好地工作。具体可以参考如下几个示例工程:

⚠️注意:

  1. Vite 使用 Webpack 组件相对容易,但是 Webpack 使用 vite-plugin-federation 组件时最好使用 esm 格式,因为其他格式暂时缺少完整的测试用例。

  2. React 项目中不建议 ViteWebpack 混合使用,因为现在无法保证 Vite/RollupWebpack 打包 commonjs 时生成一致的 chunk,这可能会导致使用shared 出现问题。

Vite Dev开发模式支持

因为 Vite 在 dev开发模式下基于 esbuild构建,所以我们单独提供了对 dev 模式的支持,可以在远程模块部署的情况下,利用 Vite 的高性能开发服务器。

⚠️注意:

  • 只有Host端支持dev模式,Remote端需要使用vite build生成RemoteEntry.js包。这是由于Vite Dev模式是Bundleless 不打包的,您可以使用vite build --watch到达类似热更新的效果。

静态导入

支持组件的静态导入和动态导入,下面展示两种方式的区别,你可以在examples中的项目中看到动态导入和静态导入的例子,下面是一个简单的示例:

  • Vue
javascript 复制代码
// dynamic import
const myButton = defineAsyncComponent(() => import('remote/myButton'));
app.component('my-button' , myButton);
// or
export default {
  name: 'App',
  components: {
    myButton: () => import('remote/myButton'),
  }
}
javascript 复制代码
// static import
import myButton from 'remote/myButton';
app.component('my-button' , myButton);
// or
export default {
  name: 'App',
  components: {
    myButton: myButton
  }
}
  • React
js 复制代码
// dynamic import
const myButton = React.lazy(() => import('remote/myButton'))

// static import
import myButton from 'remote/myButton'

⚠️注意:

  • 静态导入可能会依赖到浏览器Top-level await特性,因此需要将配置文件中的build.target设置为next或使用插件vite-plugin-top-level-await。您可以在此查看Top-level await的浏览器兼容性

配置项说明

name:string

  • 作为远程模块的模块名称,必填。

filename:string

  • 作为远程模块的入口文件,非必填,默认为remoteEntry.js

transformFileTypes:string[]

  • 插件所需要处理的文件类型,绝大多数情况下无需配置,因为默认设置了这些类型['.js','.ts','.jsx','.tsx','.mjs','.cjs','.vue','.svelte'],当你自定义了一些文件类型时并且需要vite-plugin-federation插件处理时,请把它添加到数组配置中。

exposes

  • 作为远程模块,对外暴露的组件列表,远程模块必填。
js 复制代码
exposes: {
    // '对外暴露的组件名称':'对外暴露的组件地址'
    './remote-simple-button': './src/components/Button.vue',
    './remote-simple-section': './src/components/Section.vue'
}

remotes

作为本地模块,引用的远端模块入口文件

external:string|Promise<string>
js 复制代码
remotes: {
    // '远端模块名称':'远端模块入口文件地址'
    'remote-simple': 'http://localhost:5011/remoteEntry.js',
}
  • 或者做一个稍微复杂的配置,如果你需要使用其他字段的话
javascript 复制代码
remotes: {
    remote-simple: {
        external: 'http://localhost:5011/remoteEntry.js',
        format: 'var',
        from: 'webpack'
    }
}
externalType:'url'|'promise'
  • default:'url'

  • 设置external的类型 如果你想使用动态的URL地址,你可以设置external为一个promise,但是请注意需要同时指定externalType为'promise',确保promise部分的代码正确并且返回string,否则可能打包失败,这里提供一个简单是示例

js 复制代码
remotes: {
      home: {
          external: `Promise.resolve('your url')`,
          externalType: 'promise'
      },
},

// or from networke
remotes: {
    remote-simple: {
        external: `fetch('your url').then(response=>response.json()).then(data=>data.url)`,
        externalType: 'promise'
    }
}
format : 'esm'|'systemjs'|'var'
  • default:'esm'
  • 指定远程组件的格式,当主机和远程端使用不同的打包格式时,这样做更有效,例如主机使用 vite + esm,远程使用 webpack + var,这时需要指定type:'var'
from : 'vite'|'webpack'
  • default : 'vite'
  • 指定远程组件的来源,来源于 vite-plugin-federation 选择 vite,来源于 webpack 选择 webpack

shared

本地模块和远程模块共享的依赖。本地模块需配置所有使用到的远端模块的依赖;远端模块需要配置对外提供的组件的依赖。

import: boolean
  • default: true
  • 默认为 true ,是否加入shared共享该模块,仅对 remote 端生效,remote 开启该配置后,会减少部分打包时间,因为不需要打包部分 shared,但是一旦 host 端没有可用的 shared 模块,会直接报错,因为没有可用的回退模块
shareScope: string
  • default: 'default'
  • 默认为 defualt,共享域名称,保持 remotehost 端一致即可
version: string
  • 仅对 host 端生效,提供的share模块的版本,默认为share包中的 package.json 文件的 version ,只有当以此法无法获取 version 时才需要手动配置
requiredVersion: string
  • 仅对 remote 端生效,规定所使用的 host shared 所需要的版本,当 host 端的版本不符合 requiredVersion 要求时,会使用自己的 shared 模块,前提是自己没有配置 import=false ,默认不启用该功能
packagePath: string
  • supportMode: only serve
  • 允许自定义软件包通过packagePath共享(以前只限于node_modules下的软件包)。 比如你只能定义类似的共享
js 复制代码
shared :{
    packageName:{
        ...
    }
}
  • packageName必须是node_modules下的一个包,如vue、react等,但你不能定义自己的包。 但是现在你可以通过指定包的路径来共享一个自定义的包,比如说
js 复制代码
shared: {
    packageName: {
        packagePath:'./src/a/index.js'
    }
}
generate : boolean
  • default: true
  • 是否生成shared chunk文件 ,如果你确定host端有一个可以使用的shared,那么你可以在remote端设置不生成共享文件,以减少remote端的块文件的大小,该配置只在remote有效,host端无论如何都会生成自己的shared。
js 复制代码
shared: {
  packageName: {
    generate: false
  }
}
modulePreload : boolean
  • 默认值: false
  • 如果为 true,则会将 shared 依赖的 bundle 文件以 link modulepreload 的方式插入到 html head,仅在生产模式下生效。
js 复制代码
shared: {
    packageName: {
        modulePreload: true
    }
}

添加其他的Example工程?

首先需要判断测试适用于dev模式还是build&serve模式,或者两者都需要。

另外当前的测试会直接访问localhost:5000来进行测试,这意味着host端的启动端口必须是5000,否则会直接导致测试失败。

如何设置dev模式或者build&serve模式的测试?

根据测试文件的文件名称区分

例如 vue3-demo-esm.dev&serve.spec.ts表示会在dev模式和build&serve模式下构建测试, 而vue3-demo-esm.dev.spec.ts则只会在dev模式下构建测试,总结如下

模式 文件名称
仅用于dev模式 *.dev.spec.ts
仅用于build&serve模式 *.serve.spec.ts
devbuild&serve模式 *.dev&serve.spec.ts

Dev模式下的测试

由于当前插件只有host端支持vitedev模式,所以dev模式测试时,会依次在测试项目的根路径执行以下代码

  1. pnpm run dev:host
  2. pnpm run build:remotes
  3. pnpm run serve:remotes
  4. 执行测试用例
  5. pnpm run stop

这也表示在dev模式下项目的package.json文件中至少包含四条指令

json 复制代码
  "scripts": {
    "build:remotes": "pnpm --filter \"./remote\"  build",
    "serve:remotes": "pnpm --filter \"./remote\"  serve",
    "dev:hosts": "pnpm --filter \"./host\" dev",
    "stop": "kill-port --port 5000,5001"
  },
  "workspaces": [
    "host",
    "remote"
  ]

Build&Serve模式下的测试

build&serve模式将会依次执行以下指令

  1. pnpm run build
  2. pnpm run serve
  3. 执行测试用例
  4. pnpm run stop

这也表示在build&serve模式下项目的package.json文件中至少包含三条指令

json 复制代码
  "scripts": {
    "build": "pnpm --parallel --filter \"./**\" build",
    "serve": "pnpm --parallel --filter \"./**\" serve ",
    "stop": "kill-port --port 5000,5001"
  },
  "workspaces": [
    "host",
    "remote"
  ]

FAQ

ERROR: Top-level await is not available in the configured target environment

这是因为依赖到了浏览器的top-level-await特性,当设置的浏览器环境不支持该特性时就会出现该报错,解决办法是将build.target设置为esnext,你可以在这里查看各个浏览器对该特性的支持情况。

build.target也可以设置为

js 复制代码
 build: {
    target: ["chrome89", "edge89", "firefox89", "safari15"]
 }

或者使用插件vite-plugin-top-level-await来消除top-level-await,在vue3-demo-esm中演示了这种用法

没有正常生成chunk?

请检查是否使用vitedev模式启动了项目,当前仅有完全纯净的host端才可以使用dev模式,remote端必须使用build模式才能使插件生效。

React 使用federation的一些问题

建议查看这个Issue,这里包含了大多数React相关的问题

远程模块加载本地模块的共享依赖失败,例如localhost/:1 Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: http:your url

原因:Vite 在启动服务时对于 IP、Port 有自动获取逻辑,在 Plugin 中还没有找到完全对应的获取逻辑,在部分情况下可能会出现获取失败。

解决:

在本地模块显式到声明 IP、Port、cacheDir,保证我们的 Plugin 可以正确的获取和传递依赖的地址。

本地模块的 vite.config.ts

ts 复制代码
export default defineConfig({
  server:{
    https: "http",
    host: "192.168.56.1",
    port: 5100,
  },
  cacheDir: "node_modules/.cacheDir",
}

Wiki

详细设计说明

相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax