微前端解决方案初探 03 模块联邦(概述、案例介绍、应用初始化)

模块联邦概述

模块联邦(Module Federation)是 Webpack 5 中新增的一项功能,可以实现跨应用共享模块。

以下图为例:

在这里插入图片描述

在 A 应用中有一个 sayHelloFromA 方法,在 B 应用中有一个 sayHelloFromB 方法。

如果要实现在 A 应用中调用 B 应用中的 sayHelloFromB 方法,在 B 应用中调用 A 应用的 sayHelloFromA 方法,这种跨应用调用方法的场景可以使用 模块联邦 实现。

将每个应用看作一个模块,在一个应用中加载另一个应用,这样就可以实现微前端架构。

创建应用结构

需求

稍后会创建 3 个应用,一个容器应用,两个微应用。最终要通过模块联邦的方式在容器应用中加载微应用。应用中全部使用 faker 创建假数据。

在这里插入图片描述

应用结构

这三个应用使用相同的结构,以产品列表应用为例:

products
├─ public # 静态资源文件
│   └─ index.html
├─ src # 应用源代码
│   ├─ bootstrap.js # 加载远程模块和执行业务逻辑的文件
│   └─ index.js # 应用入口文件
├─ package-lock.json # 项目工程文件
├─ package.json # 项目工程文件
└─ webpack.config.js # webpack 配置文件

应用初始搭建

# 创建 module-federations 文件夹存放所有应用
mkdir module-federations
cd module-federations

# 创建 products 文件夹存放产品列表应用
mkdir products
cd products

# 初始化 package.json
npm init -y

# 安装依赖
npm i faker html-webpack-plugin webpack webpack-cli webpack-dev-server

本例安装的依赖版本如下:

"dependencies": {
    "faker": "^5.5.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.68.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.7.4"
}

新建模板文件 public/index.html

新建应用入口文件 src/index.js

新建 webpack.config.js 配置文件。

修改 package.json 的启动脚本:

"scripts": {
  "start": "webpack serve"
},

最后相同的步骤创建容器应用 container 和 购物车应用 cart。

产品微应用初始化

入口文件中加入产品列表

// products\src\index.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

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

定义模板文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>产品列表</title>
</head>
<body>
  <div id="dev-products"></div>
</body>
</html>

webpack 配置

// products\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devServer: {
    port: 8081
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
}

运行

npm start 运行应用,访问 http://localhost:8081 查看是否成功。

容器应用初始化

入口文件添加测试代码

// container\src\index.js
console.log('container')

定义模板文件

复制 products/public/index.html 文件到容器应用,并修改标题(注意保留同 id 的 div):

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Container</title>
</head>
<body>
  <div id="dev-products"></div>
</body>
</html>

webpack 配置

复制 products/webpack.config.js 文件到容器应用中,并修改启动端口:

// container\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'development',
  devServer: {
    port: 8080
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
}

运行

npm start 运行后访问 http://localhost:8080

在容器应用中加载产品列表微应用

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

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

// products\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入模块联邦插件
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')

module.exports = {
  mode: 'development',
  devServer: {
    port: 8081
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    // 将应用自身作为模块暴露出去
    new ModuleFederationPlugin({
      // 构建输出的模块文件名称
      // 其它应用引入当前模块时需要加载的文件名称
      filename: 'remoteEntry.js',

      // 模块名称,相当于 single-spa 的组织名称
      // 被远程引用时路径为 `<name>/<expose>`
      // 模块名称具有唯一性,不同的模块不能具有相同的名称,如果名称相同,可以在 `remotes` 配置引入的时候设置模块别名
      name: 'products',

      // 被远程引用时可暴露的资源路径及其别名
      // key 是模块名称
      // value 是具体的模块路径(`.js` 扩展名可以省略)
      exposes: {
        // 被远程引用时的路径为 `<name>/index`
        // 注意:模块名称前要添加 `./` 才会生效
        './index': './src/index'
      }
    })
  ]
}

在容器应用中导入产品列表微应用

