微前端解决方案初探 04 模块联邦(共享模块、手动挂载应用)

共享模块

当前存在的问题

当前在 products 和 cart 微应用中都使用了 faker,当 container 加载这两个应用模块后,faker 被加载了两次:

在这里插入图片描述

通过模块联邦的共享模块可以实现相同模块只需加载一次。

实现共享模块

使用模块联邦共享模块,只需要在插件配置中添加 shared 配置项即可。

修改 products 和 cart 应用的 webpack 插件配置:

new ModuleFederationPlugin({
  filename: 'remoteEntry.js',
  name: 'cart',
  exposes: {
    './index': './src/index'
  },
  // 共享模块
  shared: ['faker']
})

注意:共享模块需要异步加载,以本文为例,使用 faker 的代码要放到 importthen 的回调函数中,即:

import('faker').then(m => {
    const faker = m.default
    // 之后才能使用 faker
})

同加载远程应用模块一样,为了避免嵌套一层回调函数,可以在微应用中添加 bootstrap.js 文件,在文件中同步加载 faker,在入口文件中异步加载 bootstrap.js

修改 products 应用:

// products\src\bootstrap.js
import faker from 'faker'

let products = ''

for (let i = 0; i <= 5; i++) {
  // 随机生成产品名称
  products += `<div>${faker.commerce.productName()}</div>`
}

document.querySelector('#dev-products').innerHTML = products

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

// 导出一个方法
export default function sayHello(name) {
  console.log(`Hello ${name}`)
}

修改 cart 应用:

// cart\src\bootstrap.js
import faker from 'faker'

// 生成随机数
document.querySelector('#dev-cart').innerHTML = `在您的购物车中有${faker.datatype.number()}件商品`

// cart\src\index.js
import('./bootstrap')

重新启动 products 和 cart 应用,再次访问 http://localhost:8080 可以看到 faker 只被加载了一次:

在这里插入图片描述

解决共享模块版本冲突的问题

不同版本加载问题

如果 products 和 cart 应用使用了不同版本的 faker,即使通过上面的方式(数组)配置共享模块,该模块仍然会加载两次,共享失败。

将 products 中的 faker 更换为 4.1.0 版本:

npm install faker@4.1.0
# 重新运行
npm start

查看网路面板:

在这里插入图片描述

解决办法

可以使用对象配置 shared,并配置 singleton 选项为 true,它表示仅允许共享范围内共享模块的单一版本(使用高版本)。

// products\webpack.config.js

// 共享模块
shared: {
  faker: {
    // 当多个应用加载了不同版本的模板,仅共享最高的版本
    singleton: true
  }
}

重新运行 products 应用,查看网络面板,看到浏览器仅加载了 cart 应用下的 faker:

在这里插入图片描述

现在模块再次实现了共享,并在使用了低版本的共享模块的应用的控制台给与警告提示:

在这里插入图片描述

products 应用在控制台发出警告,使用了不安全的 5.5.3 版本的 faker 模块,它是由 cart 应用共享的,当前应用加载的是 4.1.0 版本。

容器应用通过 mount 方法挂载微应用

当前存在的问题

当前容器应用在导入微应用后,微应用的代码被直接运行,挂载内容。

这导致容器应用必须事先在模板文件中准备好微应用挂载需要的 DOM 节点,并且与微应用要求的保持一致。

<!-- 容器应用准备好的 DOM 节点 -->
<div id="dev-products"></div>
<div id="dev-cart"></div>

而理想的场景是,容器应用在导入微应用后,应该有权限决定微应用的挂载时机和挂载位置。

解决办法

在每个微应用中导出一个挂载方法,供容器应用调用。

在 products 应用中导出挂载方法:

// products\src\bootstrap.js
import faker from 'faker'

function mount(el) {
  let products = ''

  for (let i = 0; i <= 5; i++) {
    // 随机生成产品名称
    products += `<div>${faker.commerce.productName()}</div>`
  }

  el.innerHTML = products
}

export { mount }

修改 webpack 配置,由于当前配置的模块是 ./src/index.js 它并没有导出 mount 方法,所以将其修改为:

// products\webpack.config.js
exposes: {
  './index': './src/bootstrap'
},

重启 products 应用。

修改容器应用代码:

// container\src\bootstrap.js
import { mount } from 'products/index'
import 'cart/index'

mount(document.querySelector('#dev-products'))

修改 cart 应用

同样的方式修改 cart 应用:

// cart\src\bootstrap.js
import faker from 'faker'

function mount(el) {
  // 生成随机数
  el.innerHTML = `在您的购物车中有${faker.datatype.number()}件商品`
}

export { mount }

// cart\webpack.config.js
exposes: {
  './index': './src/bootstrap'
},

重启应用。

修改容器应用:

// container\src\bootstrap.js
import { mount as mountProducts } from 'products/index'
import { mount as mountCart } from 'cart/index'

mountProducts(document.querySelector('#dev-products'))
mountCart(document.querySelector('#dev-cart'))

在开发环境中使用 mount 方法挂载微应用自身

在 products 和 cart 导出mount 方法后,微应用本身就运行不起来了,所以运行微应用自身时也要调用一下 mount 方法。

// products\src\bootstrap.js
import faker from 'faker'

function mount(el) {
  let products = ''

  for (let i = 0; i <= 5; i++) {
    // 随机生成产品名称
    products += `<div>${faker.commerce.productName()}</div>`
  }

  el.innerHTML = products
}

const el = document.querySelector('#dev-products')
// 微应用自身调用
mount(el)

export { mount }

而一般只需要在开发环境中运行微应用自身,所以需要判断环境

// 判断开发环境
if (process.env.NODE_ENV === 'development') {
  const el = document.querySelector('#dev-products')
  // 微应用自身调用
  mount(el)
}

当前容器应用也是运行在开发环境中,所以 products 应用仍会调用 mount

如果修改容器应用模板文件中的挂载节点:

<!-- 容器应用准备好的 DOM 节点 -->
<div id="prod-products"></div>
<div id="prod-cart"></div>

并修改挂载方法:

// container\src\bootstrap.js
import { mount as mountProducts } from 'products/index'
import { mount as mountCart } from 'cart/index'

mountProducts(document.querySelector('#prod-products'))
mountCart(document.querySelector('#prod-cart'))

此时,products 应用自身调用的 mount 方法就会因为找不到 dev-products 而报错,所以还需要增加判断:

// 判断开发环境
if (process.env.NODE_ENV === 'development') {
  const el = document.querySelector('#dev-products')
  // 判断节点是否存在
  if (el) {
    // 微应用自身调用
    mount(el)
  }
}

同样的方式修改 cart 应用,现在微应用运行自身的时候也能挂载内容,并且容器应用也能正常访问。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值