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-crareact-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.jsexposes的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 新特性 - 模块联邦》

相关推荐
M_emory_24 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito28 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
文军的烹饪实验室3 小时前
ValueError: Circular reference detected
开发语言·前端·javascript
Martin -Tang4 小时前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发4 小时前
解锁微前端的优秀库
前端
王解4 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁5 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis