webpack模块联邦

前言

最近有一个朋友问我,他想在A项目里使用B项目的一个弹窗组件应该如何实现。当然最常见的就是将弹窗代码写成npm包的形式,项目A和B分别注入弹窗模块。那么还有其他方法,接下来就来探讨一下UMD共享机制和webpack5的模块联邦是如何实现这个需求的。推荐阅读<Webpack5 新特性 - 模块联邦>文章和YouTube视频教学

模块联邦 (微服务)

接下来将构造两个独立的SPA的应用,在runtime时使用模块联邦进行组件共享。

  • 项目A包含SayHelloFromA组件,这个SayHelloFromA组件也会共享到项目B中。
  • 项目B包含SayHelloFromB组件,这个SayHelloFromB组件也会共享到项目A中。

    通过webpack模块联邦的模式,可以每个应用都可以单独开发和部署。并且通过零部署来实现共享模块更新(SayHelloFromA组件在A项目里更新了,B项目无需重新部署即可应用到最新的SayHelloFromA组件)

开始

使用pnpm workspace管理两个独立的react项目。模块联邦允许团队独立运行的,真实场景一般都是在独立的仓库体系中。但这里只是演示项目就放在一起好管理。 通过CRA创建的端口默认为3000,此时需要将项目B端口改为3001通过cross-env即可

ts 复制代码
"scripts": {
    "start": "cross-env PORT=3001 react-scripts start",
     ...
},

pnpm中Monorepo并发执行,在命令加上--paralleloptions即可

项目搭建完成之后,分别在项目A创建SayHelloFromA组件、在项目B组件创建SayHelloFromB组件,并且再各自的APP.tsx引入这两个组件

ts 复制代码
// a-project > SayHelloFromA.tsx
export default function SayHelloFromA() {
    return <h1>Hello from Application A! </h1>
}

// a-project > SayHelloFromB.tsx
export default function SayHelloFromB() {
    return <h1>Hello from Application B! </h1>
}

接下来通过customize-cra来修改create-react-app项目的webpack配置。当然还有其他方案,不过推荐使用customize-cra来实现

在根目录添加customize-cra和react-app-rewired包

ts 复制代码
pnpm add customize-cra react-app-rewired -w

编写config-overrides.js文件要跟package.json文件放置在同一层级。customize-cra暴露了一些函数API。使用addWebpackPlugin方法扩展webpack的plugin参数,使用setWebpackPublicPath设置publicPath路径。 如下代码库的名称定义为application_a并且将SayHelloFromA组件暴露出去

typescript 复制代码
// A项目 => config-overrides.js
const {
    override,
    addWebpackPlugin
} = require("customize-cra");
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

// 配置公共路径
const publicPath = 'http://localhost:3000/';

// 模块联邦相关配置
const ModuleFederationConfig = new ModuleFederationPlugin({
    name: 'application_a',
    library: { type: 'var', name: 'application_a' },
    filename: 'remoteEntry.js',
    exposes: {
        'SayHelloFromA': './src/SayHelloFromA',
    },
    remotes: {
        'application_b': 'application_b',
    },
    shared: ['react', 'react-dom'],
})

module.exports = override(
    setWebpackPublicPath(publicPath),
    addWebpackPlugin(ModuleFederationConfig)
)

// B项目 => 类似把publicPath端口改为3001,以及对应名称改为`application_b`暴露组件改为SayHelloFromB组件,这里就不在统一展示了

配置完config-overrides.js之后,在public/index.html添加script链接remoteEntry文件

ts 复制代码
项目A
 <head>
     ...
     <script src="http://localhost:3001/remoteEntry.js"></script>
</head>   

项目B
<head>
     ...
     <script src="http://localhost:3000/remoteEntry.js"></script>
</head>   

将package.jso中使用react-scripts的命令全部改为react-app-rewired启动

ts 复制代码
  "start": "cross-env PORT=3001 react-app-rewired start",
  ...

通过上面配置完之后,启动项目发现有如下报错。下面来看看这些报错如何解决

问题一: 当我配置完成运行报错了如下错误,那应该如何解决呢? webpack也提供了Troubleshooting来追击问题 根据webpack中Troubleshooting提供的解决方案

首先将原本index.tsx文件内容改为bootstrap.tsx,然后再新建一个index.js导入import('./bootstrap'); 其次修改config-overrides.js的配置,将shared由原来的数组改为对象

arduino 复制代码
// 修改前
shared:['react', 'react-dom']

// 修改后
shared: {
    "react-dom": "^18.2.0",
    react: {
        eager: true,
    },
},

通过上面的修改解决了问题一,之后我在App.tsx引入application_b/SayHelloFromB组件报了问题二的错误。

问题二: 当我在项目A中导入项目B的组件报错了,从下图不难看出项目B的remoteEntry.js加载成功。 Stack Overflow提供的问题解答 解决该问题也很简单,只需要在src目录新建index.d.ts声明这个模块即可 通过上面声明解决了问题二但此时又遇到了问题三
问题三: 报错提示说找不到SayHelloFromB,但我查看了remoteEntry.js文件是存在的SayHelloFromB组件的 在webpack的Troubleshooting提供的解决方案。将config-overrides.js的exposes的key在前面追加./即可

ts 复制代码
// 修改之前
new ModuleFederationPlugin({
    ...
     exposes: {
        'SayHelloFromB': './src/SayHelloFromB',
    },
})    

// 修改之后
new ModuleFederationPlugin({
    ...
     exposes: {
        './SayHelloFromB': './src/SayHelloFromB',
    },
})    

将上面的三个问题修改完成之后,就可以看到如下效果,在项目A里引用了项目B的SayHelloFromB组件 项目目录结构如下: HTTP请求如下图: (第二次访问状态码为304,缓存机制)

总结

  1. 通过如上步骤,使用webpackv5的模块联邦属性实现了不同项目之间的组件共享。使用模块联邦的好处在于不需要向npm共享机制那样每个项目都需要安装公共模块,模块稍微改动各个项目之间需要更新然后重新部署。 比UMD共享机制的好处在于包体积得到了保证,我们都知道UMD格式是不会做tree shaking的。
  2. UMD共享机制的实现机制在下一节进行探讨
  3. 此次演示的完整代码git仓库

参考文献

Federation: A game-changer in JavaScript architecture
Getting Started With Federated Modules
Merge Proposal: Module federation and code sharing between bundles. Many builds act as one
精读《Webpack5 新特性 - 模块联邦》

相关推荐
福兮说10 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理10 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66611 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)11 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan12 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_12 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒13 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技13 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu14 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub
默_笙14 小时前
🛴 从散件到整机:DeepAgents 与 Agent 身上预留的那些"插槽"(前置介绍)
前端·javascript