共享模块
当前存在的问题
当前在 products 和 cart 微应用中都使用了 faker,当 container 加载这两个应用模块后,faker 被加载了两次:
通过模块联邦的共享模块可以实现相同模块只需加载一次。
实现共享模块
使用模块联邦共享模块,只需要在插件配置中添加 shared
配置项即可。
修改 products 和 cart 应用的 webpack 插件配置:
new ModuleFederationPlugin({
filename: 'remoteEntry.js',
name: 'cart',
exposes: {
'./index': './src/index'
},
// 共享模块
shared: ['faker']
})
注意:共享模块需要异步加载,以本文为例,使用 faker 的代码要放到 import
的 then
的回调函数中,即:
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 应用,现在微应用运行自身的时候也能挂载内容,并且容器应用也能正常访问。