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

相关推荐
会发光的猪。32 分钟前
uniapp使用uv-popup弹出框隐藏底部导航tabbar方法
前端·小程序·uni-app·uv
—丫丫34 分钟前
uniapp商城之用户模块【个人信息】
前端·uni-app
HWL56791 小时前
使用媒体查询确保网页能够在手机、平板和电脑上正常浏览
前端·javascript·vue.js·智能手机·媒体
大大大大小小2 小时前
问卷调查系统Two-Step-Kmeans-前端后端搭建完成
前端·算法·kmeans
m0_zj2 小时前
CSS整体回顾
前端·css·css3
沉默的煎蛋3 小时前
Java 中接口和抽象类的异同
java·开发语言·前端·css·tomcat·html5
Ellen翔4 小时前
npx tailwindcss init报错npm error could not determine executable to run
前端·npm·node.js
linweidong6 小时前
猫眼前端开发面试题及参考答案
react.js·webpack·前端面试·flex布局·css3动画·vue组件·三栏布局
m0_zj6 小时前
19.[前端开发]Day19-王者荣项目耀实战(二)
前端·css·chrome·html·html5
engchina6 小时前
React中为每个列表项显示多个DOM节点的解决方案
前端·javascript·react.js