Die Praxis der Webpack-Modulföderation in Vue SSR

Föderation des Webpack-Moduls

Ich glaube, Sie können es hier finden, was darauf hinweist, dass Sie Webpack module federationbereits ein gewisses Verständnis haben. Es gibt viele Tutorials in der Community, die Ihnen erklären, wie Sie es verwenden und was es tut. Wenn Sie nicht viel darüber wissen, können Sie sich auch zuerst die offizielle Dokumentation ansehen.

Genesis2.0 ist derzeit das einzige Framework, das von Vue2 in Bezug auf SSR unterstützt wird.Warum Webpack module federation? Denn bei Genesis 1.0 wurde das Konzept einer Remote-Komponente vorgeschlagen, die es verschiedenen Diensten ermöglicht, Seiten anderer Dienste aufzurufen. Wenn wir das von Webpack vorgeschlagene Konzept sehen module federation, haben wir bereits begonnen, über die Iteration von Genesis 2.0 nachzudenken

Bis heute haben wir endlich die gesamte Funktionsentwicklung abgeschlossen, und das Projekt im Unternehmen wurde mit der Aktualisierung begonnen, und die Remote-Komponenten wurden geändert, module federationum auf neue Weise aufgerufen zu werden. In diesem Prozess haben wir erfolgreich das module federationProblem gelöst, dass der TS-Typ nicht unterstützt wird, und wir können module federationalle Eingabedateien stark zwischenspeichern

Verwenden des MF-Plugins

MF ist module federationein Akronym für ein Akronym, und ein Dienst kann entweder eine hostSeite oder eine remoteSeite sein.

Der Verwendung von Plug-Ins muss besondere Aufmerksamkeit geschenkt werden MF, und die Extraktion externer CSS-Dateien wird nicht mehr unterstützt, da es bei Verwendung des hostTerminals unmöglich ist, remotedie Informationen der vom Terminal extrahierten externen CSS-Dateien zu kennen . Um dieses Problem zu lösen, renderer.renderHtmlwird der Funktion ein neuer Parameter hinzugefügt styleTagExtractCSS: Wenn der Wert der Wert ist true, wird der Inhalt des Style-Tags, der von der aktuellen Seite gerendert wird, in eine separate CSS-Datei extrahiert. Da der Inhalt des Style-Tags, der von jeder Seite gerendert wird, unterschiedlich ist, werden möglicherweise viele CSS-Dateien generiert. Bitte berücksichtigen Sie dies, bevor Sie es aktivieren.

Klicken Sie hier, um die API-Dokumentation von MF anzuzeigen

Host-Seite

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'
        }
    ]
});

复制代码

entfernt

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')
});
复制代码

Knotenseitiges module federationImplementierungsprinzip

module federation在纯粹的CSR项目中比较容易实现,但是在SSR项目中需要在服务端运行一个Node程序,目前Webpack对此并没有一个好的解决方案,所以Genesis自己实现了Node端module federation下载和执行过程

编译阶段

如果你的项目导出了远程模块,在编译阶段client的文件夹中会多出一个node-exposes文件夹,让我们来看看这几个文件的作用都是什么吧。
Bild

  • 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类型支持

运行阶段

Bild 在程序运行的时候,MF就会去下载远程模块的代码,你会在控制台看到类似这样的一个日志

Bild

远程模块下载完成后,会解压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中讨论

Ich denke du magst

Origin juejin.im/post/7078846054862422046
Empfohlen
Rangfolge