在容器应用中引入产品列表微应用的方式:

  1. 在容器应用中加载产品列表微应用构建的模块文件
  2. 在容器应用中通过 import 关键字从模块文件中导入产品列表微应用模块
// container\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入模块联邦插件
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')

module.exports = {
  mode: 'development',
  devServer: {
    port: 8080
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new ModuleFederationPlugin({
      name: 'container',

      // 远程引用的应用及其别名的映射
      remotes: {
        // key 是模块的别名,作为当前应用中引入该模块时的 name
        // value 是模块具体地址,有两部分组成:`<name>@<url>`
        //   name 是模块自己配置的名称
        //   url 是模块构建的文件地址
        products: 'products@http://localhost:8081/remoteEntry.js'
      }
    })
  ]
}

加载远程应用模块:

// container\src\index.js
// 因为 products 是远程应用模块,要发送请求,所以使用异步加载的方式
// products 是 remotes 中配置的模块别名,index 是产品列表微应用配置的具体资源名称
import('products/index').then(products => {
  const sayHello = products.default
  sayHello('container')
})

重新启动

重新启动 products 和 container 应用,访问 http:localhost:8080 看是否显示了产品列表。

加载方式优化

webpack 关于使用 bootstrap.js 的介绍

在入口文件中加载远程应用模块,需要异步加载,在回调中执行全部逻辑。

可以将加载远程应用模块和逻辑,以同步的形式写在另一个文件中,在入口文件中异步加载这个文件,解决嵌套一层回调的问题。

创建一个 bootstrap.js 文件:

// container\src\bootstrap.js
// 已同步的方式引入远程应用模块
import sayHello from 'products/index'

sayHello('container-bootstrap')

修改入口文件:

// container\src\index.js
// 因为 products 是远程应用模块,要发送请求,所以使用异步加载的方式
// products 是 remotes 中配置的模块别名,index 是产品列表微应用配置的具体资源名称
// import('products/index').then(products => {
//   const sayHello = products.default
//   sayHello('container')
// })

// 异步加载
import('./bootstrap')

应用 webpack 打包分析和容器应用文件加载顺序分析

Products 应用打包分析

在这里插入图片描述

products 应用中只有一个入口文件 index.js,webpack 在对这个应用打包的时候会执行两个流程:

  • 正常打包流程
  • 模块联邦插件的打包流程

正常打包流程

正常打包流程,最终会构建生成 main.js(默认)的文件,允许我们单独运行应用。

模块联邦插件打包流程

在这里插入图片描述

配置模块联邦插件的时候,通过 filename 选项指定了当前应用模块的文件名称为 remoteEntry.js,模块联邦插件会将应用模块打包成这个文件。

该文件中包含模块中需要加载的文件列表,以及如何加载它们的代码。

另外还通过 exposes 选项配置了具体资源文件,即当前应用模块要导出的模块列表。

模块联邦插件会将这个模块列表中的文件打包成单独的文件,例如当前 products 应用中会将 ./src/index.js 打包成名为 src_index_js.js 的文件(图中以 src_index.js 表示),这个文件中包含 ./src/index.js 文件的代码。

./src/index.js 文件中又引入了 faker 模块,该模块对应的文件地址是 node_modules/faker/index.js,所以模块联邦插件也会将它单独打包为名为(默认前缀) vendors-node_modules_faker_index_js.js 的文件(图中以 faker.js 表示)。

Container 应用打包分析

在这里插入图片描述

container 应用中包含两个文件:

  • src/index.js:入口文件,异步加载了 src/bootstrap.js 文件
  • src/bootstrap.js:同步加载 products 远程模块

webpack 在对 container 应用打包的时候,最终会生成两个文件:

  • main.js:包含 src/index.js 文件的内容
  • bootstrap.js:包含 src/bootstrap.js 文件的内容

由于 products 模块是远程模块,需要异步加载,所以不会被打包为本地文件。

文件加载顺序分析

在这里插入图片描述

