Webpackモジュールフェデレーション
ここで見つけることができると思いますWebpack module federation
。これは、すでにある程度の理解があることを示しています。コミュニティには、使用方法と機能を説明するチュートリアルが多数あります。それについてよく知らない場合は、最初に公式ドキュメントを確認することをお勧めします。
Genesis2.0は、現在、SSRに関してVue2でサポートされている唯一のフレームワークWebpack module federation
です。なぜですか?これは、Genesis 1.0のときに、さまざまなサービスが他のサービスのページを呼び出すことができるリモートコンポーネントの概念が提案されたためです。Webpackによって提案されたコンセプトを見ると、module federation
すでにGenesis2.0のイテレーションについて考え始めています。
今日まで、ようやくすべての機能開発が完了し、社内のプロジェクトのアップグレードが始まり、リモートコンポーネントがmodule federation
新しい方法で呼び出されるように変更されました。このプロセスではmodule federation
、TSタイプをサポートしないという問題を正常に解決し、module federation
すべてのエントリファイルを強力にキャッシュできます。
MFプラグインの使用
MFはmodule federation
頭字語の頭字語であり、サービスはhost
サイドまたはサイドのいずれかになりますremote
。
MF
プラグインの使用には特に注意を払う必要があり、端末を使用すると、端末によって抽出された外部CSSファイルの情報host
を知ることができないため、外部CSSファイルの抽出はサポートされなくなります。 remote
。この問題を解決するためにrenderer.renderHtml
、新しいパラメーターが関数に追加されますstyleTagExtractCSS
。値がvalueの場合true
、現在のページでレンダリングされたスタイルタグのコンテンツが別のCSSファイルに抽出されます。各ページでレンダリングされるスタイルタグの内容が異なるため、多くのCSSファイルが生成される可能性があります。有効にする前に検討してください。
MFのAPIドキュメントを表示するには、ここをクリックしてください
ホスト側
import { MF, SSR } from '@fmfe/genesis-core';
/**
* 创建一个 SSR 实例
*/
export const ssr = new SSR({
name: 'ssr-mf-home',
build: {
/**
* 使用了MF,这个值必须设置为false
*/
extractCSS: false
}
});
/**
* 创建MF实例
*/
export const mf = new MF(ssr, {
/**
* 共享依赖
*/
shared: {
/**
* 注意!!!
* Vue需要设置单例,否则页面会出现异常
*/
vue: {
singleton: true
},
'vue-router': {
singleton: true
},
'vue-meta': {
singleton: true
}
},
remotes: [
{
/**
* 服务名称
*/
name: 'ssr-mf-about',
/**
* 客户端的远程模块下载源,程序会自动拼接:http://localhost:3002/[服务名称]/node-exposes/[文件名]
*/
clientOrigin: 'http://localhost:3002',
/**
* 服务端的远程模块下载源,程序会自动拼接:http://localhost:3002/[服务名称]/node-exposes/[文件名]
*/
serverOrigin: 'http://localhost:3002'
}
]
});
复制代码
リモートエンド
import { MF, SSR } from '@fmfe/genesis-core';
/**
* 创建一个 SSR 实例
*/
export const ssr = new SSR({
name: 'ssr-mf-about',
build: {
/**
* 使用了MF,这个值必须设置为false
*/
extractCSS: false
}
});
/**
* 创建MF实例
*/
export const mf = new MF(ssr, {
/**
* 配置当前模块导出的文件,建议导出的命名和原模块的命名一致,这样就能和TS类型生成的文件名一致,获得更好的类型支持
*/
exposes: {
'./src/vue-use': './src/vue-use.ts',
'./src/common-header.vue': './src/common-header.vue'
},
/**
* 共享依赖
*/
shared: {
/**
* 注意!!!
* Vue需要设置单例,否则页面会出现异常
*/
vue: {
singleton: true
},
'vue-router': {
singleton: true
},
'vue-meta': {
singleton: true
}
},
/**
* 读取本地生成的类型文件,生成给其它的远程模块调用
*/
typesDir: path.resolve('./types')
});
复制代码
ノード側の実装module federation
原則
module federation
在纯粹的CSR
项目中比较容易实现,但是在SSR
项目中需要在服务端运行一个Node程序,目前Webpack
对此并没有一个好的解决方案,所以Genesis自己实现了Node端module federation
下载和执行过程
编译阶段
如果你的项目导出了远程模块,在编译阶段client
的文件夹中会多出一个node-exposes
文件夹,让我们来看看这几个文件的作用都是什么吧。
-
manifest.json
这是一个清单文件,告诉了当前模块导出的基本信息{ "c": "9fed5146", "s": "e608c015", "d": 1, "t": 1645870356360 } 复制代码
c
是指客户端module federation
入口文件的版本号s
是指服务端module federation
入口文件的版本号d
是用来判断当前服务是否生成了dts,1是生成t
是当前构建完成的时间戳,如果本地已经下载过远程模块,MF
发送请求的时候会把这个t
的参数带过去,通过比较两个不同的t
值来判断是否发布了新的版本
-
e608c015.zip
这是将构建出来的server
目录下的全部内容,打包成的一个zip
文件,放到client/node-exposes
的目录中,方便其它的服务请求下载运行 -
e608c015-dts.zip
如果你给MF
插件指定了类型文件生成的目录,插件便会生成一个zip
文件,这样其它服务端在开发阶段,程序会下载e608c015-dts.zip
这个文件,并且解压到node_modules
目录中,就能得到完整的TS类型支持
运行阶段
在程序运行的时候,MF
就会去下载远程模块的代码,你会在控制台看到类似这样的一个日志
远程模块下载完成后,会解压zip
文件,就能看到上图Hot update
的字样,程序会热重载最新的代码执行。整个SSR
渲染的程序,会放在一个Node VM
中运行,能够有效的解决服务热重载时的内存泄漏问题。当然了,如果像定时器这种,如果不清理,还是会发生内存泄漏的,因为在ssr.sandboxGlobal
注入了全局的定时器相关的函数。
轮询阶段
在服务运行的过程中,如果远程服务发布了新的版本,这个时候我们就需要热更新了,MF
提供了轮询的方法,所以在host端
代码看起来像是下面这样的
/**
* 拿到渲染器后,启动应用程序
*/
export const startApp = (renderer: Renderer) => {
/**
* 初始化远程模块
*/
mf.remote.init(renderer);
/**
* 轮询远程模块
*/
mf.remote.polling();
/**
* 使用默认渲染中间件进行渲染,你也可以调用更加底层的 renderer.renderJson 和 renderer.renderHtml 来实现渲染
*/
app.use(renderer.renderMiddleware);
/**
* 监听端口
*/
app.listen(3001, () => console.log(`http://localhost:3001`));
};
复制代码
为了保证模块的及时性,MF
默认的轮询间隔时间为1000ms
,这个轮询的时间太频繁了,所以我们也可以在remote
端适当的延长响应的时间,避免请求过于频繁
/**
* 重写 manifest.json 的响应逻辑,注意要在静态服务的请求之前添加处理函数
* 如果1分钟内有更新,则立即往下执行,响应请求
* 如果1分钟内没有更新,则再结束请求,避免对方太频繁轮询
*/
app.get(mf.manifestRoutePath, async (req, res, next) => {
// host端传过来的编译时间
const t = Number(req.query.t);
// 最大等待时间
const maxAwait = 1000 * 60;
// 尝试等待manifest.json新的文件
await mf.exposes.getManifest(t, maxAwait);
// 继续往下执行,读取真实的静态资源文件
next();
});
复制代码
如果你不喜欢使用轮询的方式,可以自己编写接口,等其它服务发布完成后,手动触发服务的热更新接口,并且返回状态判断是否更新成功
app.get('/hot-reload/:name', async (req, res, next) => {
const result = await mf.remote.fetch(req.params.name);
res.send({ ok: !result.includes(false) });
});
复制代码
安装依赖时,自动下载远程模块的类型文件
在项目目录创建postinstall.ts
,调用mf.remote.fetch
方法拉取远程模块和类型
import { mf } from './genesis';
/**
* 下载远程模块,如果远程模块有类型文件提供,则会一并下载
*/
mf.remote.fetch();
复制代码
为了在依赖安装完成后,能执行这个代码,我们还需要在package.json
配置执行的命令
{
"scripts": {
"postinstall": "genesis-try-ts-node --project=./tsconfig.node.json postinstall.ts"
}
}
复制代码
小知识
:
为什么是使用genesis-try-ts-node
命令而不是genesis-ts-node
命令,是因为安装生产依赖时,@fmfe/genesis-compiler
不会被安装,genesis-ts-node
也就不存在,为了避免报错,所以又包装了一个genesis-try-ts-node
命令来防止安装生产依赖时报错
最后
到这里,我们已经完整实现了MF
的全部功能,如果你觉得还有疑问的地方,欢迎在issues中讨论