VueSSRでのWebpackモジュールフェデレーションの実践

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中讨论

おすすめ

転載: juejin.im/post/7078846054862422046
おすすめ