-
Notifications
You must be signed in to change notification settings - Fork 246
Shared
shared
通常指向host
端和 remote
端相同的模块(例如公共依赖的库)。主要为了 防止host
端和 remote
端重复加载相同依赖产生问题。
打包阶段依赖于 rollup
的打包功能,当你在 rollup.config.js/vite.config.js
中配置如下代码时
plugins:[
federation ({
shared:['vue','vuex']
})
]
federation
插件会在 rollup options
钩子时读取 shared
相关配置
使用this.emitFile()
来为配置了shared
的库生成单独的chunk(例如:__federation_shared_vue.js)。因为shared
特性依赖于preserveSignature
属性的配置,该方法可以更改单个chunk
的配置而不会影响用户的配置
type EmittedChunk = {
type: 'chunk';
id: string;
name?: string;
fileName?: string;
implicitlyLoadedAfterOneOf?: string[];
importer?: string;
preserveSignature?: 'strict' | 'allow-extension' | 'exports-only' | false;
};
- __federation_fn_import.js
- __federation_lib_semver.js
- satisfy.js ??
Vite 2.9 之后不再修改
manualChunks
,但用户可以通过配置 splitVendorChunkPlugin 实现原来的效果
如果没有配置 manualChunks
,vite 默认会提供一个如下的 manualChunks
实现
function createMoveToVendorChunkFn (config: ResolvedConfig): GetManualChunk {
const cache = new Map<string, boolean>()
return (id, { getModuleInfo }) => {
if (
id.includes ('node_modules') &&
!isCSSRequest (id) &&
staticImportedByEntry (id, getModuleInfo, cache)
) {
return 'vendor'
}
}
}
简单理解就是如果 moduleId
包含了 node_modules
,就会将这部分全部打包到 vendor
中,也就是将所有 shared
相关的配置全部打包到一个文件中。
我们需要的是每一个 shared
配置都单独打一个包,所以如果检测到用户配置了 manualChunks
函数实现(不论是用户配置的还是 vite
配置的),都将会代理这个函数,简要实现如下
if (typeof outputOption.manualChunks === 'function') {
outputOption.manualChunks = new Proxy (outputOption.manualChunks, {
apply (target, thisArg, argArray) {
const moduleId = argArray [0]
// if id is in shareModuleIds , return id ,else return vite function value
let find = ''
for (const sharedMapElement of sharedMap) {
//shared key,such as vue,vuex...
const key = sharedMapElement [0]
//shared values,such as version and so on
const value = sharedMapElement [1]
if (value.get ('dependencies')?.has (id)) {
find = key
break
}
}
return find ? find : target (argArray [0], argArray [1])
}
})
}
也就是如果 moduleId
如果属于 shared
依赖,将会把这部分依赖放到对应的 shared module
中,不属于就继续走用户配置的配置函数,这样就能将 shared
从 vite
的 vendor
文件中抽离出来打成单独的 chunk
我们需要将原来直接静态import shared模块替换为使用自定义的动态import函数。以配置了shared:['vue']
为例,在expose组件A中使用vue
的源码:
import { ref } from 'vue'
会被federation插件修改为
import { importShared } from './__federation_fn_import.js';
const { ref } = await importShared('vue');
importShared
是shared导入的关键方法,主要逻辑为
function importShared(name){
// 1. 如果有缓存,从缓存中取
return moduleCache[name]
// 2. 从runtime运行时(host)取
return getSharedFromRuntime()
// 3. 从本地Local取(remote)
return getSharedFromLocal()
}
-
getSharedFromRuntime()
优先在全局
globalThis
上查询__federation_shared__
,如果查询到并且版本符合配置要求则会优先使用。一个示例的全局shared
配置对象如下:{ "vue":{ "3.2.45":{ get() =>{ __federation_import('./__federation_shared_vue.js') }, "loaded":1 } } }
globalThis
上的shared
来自于哪里呢?在注册远程组件时,
host
端会将自身能提供的shared
信息提供给remote
的init
方法(可以在remoteEntry.js
文件中找到),remote
将会把这些信息写入全局globalThis
。因此这里的./__federation_shared_vue.js
文件对应的是host端的文件路径。 -
getSharedFromLocal()
需要注意这里从本地(remote端)加载shared。因此需要加载remote端的
./__federation_shared_vue.js
。
- 方案1:在bundle阶段替换
当前是在generateBundle代码生成阶段替换,代码逻辑位于transformImportFn(),当chunk中有import _federation_shared_xxx.js
时需要替换。
需要将
import { ref } from './_federation_shared_xxx.js'
替换为
const { ref } = await importShared('xxx');
但是这里存在一个问题,以react为例,生成的_federation_shared_react.js是一个facade chunk,内容为:
import './index-160ec1ee.js'
react 库的实际内容在index-160ec1ee.js,而其他的chunk都是依赖index-160ec1ee.js,并没有依赖_federation_shared_react.js。导致替换失败,没有生成期望的importShared('react')
-
方案2:在transform阶段替换
-
方案3:manualChunks
无法关闭tree shaking,导致生成的shared可能会缺少导出,运行时报错。