Skip to content

webpack 5 模块联邦

🔗 webpack module federation

官方是这么解释模块联邦的:多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。通常称为微前端,但不仅于此。

🧐🧐🧐 或许打个比方,在此之前在多个项目之间我们是如何共享一个模块 或 组件的

  1. copy,缺点:每次修改都需要cv到依赖项目中。
  2. 将公共模块 打包成 npm 包,缺点:如果包更新,依赖的项目需要更新依赖包且重新构建上线。
  3. 以 UMD 方式共享,即直接使用线上的 cdn 链接,缺点:可能会存在库之间的冲突,无法最大优化编译打包。

因此这个东西诞生了🥳🥳🥳

模块联邦

所有子应用都可以利用 Runtime 方式复用主应用的 Npm 包和模块,让应用具备模块化输出能力,开辟了一种新的应用形态,即 “中心应用”,这个中心应用用于在线动态分发 Runtime 子模块,并不直接提供给用户使用。

案例演示

📁 首先新建两个应用,分别是 app-aapp-b,目录如下👇

├─ app-*
│ ├─ src
│ │ ├─ components
│ │ │ └─ Example.jsx
│ │ │
│ │ ├─ app.js
│ │ └─ app.jsx
| │
│ ├─ index.js
│ ├─ package.json
│ ├─ webpack.config.js

🔃 安装依赖

sh
npm i -S react react-dom
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader @babel/preset-env @babel/preset-react @babel/core

⚠️ 注意:webpack 必须要是 5版本及以上

📋 配置

jsx
// app-a/src/app.jsx
import React from 'react'

// 引用 app_b 导出的 Example 组件和 Example2 组件
const Example1 = React.lazy(() => import('app_b/Example'))
const Example2 = React.lazy(() => import('app_b/Example2'))

const App = () => {
  return (
    <div>
      <p>this is application</p>
      <Example1 />
      <Example2 />
    </div>
  )
}

export default App
javascript
// app-a/src/app.js
// app-b 一样
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './app.jsx'
const root = createRoot(document.getElementById('app'))
root.render(<App />)
jsx
// app-a/src/components/Example.jsx
// app-b 一样,在 Example 和 Example2 随便写个组件就好了
import React from 'react'

export default function Example() {
  return (
    <h1>我是A应用的一个组件-example1</h1>
  )
}
js
// app-a/src/index.jsx
// 为什么要设置这个文件呢?
// 大致的理由是 由于被共享的组件需要被先加载进来。但 webpack 在构建打包时,并不知道 remotes 里哪些组件是被使用到的。于是建议在入口里动态加载我们的程序。
// 详情可以查阅这篇 https://webpack.docschina.org/concepts/module-federation/#Uncaught-Error-Shared-module-is-not-available-for-eager-consumption
import('./src/app.js')

重点来了!!!

js
// app-a/webpack.config.js
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')
const { dependencies } = require('./package.json')

module.exports = {
  mode: 'development',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
  },
  resolve: {
    extensions: ['.js', '.jsx', 'json'],
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        options: {
          presets: ['@babel/env', '@babel/preset-react'],
        },
      },
    ],
  },
  plugins: [
    // 使用 模块联邦插件
    new ModuleFederationPlugin({
      name: 'app_a', // 当前的应用名,唯一
      // 将 模块以 UMD 方式打包,name 为 使用的模块名,类似 jquery 的 $
      // library: {
      //   type: 'var',
      //   name: 'app_a',
      // },
      // 入口文件名称,用于给对外模块使用时的入口文件名
      filename: 'remoteEntry.js',
      // 选择导出的组件,供外部使用
      exposes: {
        './Example': './src/components/Example',
      },
      // 依赖的远程模块
      remotes: {
        app_b: 'app_b@http://localhost:8082/remoteEntry.js',
      },
      // 共享的第三方库,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM
      shared: {
        ...dependencies,
        react: {
          singleton: true, // 只允许用单个版本
          requiredVersion: dependencies['react'], // 版本
        },
        'react-dom': {
          singleton: true,
          requiredVersion: dependencies['react-dom'],
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    // 热加载
    new webpack.HotModuleReplacementPlugin(),
  ],
  devServer: {
    hot: true,
  },
}

app-bwebpack.config.js 差不多,只是修改一下 exposesremotes 即可。

现在就完成了 app-a 共享 Example 组件,同时使用远程模块 app-bExampleExample2 组件。

打包情况模块联邦

🖥️ 运行一下看看

模块联邦

可以清楚看到 remoteEntry.jssrc_app_js.bundle.js 前加载。 其中 src_components_Example.bundle.jssrc_components_Example2.bundle.js 是来自 远程模块 app_b exposes 的。 vendors-node_modules_react_index_js.bundle.jsvendors-node_modules_react-dom_index_js.bundle.js 是来自 远程模块 app_b shared 的。

📦 完整代码

至此,一个简单的 模块联邦 小demo完成,实现了 共享组件功能。

后记

如果有使用过一些 微前端 的解决方案,比如 阿里的 qiankun,你会发现与 webpack 的模块联邦给出的微前端解决方案很类似。有兴趣的可以使用 它 来实现一个简单版的 qiankun。

Released under the MIT License.