模块联邦

模块联邦,这个名字也挺奇怪的,我感觉叫动态引用 更合适,有点像 动态库 .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.js,font.[hash].js,theme.[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 的。

相关推荐
guzicheng19901 天前
webpack 安装笔记:全局 vs 本地,以及一个 PATH 环境变量的坑
前端·webpack
liangshanbo121510 天前
面试题:Webpack 的 publicPath 有什么作用?
前端·webpack·node.js
liangshanbo121512 天前
Webpack vs Vite 底层原理高频追问的问题
前端·webpack·node.js
liangshanbo121512 天前
面试题:AI 对话上下文超限怎么办?
面试·webpack·devops
liangshanbo121513 天前
Webpack 文件指纹面试题整理
前端·webpack·node.js
liangshanbo121513 天前
Webpack 常见 Plugin 面试题
前端·webpack·node.js
liangshanbo121513 天前
Webpack Tree Shaking 面试题整理
前端·webpack·node.js
陪我一起学编程13 天前
vue3笔记
前端·webpack·vue3·vite·脚手架·vue3笔记·组件式
liangshanbo121513 天前
Webpack的分包策略面试题
前端·webpack·node.js
liangshanbo121515 天前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js