续篇:基于 Promise 的动态 Remote,让模块联邦版本化
Webpack 5 增加了一个新的功能 “模块联邦”,它允许多个 webpack 构建一起工作。 从运行时的角度来看,多个构建的模块将表现得像一个巨大的连接模块图。 从开发者的角度来看,模块可以从指定的远程构建中导入,并以最小的限制来使用。
多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。这通常被称作微前端,但并不仅限于此。
Webpack5 模块联邦让 Webpack 达到了线上 Runtime 的效果,让代码直接在项目间利用 CDN 直接共享,不再需要本地安装 Npm 包、构建再发布了!
我们知道 Webpack 可以通过 DLL 或者 Externals 做代码共享时 Common Chunk, 但不同应用和项目间这个任务就变得困难了,我们几乎无法在项目之间做到按需热插拔。
模块联邦可以将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取能力。
以下为案例应用,详解见注释
目录
├── src
│ ├── Home.js
│ └── index.js
└── webpack.config.js
src/Home.js
export default (num) => {
let str = ''
;
for (let i = 0; i < num; i++) {
str += `item ${i}`;
}
str += '';
return str;
};
src/index.js
import Home from './Home';
const home = document.createElement('h1');
home.textContent = '这里是 App1 的 Home 模块:';
document.body.appendChild(home);
document.body.innerHTML += Home(5);
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
devServer: {
port: '3000',
},
plugins: [
new HtmlWebpackPlugin(),
],
};
执行 npx webpack serve
目录
├── src
│ ├── Header.js
│ └── index.js
└── webpack.config.js
src/Header.js
export default () => {
const header = document.createElement('h1');
header.textContent = '这里是app2 的 Header 模块';
return header;
};
src/index.js
import Header from './Header';
const div = document.createElement('div');
div.appendChild(Header());
document.body.appendChild(div);
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
devServer: {
port: '3001',
},
plugins: [
new HtmlWebpackPlugin(),
],
};
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'production',
entry: './src/index.js',
devServer: {
port: '3001',
},
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
// 模块联邦名字,提供给其他模块使用
name: 'app2',
// 提供给外部访问的资源入口
filename: 'App2RemoteEntry.js',
// 引用的外部资源列表
remotes: {},
// 暴露给外部的资源列表
exposes: {
/**
* ./Header 是让外部应用使用时基于这个路径拼接引用路径,如:App2/Header
* ./src/Header.js 是当前应用的要暴露给外部的资源模块路径
*/
'./Header': './src/Header.js',
},
// 共享模块,值当前被 exposes 的模块需要使用的共享模块,如lodash
shared: {},
}),
],
};
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'production',
entry: './src/index.js',
devServer: {
port: '3000',
},
plugins: [
new HtmlWebpackPlugin(),
new ModuleFederationPlugin({
// 模块联邦名字,提供给其他模块使用
name: 'app1',
// 提供给外部访问的资源入口
filename: 'App1RemoteEntry.js',
// 引用的外部资源列表
remotes: {
/**
* App2 引用其他应用模块的资源别名
* app2 是 APP2 的模块联邦名字
* http://localhost:3001 是 APP2 运行的地址
* App2RemoteEntry.js 是 APP2 提供的外部访问的资源名字
* 可以访问到 APP2 通过 exposes 暴露给外部的资源
*/
App2: 'app2@http://localhost:3001/App2RemoteEntry.js',
},
// 暴露给外部的资源列表
exposes: {},
// 共享模块,如lodash
shared: {},
}),
],
};
src/index.js
import Home from './Home';
/**
* 需要异步导入
* App2 为 remotes 中定义的资源别名
* ./Header 为 APP2 exposes 定义的 ./Header
*/
import('App2/Header').then((Header) => {
const body = document.createElement('div');
body.appendChild(Header.default());
document.body.appendChild(body);
const home = document.createElement('h1');
home.textContent = '这里是 App1 的 Home 模块:';
document.body.appendChild(home);
document.body.innerHTML += Home(5);
});
APP1 执行 npx webpack
可以看到 dist/main.js
包含了APP1的Header模块,它依然是通过CDN引入的:
APP1 执行 npx webpack serve
可以看到模块联邦使用成功!验证了模块联邦可以将一个应用的包应用于另一个应用,同时具备整体应用一起打包的公共依赖抽取的能力
试想一下,你有一个组件包通过npm发布后,你的10个业务项目引用这个组件包。当这个组件包更新了版本,你的10个项目想要使用最新功能就必须一一升级版本、编译打包、部署,这很繁琐。但是模块联邦让组件包利用CDN的方式共享给其他项目,这样一来,当你到组件包更新了,你的10个项目中的组件也自然更新了。是不是很香(*^▽^*)
续篇:基于 Promise 的动态 Remote,让模块联邦版本化
源码:https://gitee.com/yanhuakang/webpack-demos/tree/master/advanced/step_13-Module-Feder
参考:webpack 5 模块联邦 Module Federation