Skip to content

Latest commit

 

History

History
488 lines (366 loc) · 18.3 KB

README-zh.md

File metadata and controls

488 lines (366 loc) · 18.3 KB

English | 简体中文

vite-plugin-federation

Build Status Version Node Compatibility License

支持模块联邦Module Federation的 Vite/Rollup 插件,可以与 Webpack Module Federation 兼容。

运行效果

Preview

安装

npm install @originjs/vite-plugin-federation --save-dev

或者

yarn add @originjs/vite-plugin-federation --dev

使用

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

步骤一:Remote端配置暴露的模块

  • 使用Vite构建的项目,修改vite.config.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
// 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
// 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
// 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项目为例

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

在模板中使用远程组件

<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
// dynamic import
const myButton = defineAsyncComponent(() => import('remote/myButton'));
app.component('my-button' , myButton);
// or
export default {
  name: 'App',
  components: {
    myButton: () => import('remote/myButton'),
  }
}
// static import
import myButton from 'remote/myButton';
app.component('my-button' , myButton);
// or
export default {
  name: 'App',
  components: {
    myButton: myButton
  }
}
  • React
// 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

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

remotes

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

external:string|Promise<string>

remotes: {
    // '远端模块名称':'远端模块入口文件地址'
    'remote-simple': 'http://localhost:5011/remoteEntry.js',
}
  • 或者做一个稍微复杂的配置,如果你需要使用其他字段的话
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,否则可能打包失败,这里提供一个简单是示例

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下的软件包)。 比如你只能定义类似的共享
shared :{
    packageName:{
        ...
    }
}
  • packageName必须是node_modules下的一个包,如vue、react等,但你不能定义自己的包。 但是现在你可以通过指定包的路径来共享一个自定义的包,比如说
shared: {
    packageName: {
        packagePath:'./src/a/index.js'
    }
}

generate : boolean

  • default: true
  • 是否生成shared chunk文件 ,如果你确定host端有一个可以使用的shared,那么你可以在remote端设置不生成共享文件,以减少remote端的块文件的大小,该配置只在remote有效,host端无论如何都会生成自己的shared。
shared: {
  packageName: {
    generate: false
  }
}

添加其他的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文件中至少包含四条指令

  "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文件中至少包含三条指令

  "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也可以设置为

 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

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

Star History

Star History Chart

Wiki

详细设计说明