@originjs/vite-plugin-federation
简介
模块联邦 是一种前端的类微服务架构,它允许不同应用程序在运行时共享模块。
在传统的模块化开发中,应用程序通常被拆分为多个模块,每个模块有自己的代码和功能。然而,这些模块通常是在构建时静态地组合在一起的。模块联邦通过在运行时动态加载和共享模块,提供了更灵活的模块化解决方案。
联邦模块的关键概念包括:
独立开发和部署: 不同的团队可以独立地开发和部署其自己的模块。每个模块可以拥有自己的代码库、开发流程和版本管理。
动态加载: 在运行时,应用程序可以动态地加载和卸载模块。这使得应用程序能够根据需要动态地获取和使用特定的模块,而不需要在构建时静态地将它们组合在一起。
共享模块: 不同的应用程序可以共享彼此的模块。这意味着一个应用程序的模块可以被另一个应用程序动态地使用,实现了更高程度的代码复用。
- Remote: 远程服务端上的模块,本地模块运行时进行导入使用,不参与当前构建流程。
- Host: 项目中的普通模块,参与当前项目的构建流程。
- Shared: 共享模块是指既可重写的又可作为向嵌套容器提供重写的模块。它们通常指向每个构建中的相同模块,例如相同的库。
所以使用 Module Federation 通常需要2个以上的工程,一个作为Remote端,一个作为Host端。
- Remote 端一般用于暴露组件,这些组件共同组成一个组件库。
- Host 端一般用于引入并使用远程模块提供的组件。
对于 Remote端和 Host 端共同依赖的模块(如 vue, jsview):
- 如果希望使用集成在 Host端的模块,可以通过Shared方式,这样 Remote 端就不会进行该模块的的打包。
- 如果希望使用集成在 Remote 端的模块,可以在 Remote 端暴露出来,然后 Host 端引入 Remote 暴露的模块。
提示
注意: 一般来说,共同依赖的模块只能存在一份,如果同时引入了 Remote 端和 Host 端的共同模块,会出现一些未知错误。
集成到 JsView
注意
由于JsView需要对 @originjs/vite-plugin-federation 做一些源码级调整,所以需要安装指定版本 v1.3.4,其他版本暂不支持
参考工程源码: jsview-module-federation-demo (opens new window)
JsView 可以选择 shared 或 expose 其中一种方式,需要 Remote 端和 Host 端配合使用,即要么两者都是 shared 方式,要么两者都是 expose 方式。
可以选择 JsView 提供的命令行自动配置或自行手动配置
1. 自动配置
(1) 参考 创建 HelloWorld 工程 新建一个可运行的基础工程,例如 jsview-federation-remote。
如何你已经有一个基础工程,忽略此步骤。
(2) 配置
npm run tool -- --module-federation --mode=remote --jsview=shared
1
npm run tool -- --module-federation --mode=remote --jsview=expose
1
(3) 编译并运行 Remote Server
由于 Remote 端不支持开发模式,所以必须先进行编译。
提示
第一次编译前需要先进行App配置和生成签名,可以使用命令生成一个临时版本:
npm run tool -- --config-app && npm run tool -- --gen-keypair
1
npm run build
npx serve -C -p 8088 ./dist
1
2
3
(1) 参考 创建 HelloWorld 工程 新建一个可运行的基础工程,例如 jsview-federation-host。
如何你已经有一个基础工程,忽略此步骤。
(2) 配置和引入Remote组件
npm run tool -- --module-federation --mode=host --jsview=shared -remote-domain=http://localhost:8088
1
http://localhost:8088 需要替换成 Remote 端的 Http Server 地址
引入自定义 Remote 组件, 例如:
src/main.tsx
import App from './App.vue'
改为
import App from 'RemoteEntry/App.vue'
1
2
3
npm run tool -- --module-federation --mode=host --jsview=expose --remote-domain=http://localhost:8088
1
http://localhost:8088 需要替换成 Remote 端的 Http Server 地址
import { jsvCreateFocusManager } from 'jsview'
改为
import { jsvCreateFocusManager } from 'RemoteEntry/JsViewVue'
1
2
3
引入自定义 Remote 组件, 例如:
src/main.tsx
import App from './App.vue'
改为
import App from 'RemoteEntry/App.vue'
1
2
3
(3) 运行 Host 开发服务
2. 手动配置
(1) 参考 创建 HelloWorld 工程 新建一个可运行的基础工程,例如 jsview-federation-remote。
如何你已经有一个基础工程,忽略此步骤。
(2) 安装 @originjs/vite-plugin-federation
npm install @originjs/vite-plugin-federation@1.3.4
npm ci
1
2
3
执行后会有一个打补丁成功的log如下图。
(3) 修改 vite.config.ts
import { setFederationRemoteConfig } from './federation.remote.config'
import federation from '@originjs/vite-plugin-federation'
const viteConfig = ({
...
})
setFederationRemoteConfig(viteConfig, federation)
export default defineConfig(viteConfig)
1
2
3
4
5
6
7
8
9
10
(4) 新建 federation.remote.config.ts
import federation from '@originjs/vite-plugin-federation'
function getRemoteConfig() {
const config = {
name: 'remote-lib',
filename: 'remoteEntry.js',
exposes: {
'./App.vue': './src/App.vue',
},
shared: {
'@shijiu/jsview-vue': {},
jsview: { packagePath: '@shijiu/jsview-vue' },
vue: {},
'vue-router': {},
},
}
return config
}
function setFederationRemoteConfig(config, federation) {
config.build = (config.build || {})
config.build.rollupOptions = (config.build.rollupOptions || {})
config.build.rollupOptions.output = (config.build.rollupOptions.output || {})
config.build.rollupOptions.output.manualChunks = (config.build.rollupOptions.output.manualChunks || {})
config.build.rollupOptions.output.manualChunks["__federation_fn_import"] = ["__federation_fn_import"]
const federationConfig = getRemoteConfig()
config.plugins.push(federation(federationConfig))
}
export {
setFederationRemoteConfig,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import federation from '@originjs/vite-plugin-federation'
function getRemoteConfig() {
const config = {
name: 'remote-lib',
filename: 'remoteEntry.js',
exposes: {
'./JsViewLoader': 'node_modules/@shijiu/jsview/loader/jsview-loader.js',
'./JsViewVue': '/node_modules/@shijiu/jsview-vue/index.js',
'./App.vue': './src/App.vue',
},
shared: {
vue: {},
'vue-router': {},
},
}
return config
}
function getChunkFileName(chunkInfo) {
if(chunkInfo.name == '__federation_expose_JsViewLoader') {
return 'js/JsViewLoader.js'
}
return (process.env['JSVIEW_KEEP_CHUNKNAME'] ? 'js/[name].[hash].js' : 'js/chunk.jsv.[hash].js')
}
function setFederationRemoteConfig(config, federation) {
config.build = (config.build || {})
config.build.rollupOptions = (config.build.rollupOptions || {})
config.build.rollupOptions.output = (config.build.rollupOptions.output || {})
config.build.rollupOptions.output.manualChunks = (config.build.rollupOptions.output.manualChunks || {})
config.build.rollupOptions.output.manualChunks["__federation_fn_import"] = ["__federation_fn_import"]
config.build.rollupOptions.output.chunkFileNames = getChunkFileName
const federationConfig = getRemoteConfig()
config.plugins.push(federation(federationConfig))
}
export {
setFederationRemoteConfig,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(5) 编译并运行 Remote Server
由于 Remote 端不支持开发模式,所以必须先进行编译。
提示
第一次编译前需要先进行App配置和生成签名,可以使用命令生成一个临时版本:
npm run tool -- --config-app && npm run tool -- --gen-keypair
1
npm run build
npx serve -C -p 8088 ./dist
1
2
3
(1) 参考 创建 HelloWorld 工程 新建一个可运行的基础工程,例如 jsview-federation-host。
如何你已经有一个基础工程,忽略此步骤。
(2) 安装 @originjs/vite-plugin-federation
npm install @originjs/vite-plugin-federation@1.3.4
npm ci
1
2
3
执行后会有一个打补丁成功的log如下图。
(3) 修改 vite.config.ts
import { setFederationHostConfig } from './federation.host.config'
import federation from '@originjs/vite-plugin-federation'
const viteConfig = ({
...
})
setFederationHostConfig(viteConfig, federation)
export default defineConfig(viteConfig)
1
2
3
4
5
6
7
8
9
10
(4) 新建 federation.host.config.ts
const remoteDomain = 'http://localhost:8088'
const remoteEntryUrl = remoteDomain + '/js/remoteEntry.js'
function getHostConfig() {
const config = {
name: 'host-app',
remotes: {
'RemoteEntry': remoteEntryUrl,
},
shared: {
'@shijiu/jsview-vue': {},
jsview: { packagePath: '@shijiu/jsview-vue' },
vue: {},
'vue-router': {},
},
}
return config
}
function setFederationHostConfig(config, federation) {
config.build = (config.build || {})
config.build.rollupOptions = (config.build.rollupOptions || {})
config.build.rollupOptions.output = (config.build.rollupOptions.output || {})
config.build.rollupOptions.output.manualChunks = (config.build.rollupOptions.output.manualChunks || {})
config.build.rollupOptions.output.manualChunks["__federation_fn_import"] = ["__federation_fn_import"]
const federationConfig = getHostConfig()
config.plugins.push(federation(federationConfig))
}
export {
setFederationHostConfig,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const remoteDomain = 'http://localhost:8088'
const remoteEntryUrl = remoteDomain + '/js/remoteEntry.js'
const jsviewLoaderUrl = remoteDomain + '/js/JsViewLoader.js'
function getHostConfig() {
const config = {
name: 'host-app',
remotes: {
'RemoteEntry': remoteEntryUrl,
},
shared: {
vue: {},
'vue-router': {},
},
}
return config
}
function setFederationHostConfig(config, federation) {
config.build = (config.build || {})
config.build.rollupOptions = (config.build.rollupOptions || {})
config.build.rollupOptions.output = (config.build.rollupOptions.output || {})
config.build.rollupOptions.output.manualChunks = (config.build.rollupOptions.output.manualChunks || {})
config.build.rollupOptions.output.manualChunks["__federation_fn_import"] = ["__federation_fn_import"]
const federationConfig = getHostConfig()
config.plugins.push(federation(federationConfig))
}
export {
setFederationHostConfig,
jsviewLoaderUrl,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(5) src/appConfig/jsview.config.mjs 使用 Remote 端 JsViewLoader。
忽略此步, shared方式不需要从 Remote 端加载 JsView loader。
import { jsviewLoaderUrl } from '../../federation.host.config'
export default {
jsviewConfig: {
remoteLoader: jsviewLoaderUrl,
...
}
}
1
2
3
4
5
6
7
8
(6) src/main.tsx 引入 Remote 端组件 (所有从 jsview 导入的组件均改成'RemoteEntry/JsViewVue')
忽略此步, shared方式不需要从 Remote 端加载 JsView 组件。
1
import { jsvCreateFocusManager } from 'jsview'
改为
import { jsvCreateFocusManager } from 'RemoteEntry/JsViewVue'
1
2
3
4
5
6
(7) 引入自定义 Remote 组件, 例如:
import App from './App.vue'
改为
import App from 'RemoteEntry/App.vue'
1
2
3
(8) 删除本地组件(可选,防止相同组件出现混淆)
(9) 运行 Host 开发服务
查看运行结果
采用 expose 方式或 shared 方式的 JsView 区别在于 JsView Loader 和 JsView 组件是从 Remote 加载还是从 Host 加载,通过 DevTools 可以看出结果。