23. 模块联邦

760 阅读2分钟

一、不同的方式共享模块

1. NPM 方式共享模块

正常的代码共享需要将依赖作为 Lib 安装到项目,进行 Webpack 打包构建再上线。

如下图,对于项目 Home 与 Search,需要共享一个模块时,最常见的办法就是将其抽成通用依赖,并分别安装在各自项目中。

image.png

2. UMD 方式共享模块

真正 Runtime 的方式可能是 UMD 方式共享代码模块,也就是将模块用 Webpack UMD 模式打包,并输出到其他项目中。

如下图,对于项目 Home 与 Search,直接利用 UMD 包复用了一个模块。

image.png

3. 微前端方式共享模块

微前端(MFE:micro-frontends)也是最近比较火的模块共享管理方式,微前端就是要解决多项目并存问题,多项目并存的最大问题就是模块共享,不能有冲突。

image.png

4. 模块联邦方式共享模块

Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 npm 包、构建再发布了!简单来说,就是可以实现跨应用共享模块。

如下图,这个方案是直接将一个项目的包应用于另一个项目,同时具备整体项目一起打包的公共依赖抽取能力。

image.png

二、模块联邦应用案例

案例代码

案例中,先创建了三个独立的项目,后又用模块联邦的方式将三个项目联系到一起。

关键代码:

nav 项目:

// webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: 'nav',    // 这个模块联邦的名字,将来其他应用访问这个组件时需要通过这个名字来访问
            filename: 'remoteEntry.js',     // 向外暴露的资源的名字,供其他应用使用
            remotes: {},    // 引用其他应用暴露出来的组件
            exposes: {      // 暴露一些组件给其他应用使用
                './Header': './src/Header.js'
            },
            shared: {}      // 共享的模块,比如第三方模块 lodash
        })
    ]
}

home 项目:

// webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: 'home',
            filename: 'remoteEntry.js',
            remotes: {    // 这里引用了 nav 项目中暴露出来的组件
                // 格式:
                // xxx: nav项目的模块联邦name @ 项目地址 / nav项目模块联邦向外暴露的资源
                nav: 'nav@http://localhost:3003/remoteEntry.js'
            },
            exposes: {
                './HomeList': './src/HomeList.js'
            },
            shared: {}
        })
    ]
}
// src/index.js

import HomeList from './HomeList'
// 格式:
// import('webpack配置中的模块联邦的remotes中的xxx / nav项目模块联邦中的exposes中的./Header')
import('nav/Header').then((Header) => {  // 通过异步方式导入,因为网络共享或者模块载入是有延迟的
    const body = document.createElement('div')
    body.appendChild(Header.default())
    document.body.appendChild(body)
    document.body.innerHTML += HomeList(5)
})

search 项目:

// webpack.config.js

const { ModuleFederationPlugin } = require('webpack').container
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: 'search',
            filename: 'remoteEntry.js',
            remotes: {
                nav: 'nav@http://localhost:3003/remoteEntry.js',
                home: 'home@http://localhost:3001/remoteEntry.js'
            }
        })
    ]
}
// src/index.js

// 引入 nav项目暴露出来的Header组件 和 home项目暴露出来的HomeList组件
Promise.all([import('nav/Header'), import('home/HomeList')])
    .then(([
        {
            default: Header
        },
        {
            default: HomeList
        }
    ]) => {
        document.body.appendChild(Header())
        document.body.innerHTML += HomeList(3)
    })