模块联邦

模块联邦,这个名字也挺奇怪的,我感觉叫动态引用 更合适,有点像 动态库 .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 小时前
Vite 与 Webpack 热更新原理
前端·webpack·vite
天天进步20153 天前
前端工程化:Webpack从入门到精通
前端·webpack·node.js
難釋懷6 天前
TypeScript-webpack
javascript·webpack·typescript
ᥬ 小月亮6 天前
webpack基础
前端·webpack
abigale037 天前
webpack+vite前端构建工具 -11实战中的配置技巧
前端·webpack·node.js
拾光拾趣录9 天前
Webpack 打包中的 Hash 生成机制
前端·webpack·前端工程化
abigale0318 天前
webpack+vite前端构建工具 - 8 代码分割
前端·webpack·node.js
abigale0318 天前
webpack+vite前端构建工具 - 9 webpack技巧性配置
前端·webpack·node.js
Hilaku18 天前
你以为的 tree shaking,其实根本没生效
前端·javascript·webpack