微前端 -- 模块联邦

模块联邦概述

Module Federation 即为模块联邦,是 Webpack 5 中新增的一项功能,可以实现跨应用共享模块。
在这里插入图片描述

快速上手

需求
通过模块联邦在容器应用中加载微应用。
在这里插入图片描述
应用结构

products 
   ├── package-lock.json 
   ├── package.json
   ├── public   
   │   └── index.html  
   ├── src  
   │   └── index.js  
   └── webpack.config.js

应用初始化

  • 在入口 JavaScript 文件中加入产品列表

    import faker from "faker"
    
    let products = ""
    
    for (let i = 1; i <= 5; i++) {
    	products += `<div>${faker.commerce.productName()}</div>`
    }
    
    document.querySelector("#dev-products").innerHTML = products
    
  • 在入口 html 文件中加入盒子

    <div id="dev-products"></div>
    
  • webpack 配置

    const HtmlWebpackPlugin = require("html-webpack-plugin")
    
    module.exports = {
      mode: "development",
      devServer: {
        port: 8081
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: "./public/index.html"
        })
      ]
    }
    
  • 添加应用启动命令

    "scripts": {
    	"start": "webpack serve"
    }
    
  • 通过 copy 的方式创建 container 和 cart

Module Federation

通过配置模块联邦实现在容器应用中加载产品列表微应用。

  • 在产品列表微应用中将自身作为模块进行导出

    // webpack.config.js
    // 导入模块联邦插件
    const ModuleFederationPlugin =
    require("webpack/lib/container/ModuleFederationPlugin")
    
    // 将 products 自身当做模块暴露出去
    new ModuleFederationPlugin({
    	// 模块文件名称, 其他应用引入当前模块时需要加载的文件的名字
    	filename: "remoteEntry.js",
    	// 模块名称, 具有唯一性, 相当于 single-spa 中的组织名称
    	name: "products",
    	// 当前模块具体导出的内容
    	exposes: {
    		"./index": "./src/index"
    	}
    })
    
    // 在容器应用中要如何引入产品列表应用模块?
    // 1. 在容器应用中加载产品列表应用的模块文件
    // 2. 在容器应用中通过 import 关键字从模块文件中导入产品列表应用模块
    
  • 在容器应用的中导入产品列表微应用

    // webpack.config.js
    // 导入模块联邦插件
    const ModuleFederationPlugin =
    require("webpack/lib/container/ModuleFederationPlugin")
    
    new ModuleFederationPlugin({
    	name: "container",
    	// 配置导入模块映射
    	remotes: {
    		// 字符串 "products" 和被导入模块的 name 属性值对应
    		// 属性 products 是映射别名, 是在当前应用中导入该模块时使用的名字
    		products: "products@http://localhost:8081/remoteEntry.js"
    	}
    })
    
    // src/index.js
    // 因为是从另一个应用中加载模块, 要发送请求所以使用异步加载方式
    import("products/index").then(products => console.log(products))
    

    通过上面这种方式加载在写法上多了一层回调函数, 不爽, 所以一般都会在 src 文件夹中建立bootstrap.js,在形式上将写法变为同步

    // src/index.js
    import('./bootstrap.js')
    
    // src/bootstrap.js
    import "products/index"
    

文件打包加载分析

  • Products 应用打包分析
    在这里插入图片描述

  • Container 应用打包分析
    在这里插入图片描述

  • 文件加载顺序分析
    在这里插入图片描述

加载 Cart 微应用

// cart/webpack.config.js
const ModuleFederationPlugin =
require("webpack/lib/container/ModuleFederationPlugin")

new ModuleFederationPlugin({
	name: "cart",
	filename: "remoteEntry.js",
	exposes: {
		"./index": "./src/index"
	}
})
// container/webpack.config.js
remotes: {
	cart: "cart@http://localhost:8082/remoteEntry.js"
}
// container/bootstrap.js
import "cart/index"
<!-- container/index.html -->
<div id="dev-cart"></div>

注意:cart/index.html 和 products/index.html 仅仅是在开发阶段中各自团队使用的文件,而 container/index.html 是在开发阶段和生产阶段都要使用的文件。

共享模块

实现模块共享

在 Products 和 Cart 中都需要 Faker,当 Container 加载了这两个模块后,Faker 被加载了两次。

// 分别在 Products 和 Cart 的 webpack 配置文件中的模块联邦插件中添加以下代码
{
	shared: ["faker"]
}
// 重新启动 Container、Products、Cart

注意:共享模块需要异步加载,在 Products 和 Cart 中需要添加 bootstrap.js

共享模块版本冲突解决

Cart 中如果使用 4.1.0 版本的 faker,Products 中使用 5.2.0 版本的 faker,通过查看网络控制面板可以发现 faker 又会被加载了两次,模块共享失败。

解决办法是分别在 Products 和 Cart 中的 webpack 配置中加入如下代码

shared: {
	faker: {
		singleton: true
	}
}

但同时会在原本使用低版本的共享模块应用的控制台中给予警告提示。

开放子应用挂载接口

在容器应用导入微应用后,应该有权限决定微应用的挂载位置,而不是微应用在代码运行时直接进行挂载。所以每个微应用都应该导出一个挂载方法供容器应用调用。

// Products/bootstrap.js
import faker from "faker"

function mount(el) {
	let products = ""
	for (let i = 1; i <= 5; i++) {
		products += `<div>${faker.commerce.productName()}</div>`
	}
	el.innerHTML = products
}

// 此处代码是 products 应用在本地开发环境下执行的
if (process.env.NODE_ENV === "development") {
	const el = document.querySelector("#dev-products")
	// 当容器应用在本地开发环境下执行时也可以进入到以上这个判断, 容器应用在执行当前代码时肯定是获
	取不到 dev-products 元素的, 所以此处还需要对 el 进行判断.
	if (el) mount(el)
}

export { mount }
// Products/webpack.config.js
exposes: {
	// ./src/index => ./src/bootstrap 为什么 ?
	// mount 方法是在 bootstrap.js 文件中导出的, 所以此处要导出 bootstrap
	// 此处的导出是给容器应用使用的, 和当前应用的执行没有关系, 当前应用在执行时依然先执行 index
	"./index": "./src/bootstrap"
}
// Container/bootstrap.js
import { mount as mountProducts } from "products/index"
mountProducts(document.querySelector("#my-products"))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值