模块联邦

模块联邦,这个名字也挺奇怪的,我感觉叫动态引用 更合适,有点像 动态库 .so 的这种感觉。

直接依赖远程 js,其映射关系为供给方生成的一个 js,用于查找其库的真实地址。

我们一直也这么用的,比如 vconsole 这种工具,一种方式就是直接加载线上脚本使用。

但这种方式不够工程化,引用、打包、使用时,需要特殊对待,不像本地包那样可以直接 import 'xxx'

ModuleFederationPlugin 就是提供一种方式,可以将想共享的内容发布到线上,使用方再通过类似本地包的形式去使用它。

供给方 A 的配置:

目标是生成用于共享的库 (font.js, theme.js ...),和用于查找库地址的清单 (entry.js),

js 复制代码
new ModuleFederationPlugin({
  name: 'app_frame',
  // version 号主要是怕升级多台机器过程中,半半拉拉的时候,有 gap,线上一些依赖找不到了,保留老的清单映射
  // filename 就是 库对应清单 js 了
  filename: 'static/js/remote-entry-v2.js',
  // 这是 entry.js 暴露方式,直接在 window 上暴露变量了
  library: { type: 'var', name: 'app_frame' },
  // 需要共享的内容
  exposes: {
    './font.css': path.resolve(paths.appSrc, './css/font.css'),
    './theme.css': path.resolve(paths.appSrc, './css/theme/index.css'),
  },
  // 一些可以共享的库,先按下不讲
  shared: {
    react: {
      // 这个库(比如react)在整个页面上只能有一个实例。如果 B 发现 A 提供的React版本不兼容,它不会加载自己的版本,而是会直接报错
      singleton: true,
      requiredVersion: deps.react,
    },
    'react-dom': {
      singleton: true,
      requiredVersion: deps['react-dom'],
    },
    'react/jsx-runtime': {
      singleton: true,
      requiredVersion: deps.react,
    },
  },
}),

需求方 B 的配置:

目的是引用上面生成的 entry.js,然后找到对应共享库

js 复制代码
new ModuleFederationPlugin({
  // 库清单 entry.js 的地址
  remotes: {
    // key 在项目内自行定义, name 为提供容器内 name 字段
    app_frame: `app_frame@https://xxx.com/static/js/remote-entry-v2.js`,
  },
  // shared 这里和上面重复了发现没?其实说的就是:
  // 如果上面清单里有这些库的地址,我就不用我自己生成的了,直接复用清单里的
  shared: {
    react: {
      requiredVersion: reactVersion,
    },
    'react-dom': {
      requiredVersion: reactDomVersion,
    },
    'react/jsx-runtime': {
      requiredVersion: reactVersion,
    },
  },
}),

B 在页面中使用的时候,就这么用:

js 复制代码
import 'app_frame/font.css';
import 'app_frame/theme.css';

流程

  1. A 先 build 生成 entry.jsfont.[hash].jstheme.[hash].js, abc.[hash].js ... 放到自己的网站上

  2. B 打开了,先加载 entry.js (因为它是入口,地址也是固定的)

    1. 找到 font.css / theme.css 的地址,加载它们

    2. entry.js 中定义的 shared 的库

      1. 如果满足自己的版本需求,就复用 entry.js 里的 shared 库(直接加载 entry.js 中的对应地址)
      2. 如果不满足,就不看它了,加载自己的 .js

更进一步

其实上面已经看出端倪了,shared 的都是一些不会经常变的库,这几个要是整合到一起,然后正好也满足 B 的需求,B 就不用加载自己的那份了嘛:

给 A 加上一个提取这几个库的功能:

js 复制代码
optimization: {
  splitChunks: {
    cacheGroups: {
       firmware: {
        test: new RegExp(`node_modules${stp}(react|react-dom)([${stp}]|$)`),
        name: 'firmware',
        chunks: 'all',
        enforce: true,
        priority: 30,
      },
    }
  }
}

这样把这三个 react 相关的,放到了 firmware.[hash].js 中,A 当然本身就会加载它,在 B 里我们也做类似的配置:

js 复制代码
optimization: {
  splitChunks: {
    cacheGroups: {
       firmware: {
        test: new RegExp(`node_modules${stp}(react|react-dom)([${stp}]|$)`),
        // 注意这里换了个名字
        name: 'contained-firmware',
        chunks: 'all',
        enforce: true,
        priority: 30,
      },
    }
  }
}

这里换了个名字,主要是为了方便验证,到底加载的是哪个 js。

  • 如果复用成功,只会加载 firmware.[hash].js,两次(第二次就有浏览器缓存了)
  • 如果复用失败,A 加载 firmware.[hash].js,B 加载 contained-firmware.[hash].js

如何保证复用成功?

其实很简单,只要保证版本一致就行了,在上面 ModuleFederationPlugin 的配置里,已经写上了。

刷新下,可以观察到,只去加载 firmware.[hash].js 的。

相关推荐
天蓝色的鱼鱼6 小时前
Turbopack vs Webpack vs Vite:前端构建工具三分天下,谁将胜出?
前端·webpack
西洼工作室1 天前
Vue CLI为何不显示webpack配置
前端·vue.js·webpack
富贵2号2 天前
从零开始理解 Webpack:构建现代前端工程的基石
webpack
Hashan5 天前
告别混乱开发!多页面前端工程化完整方案(Webpack 配置 + 热更新)
webpack
开心不就得了5 天前
构建工具webpack
前端·webpack·rust
鲸落落丶6 天前
webpack学习
前端·学习·webpack
闲蛋小超人笑嘻嘻6 天前
前端面试十四之webpack和vite有什么区别
前端·webpack·node.js
guslegend6 天前
Webpack5 第五节
webpack
海涛高软8 天前
qt使用opencv的imread读取图像为空
qt·opencv·webpack
行者..................8 天前
手动编译 OpenCV 4.1.0 源码,生成 ARM64 动态库 (.so),然后在 Petalinux 中打包使用。
前端·webpack·node.js