La práctica de la federación de módulos Webpack en Vue SSR

Federación de módulos de paquetes web

Creo que puedes encontrarlo aquí, lo que indica que Webpack module federationya tienes una cierta comprensión. Hay muchos tutoriales en la comunidad que te dicen cómo usarlo y qué hace. Si no sabes mucho al respecto, primero podrías echar un vistazo a la documentación oficial .

Genesis2.0 es actualmente el único marco soportado por Vue2 en términos de SSR Webpack module federation¿Por qué? Es porque cuando Génesis 1.0, se propuso el concepto de un componente remoto, que permite que diferentes servicios llamen a páginas de otros servicios. Cuando vemos el concepto que propone Webpack module federation, ya nos hemos puesto a pensar en la iteración de Génesis 2.0.

Hasta hoy, finalmente hemos completado todo el desarrollo de funciones, y el proyecto en la empresa comenzó a actualizarse, y los componentes remotos se modificaron para module federationllamarlos de una nueva manera. En este proceso, solucionamos con éxito el module federationproblema de no admitir el tipo TS y podemos module federationalmacenar en caché todos los archivos de entrada.

Uso del complemento MF

MF es module federationun acrónimo de un acrónimo, y un servicio puede ser un hostlado o un remotelado.

Es necesario prestar especial atención al uso de MFcomplementos, y ya no se admite la extracción de archivos CSS externos, ya que cuando hostse usa el terminal, es imposible conocer remotela información de los archivos CSS externos extraídos por el terminal. . Para resolver este problema, renderer.renderHtmlse agrega un nuevo parámetro a la función styleTagExtractCSSCuando el valor es el valor true, el contenido de la etiqueta de estilo representada por la página actual se extraerá en un archivo CSS separado. Debido a que el contenido de la etiqueta de estilo representada por cada página es diferente, es posible que se generen muchos archivos CSS. Considérelo antes de habilitarlo.

Haga clic aquí para ver la documentación de la API de MF

lado anfitrión

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

复制代码

control remoto

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 federationPrincipio de implementación del lado del nodo

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

编译阶段

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

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

运行阶段

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

imagen

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

Supongo que te gusta

Origin juejin.im/post/7078846054862422046
Recomendado
Clasificación