2022-02-08 17:58:00 +08:00

297 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 模块联邦
在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成「微前端」架构
在微前端架构中,不同的工程可能出现下面的场景
![image-20210122172549530](http://mdrs.yuanjin.tech/img/20210122172549.png)
这涉及到很多非常棘手的问题:
- 如何避免公共模块重复打包
- 如何将某个项目中一部分模块分享出去,同时还要避免重复打包
- 如何管理依赖的不同版本
- 如何更新模块
- `......`
`webpack5`尝试着通过`模块联邦`来解决此类问题
## 示例
现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块。
<img src="http://mdrs.yuanjin.tech/img/20220208150521.png" alt="image-20220208150515731" style="zoom:50%;" />
### 初始化工程
#### home项目
安装
```shell
# 初始化 package.json
npm init -y
# 安装依赖
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
npm i jquery
```
修改`package.json`
```json
"scripts": {
"build": "webpack",
"dev": "webpack serve"
}
```
配置`webpack.config.js`
```js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: 'source-map',
devServer: {
port: 8080,
},
output: {
clean: true,
},
plugins: [ new HtmlWebpackPlugin() ]
};
```
代码
```js
// src/now.js
import $ from 'jquery';
export default function (container) {
const p = $('<p>').appendTo(container).text(new Date().toLocaleString());
setInterval(function () {
p.text(new Date().toLocaleString());
}, 1000);
}
// src/bootstrap.js
import $ from 'jquery';
import now from './now';
// 生成首页标题
$('<h1>').text('首页').appendTo(document.body);
// 首页中有一个显示当前时间的区域
now($('<div>').appendTo(document.body));
// src/index.js
import('./bootstrap')
```
#### active项目
安装
```shell
# 初始化 package.json
npm init -y
# 安装依赖
npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
npm i jquery
```
修改`package.json`
```json
"scripts": {
"build": "webpack",
"dev": "webpack serve"
}
```
配置`webpack.config.js`
```js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
mode: 'development',
devtool: 'source-map',
devServer: {
port: 3000,
},
output: {
clean: true,
},
plugins: [ new HtmlWebpackPlugin() ]
};
```
代码
```js
// src/news.js
import $ from 'jquery';
export default function (container) {
const ul = $('<ul>').appendTo(container);
let html = '';
for (var i = 1; i <= 20; i++) {
html += `<li>新闻${i}</li>`;
}
ul.html(html);
}
// src/bootstrap.js
import $ from 'jquery';
import news from './news';
// 生成活动页标题
$('<h1>').text('活动页').appendTo(document.body);
// 活动页中有一个新闻列表
news($('<div>').appendTo(document.body));
// src/index.js
import('./bootstrap')
```
### 暴露和引用模块
#### active项目需要使用home项目的now模块
home项目暴露now模块
```js
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 模块联邦的名称
// 该名称将成为一个全部变量,通过该变量将可获取当前联邦的所有暴露模块
name: 'home',
// 模块联邦生成的文件名,全部变量将置入到该文件中
filename: 'home-entry.js',
// 模块联邦暴露的所有模块
exposes: {
// key相对于模块联邦的路径
// 这里的 ./now 将决定该模块的访问路径为 home/now
// value: 模块的具体路径
'./now': './src/now.js',
},
}),
],
};
```
active项目引入now模块
```js
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 远程使用其他项目暴露的模块
remotes: {
// key: 自定义远程暴露的联邦名
// 比如为 abc 则之后引用该联邦的模块则使用 import "abc/模块名"
// value: 模块联邦名@模块联邦访问地址
// 远程访问时,将从下面的地址加载
home: 'home@http://localhost:8080/home-entry.js',
},
}),
],
};
// src/bootstrap.js
// 远程引入时间模块
import now from 'home/now'
now($('<div>').appendTo(document.body));
```
#### home项目需要使用active项目的news模块
active项目暴露news模块
```js
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 模块联邦的名称
// 该名称将成为一个全部变量,通过该变量将可获取当前联邦的所有暴露模块
name: 'active',
// 模块联邦生成的文件名,全部变量将置入到该文件中
filename: 'active-entry.js',
// 模块联邦暴露的所有模块
exposes: {
// key相对于模块联邦的路径
// 这里的 ./news 将决定该模块的访问路径为 active/news
// value: 模块的具体路径
'./news': './src/news.js',
},
}),
],
};
```
home项目引入news模块
```js
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 远程使用其他项目暴露的模块
remotes: {
// key: 自定义远程暴露的联邦名
// 比如为 abc 则之后引用该联邦的模块则使用 import "abc/模块名"
// value: 模块联邦名@模块联邦访问地址
// 远程访问时,将从下面的地址加载
active: 'active@http://localhost:3000/active-entry.js',
}
}),
],
};
// src/bootstrap.js
// 远程引入新闻模块
import news from 'active/news'
news($('<div>').appendTo(document.body));
```
### 处理共享模块
两个项目均使用了jquery为了避免重复可以同时为双方使用`shared`配置共享模块
```js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
// 配置共享模块
shared: {
// jquery为共享模块
jquery: {
singleton: true, // 全局唯一
},
},
}),
],
};
```