ModuleFederation模块联邦常规使用不完全指北

187 阅读3分钟

image.png

前言

前一篇文章尝试将模块联邦进行落地,时隔月余,终于得空来书接上回;期间尝试多种方式进行最大化发挥其能力,最终整理出其最适合场景,分享给大家。

背景

还是这个项目结构,主要改动依然在base project

一、ModuleFederationPlugin配置

先看代码

plugins: [
    new ModuleFederationPlugin({
        // 可自行定义
        name: 'base',
        // 实测type定义无影响, name需与外层一致
        library: { type: "umd", name: "base" },
        // 可匹配拆分chunk
        filename: "js/remoteEntry.js",
        exposes: {// 全部导出的组件
            // 名称根据版本加前缀: ./
            // npm
            "./axios": { import: "axios", name: 'axios' },
            "./classnames": { import: "classnames", name: 'classnames' },
            "./core-js": { import: "core-js", name: 'core-js' },
            "./immer": { import: "immer", name: "immer" },
            "./react": "react",
            "./react-dom": "react-dom",
            "./react-redux": "react-redux",
            "./redux": { import: "redux", name: "redux" },
            "./redux-actions": { import: "redux-actions", name: "redux-actions" },
            "./redux-logger": "redux-logger",
            "./redux-thunk": "redux-thunk",
            "./antd": { import: "antd", name: "antd" },
            "./@ant-design/icons": "@ant-design/icons",
            // 地址为相对路径, 相对此文件
            "./libs": { import: "./src/libs/index", name: 'libs' },
            // comps
            './RankList': { import: './src/rank-list/index.js', name: 'RankList' },
            './ModalInfoMedals': './src/modal-info-medals/index.js',
        },
        shared: {// 使用容器内与此处保持一致
            ...npmLibsObj,
            "react": {
                singleton: true,
                eager: true,
                requiredVersion: '*',
            },
            "react-dom": {
                requiredVersion: '*',
            },
        }
    }),
]

其中变化的部分为exposesshared

  • exposes内的value部分可配置为对象,配置name后,可在其它项目使用时作为文件名称

  • shared内可将package.json内的dependencies全部npm依赖共享出去

    其中shared内的key对应的value对象若只配置requiredVersion,打包后的js/remoteEntry.js内只会增加对应key的引用(不拷贝key对应的文件本身),若同时配置了singletoneager,则会拷贝key对应的文件本身;实测本react项目仅配置react用以实现单例即可。

  • 引入dependencies并稍加处理

    const npmLibs = require("../package.json").dependencies
    const npmLibsObj = {};
    Object.entries(npmLibs).map(([key, version]) => {
        npmLibsObj[key] = {
            // singleton: true,
            // eager: true,
            requiredVersion: version,
        }
    })
    
  • analyzer结果如下

image.png

除去react,其余均为对象引用,此时的js/remoteEntry.js文件最小最干净

二、打包关键配置

依然要保持chunks不为'all',同时为实现基础模块就近使用,这里改为了chunks: (chunk) => false,,而cacheGroups内只配置default: false,就可。

optimization: {
    splitChunks: {
        // chunks: 'all',
        chunks: (chunk) => false,
        automaticNameDelimiter: '-',
        cacheGroups: {
            default: false,
        }
    },
},

三、其余remote项目

无需任何调整🥳🥳🥳

虽然我很想这么说

最好也将全部package.json内的dependencies全部依赖共享出去

目前发现"react-router-cache-route", "react-router-dom", "react-redux"这三个包不可以在remote项目内同时shared,会导致白屏,暂时未排查出具体原因

四、效果

前三个js分别为模块联邦入口、core.js、远程组件

本地 image.png

生产 image.png 第四个为增加name的远程组件

总结

针对模块联邦的定位官方文档说的也很清楚,简单来说,就是按需加载的远程组件/库;

主要通过提取公共的组件/库,以供其他remote项目使用,实现了组件的中心化维护、迭代;按需加载的特性可以进一步降低打包产物大小、节约客户端网络带宽、提升SPA应用加载/渲染性能;shared特性可以进一步缩短组件依赖加载路径,更彻底的剥离业务代码。

个人认为,最佳场景是基于相同基础包所构建围绕相同业务的平行多项目集群,换句话说就是适合上微前端的项目集群都十分适合模块联邦;

菜鸡献丑,大佬们轻喷