❝
原文地址:Getting Started With Federated Modules[1], by Jacob Ebey。微信公众号不支持外链添加,点击文末“阅读原文”可查看链接内容。
❞
❝
译注:本文将带大家快速地学会在 webpack 5 进行模块联邦功能的配置。需要说明的是,原文代码有点古旧,翻译的过程中我将代码统一转换成最新的了。
❞
我们要构建的项目
我们将构建两个单独的单页应用程序(SPA),并使用模块联邦功能在运行期间共享组件。
应用程序A将包含一个 SayHelloFromA
组件,该组件将被 Application B 消费,而 Application B 将包含一个 SayHelloFromB
组件,该组件将被 Application A 消费。具体如下:
这种架构将允许每个单页应用程序独立开发和部署,并即时接收来自其他联合应用程序(federated applications)的更新,而无需进行任何部署。
简而言之
你可以在此处找到这个示例的完整源代码:https://github.com/baooab/federated-libraries-get-started[2]
配置环境
首先,让我们配置环境。为了简单起见,我们将使用 yarn mono-repo 结构,但 Module Federation 的理念是允许团队自主操作的,在现实世界中,你的 SPA 很可能是在独立的仓库中,而不是我们这里 mono-repo 结构。
❝
译注:没有安装 yarn 包管理器的同学,可以通过
npm install -g yarn
完成安装。❞
创建一个新项目文件夹,并使用以下 package.json
文件来同时运行两个 SPA:
「package.json」
{
"name": "federation-example",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"dev": "wsrun --parallel dev",
"build": "yarn workspaces run build"
},
"devDependencies": {
"wsrun": "^5.2.4"
}
}
现在我们将创建两个文件夹,用于存放我们的单页应用程序,这些文件夹位于名为 application-a
和application-b
的目录下,分别包含以下 package.json
文件:
「packages/application-a/package.json」
{
"name": "application-a",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode=production"
},
"license": "ISC",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"html-webpack-plugin": "^5.5.1",
"webpack": "^5.83.1",
"webpack-cli": "^5.1.1",
"webpack-dev-server": "^4.15.0"
}
}
「packages/application-b/package.json」
{
"name": "application-b",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "webpack serve",
"build": "webpack --mode=production"
},
"license": "ISC",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/preset-react": "^7.18.6",
"babel-loader": "^9.1.2",
"html-webpack-plugin": "^5.5.1",
"webpack": "^5.83.1",
"webpack-cli": "^5.1.1",
"webpack-dev-server": "^4.15.0"
}
}
两个 package.json
文件只有 name
属性不同,其余全一样。
项目根目录下安装依赖:
# 也会安装 mono-repo 中的依赖
> yarn
编写单页应用程序
接下来是编写我们的 SPA React 应用程序。我们需要在每个项目中创建一个 src
目录,并包含以下文件:
「packages/application-{a,b}/src/index.js」
import('./bootstrap')
「packages/application-{a,b}/src/bootstrap.jsx」
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './app'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
我们还需要在每个包中添加一个 public
目录,包含以下 HTML 模板,承载 SPA 项目,稍后我们还会做一些修改:
「packages/application-{a,b}/public/index.html」
<!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">
</head>
<body>
<div id="root"></div>
</body>
</html>
现在我们可以为每个应用程序实现两个 app.jsx
文件,以容纳我们的共享组件:
「packages/application-a/src/app.jsx」
import React from 'react'
export default function SayHelloFromA() {
return <h1>Hello From Application A!</h1>
}
「packages/application-b/src/app.jsx」
import React from 'react'
export default function SayHelloFromB() {
return <h1>Hello From Application B!</h1>
}
最后,我们为每个应用程序添加配置文件 webpack.config.js
:
「packages/application-{a,b}/webpack.config.js」
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { ModuleFederationPlugin } = require('webpack').container
module.exports = (env, argv) => {
const mode = argv.mode || 'development'
return {
mode,
entry: './src/index',
devServer: {
static: path.join(__dirname, 'dist'),
port: 3001,
},
devtool: 'source-map',
resolve: {
extensions: ['.jsx', '.js', '.json']
},
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react']
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
}
}
在根目录,现在通过运行以下命令,就可以在 http://localhost:3001 和 http://localhost:3002 访问您的两个单页应用:
> yarn dev # 如果报错,执行 npm run dev
配置模块联邦
现在我们有两个独立的单页应用程序正在运行,让我们继续将每个单页应用程序作为联合容器(Federated Container)和消费者(Consumer)。我们通过利用 webpack 5 核心中的新 ModuleFederationPlugin
来实现这一点。
首先,我们将向 Application A 添加 ModuleFederationPlugin
,代码如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
+ const { ModuleFederationPlugin } = require('webpack').container
module.exports = (env, argv) => {
const mode = argv.mode || 'development'
return {
mode,
entry: './src/index',
+ output: {
+ publicPath: 'auto',
+ },
devServer: {
static: path.join(__dirname, 'dist'),
port: 3001,
},
devtool: 'source-map',
resolve: {
extensions: ['.jsx', '.js', '.json']
},
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react']
}
}
]
},
plugins: [
+ new ModuleFederationPlugin({
+ name: 'application_a',
+ filename: 'remoteEntry.js',
+ library: { type: 'var', name: 'application_a' },
+ exposes: {
+ './SayHelloFromA': './src/app'
+ },
+ remotes: {
+ 'application_b': 'application_b'
+ },
+ shared: {
+ react: { singleton: true },
+ 'react-dom': { singleton: true }
+ }
+ }),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
}
}
项目运行期间,指定了 Application A 将其 app
组件作为名为 SayHelloFromA
的联合模块公开给外部;同时,从 application_b
导入时,模块代码都来源于 Application B。
我们对 Application B 执行相同的操作,指定其 app
组件作为名为 SayHelloFromB
的联合模块公开给外部;同时,从 application_a
导入时,模块代码都来源于 Application A。
「packages/application-b/webpack.config.js」
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
+ const { ModuleFederationPlugin } = require('webpack').container
module.exports = (env, argv) => {
const mode = argv.mode || 'development'
return {
mode,
entry: './src/index',
+ output: {
+ publicPath: 'auto',
+ },
devServer: {
static: path.join(__dirname, 'dist'),
port: 3002,
},
devtool: 'source-map',
resolve: {
extensions: ['.jsx', '.js', '.json']
},
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react']
}
}
]
},
plugins: [
+ new ModuleFederationPlugin({
+ name: 'application_b',
+ filename: 'remoteEntry.js',
+ library: { type: 'var', name: 'application_b' },
+ exposes: {
+ './SayHelloFromB': './src/app'
+ },
+ remotes: {
+ 'application_a': 'application_a'
+ },
+ shared: {
+ react: { singleton: true },
+ 'react-dom': { singleton: true }
+ }
+ }),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
}
}
在我们开始使用暴露的组件之前,最后一步是在运行期间,指定期望消费的容器远程入口(Remote Entries for the Containers)。我们只需向希望消费的 HTML 模板中添加一个脚本标签即可。
「packages/application-a/public/index.html」
<head>
<!-- 加载应用程序 B 的远程入口 -->
<script src="http://localhost:3002/remoteEntry.js"></script>
</head>
「packages/application-b/public/index.html」
<head>
<!-- 加载应用程序 A 的远程入口 -->
<script src="http://localhost:3001/remoteEntry.js"></script>
</head>
远程入口文件中是给 webpack 解析单独导入的模块使用的,很小,能避免传输不必要的信息;还负责启用项目间使用的共享库,在这种情况下,当 Application A 请求 Application B 的 SayHelloFromB
组件时,不需要额外加载 Application B 中的 React、ReactDOM 资源了,因为 Application A 中已经有了一份副本。
使用联合组件
现在我们的两个 SPA 应用程序,既是宿主容器(Container Hosts)也是消费者(Consumers),就可以开始使用共享的组件了。在 webpack 配置中,我们已经指定了容器名称为 application_a
和 application_b
,所以我们会从这些容器中导入组件。
从 Application A 开始,在 bootstrap.jsx
文件中可以渲染 SayHelloFromB
组件了:
import React from 'react'
import ReactDOM from 'react-dom/client'
+ import SayHelloFromB from 'application_b/SayHelloFromB'
import App from './app'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
+ <>
<App />
+ <SayHelloFromB />
+ </>
)
Application B 类似,从 application_a
导入组件:
import React from 'react'
import ReactDOM from 'react-dom/client'
+ import SayHelloFromA from 'application_a/SayHelloFromA'
import App from './app'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
+ <>
<App />
+ <SayHelloFromA />
+ </>
)
一些注意事项
查看 Application A 的网络日志,你会发现我们从 Application A 加载了两个文件:remoteEntry.js
文件和包含 SayHelloFromB
组件的 977.js
。
❝
译注:大家本地测试时,跟原文作者这里截图的可能稍有不同。因为本文写作时,作者当时使用的 beta 版本,这块后面有过变动,但不影响大家对此概念的理解,特此说明。
❞
第一次 Application B 时,你会注意到我们已经缓存了 Applicatino B 和 Application A 的 remoteEntrie.js
文件 。
参考资料
Getting Started With Federated Modules: https://module-federation.github.io/blog/get-started
[2]https://github.com/baooab/federated-libraries-get-started: https://github.com/baooab/federated-libraries-get-started
原文始发于微信公众号(写代码的宝哥):webpack 5 模块联邦入门教程
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/244018.html