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+esmvue-router/pinia | vite+esmvue-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 集成
现在可以不受 Vite 和 Webpack 的限制而使用Module Federation了! 也就是说,你可以选择在 Webpack 中使用 vite-plugin-federation 暴露的组件,也可以选择在 Vite 中使用 Webpack ModuleFederationPlugin 暴露的组件。但需要注意对于不同的打包框架,你需要指定 remotes.from 和 remotes.format属性以便使它们更好地工作。具体可以参考如下几个示例工程:
⚠️注意:
- 
Vite使用Webpack组件相对容易,但是Webpack使用vite-plugin-federation组件时最好使用esm格式,因为其他格式暂时缺少完整的测试用例。
- 
React项目中不建议Vite和Webpack混合使用,因为现在无法保证Vite/Rollup和Webpack打包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>
- 远程模块地址,例如:https://localhost:5011/remoteEntry.js
- 你可以简单地进行如下配置
            
            
              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,共享域名称,保持remote和host端一致即可
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 | 
| dev和build&serve模式 | *.dev&serve.spec.ts | 
Dev模式下的测试
由于当前插件只有host端支持vite的dev模式,所以dev模式测试时,会依次在测试项目的根路径执行以下代码
- pnpm run dev:host
- pnpm run build:remotes
- pnpm run serve:remotes
- 执行测试用例
- 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模式将会依次执行以下指令
- pnpm run build
- pnpm run serve
- 执行测试用例
- 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?
请检查是否使用vite的dev模式启动了项目,当前仅有完全纯净的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",
}