访问 container 应用(http://localhost:8080) 查看文件加载顺序。

  • 首先会加载该应用的 main.js 文件
  • 加载 main.js 文件时发现需要加载 bootstrap.js
  • 加载 bootstrap.js 的时候发现需要从 products 模块中导入一些内容
  • 首先找到 products 对应的模块文件 remoteEntry.js,该文件包含如何加载模块的代码:加载 src_index.jsfaker.js
  • src_index.jsfaker.js 两个文件加载完成后,bootstrap.js 就完成了 products 模块的加载,具备了执行的条件
  • 于是 bootstrap.js 执行了 import sayHello from 'products/index' 的操作,并执行后面的代码

购物车微应用初始化

下面使用相同的方式在 Container 应用中加载 cart 应用。

入口文件

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

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

模板文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>购物车</title>
</head>
<body>
  <div id="dev-cart"></div>
</body>
</html>

webpack 配置

// cart\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')

module.exports = {
  mode: 'development',
  devServer: {
    port: 8082
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new ModuleFederationPlugin({
      filename: 'remoteEntry.js',
      name: 'cart',
      exposes: {
        './index': './src/index'
      }
    })
  ]
}

在容器应用中加载 cart 应用

在容器应用中配置 cart 应用:

// container\webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入模块联邦插件
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin')

module.exports = {
  mode: 'development',
  devServer: {
    port: 8080
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    }),
    new ModuleFederationPlugin({
      name: 'container',

      // 远程引用的应用及其别名的映射
      remotes: {
        // key 是模块的别名,作为当前应用中引入该模块时的 name
        // value 是模块具体地址,有两部分组成:`<name>@<url>`
        //   name 是模块自己配置的名称
        //   url 是模块构建的文件地址
        products: 'products@http://localhost:8081/remoteEntry.js',
        cart: 'cart@http://localhost:8082/remoteEntry.js'
      }
    })
  ]
}

模板文件中添加 cart 应用挂载节点:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Container</title>
</head>
<body>
  <div id="dev-products"></div>
  <div id="dev-cart"></div>
</body>
</html>

引入 cart 模块:

// container\src\bootstrap.js
// 已同步的方式引入远程应用模块
import sayHello from 'products/index'
import 'cart/index'

sayHello('container-bootstrap')

现在重新运行 container 和 cart 应用,再次访问 `http://localhost:8080

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
iptables 是一种在 Linux 操作系统上广泛使用的防火墙工具。它能够通过过滤、修改和重定向网络数据包来加强服务器的安全性。 首先,iptables 可以根据源和目标 IP 地址、端口号和协议类型等条件对网络数据包进行过滤。例如,我们可以配置 iptables 来阻止特定 IP 地址或端口的流量进入服务器,从而避免潜在的安全威胁。此外,我们还可以通过 iptables 的用户定义链来进行更复杂的过滤操作。 其次,iptables 还可以修改数据包的头部信息。通过修改数据包的源或目标 IP 地址等信息,我们可以实现网络地址转换(NAT)功能,将私有 IP 地址映射为公共 IP 地址以实现 Internet 访问。此外,还可以通过修改数据包的源或目标端口号来实现端口转发等功能。 最后,iptables 还可以根据一系列规则来重定向数据包。例如,我们可以配置 iptables 将特定端口号的流量重定向到不同的服务器,以实现负载均衡或高可用性。同时,iptables 还支持连接跟踪,可以跟踪连接状态,并根据连接状态来处理数据包,从而提供更高级的安全防护。 在实际应用中,我们可以使用 iptables 命令来创建、修改和删除规则。同时,为了简化配置过程,还可以使用一些图形界面的工具,如 firewalld 和 UFW,来管理iptables。需要注意的是,正确地配置 iptables 规则非常重要,因为不当配置可能会导致网络连接问题或安全漏洞。 总的来说,通过 iptables 的应用,我们能够灵活地设置与管理服务器的防火墙规则,从而保护服务器免受各种网络攻击和恶意流量的侵害。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值