淺析如何共享模組(npm釋出共享、微前端、模組聯邦)、如何使用Webpack5新特性-模組聯邦的具體流程及其適用場景
一、如何共享模組
1、NPM 方式共享模組
想象一下正常的共享模組方式,就是 NPM。正常的程式碼共享需要將依賴作為 Lib 安裝到專案,進行 Webpack 打包構建再上線。
對於專案 A 與 B,需要共享一個模組時,最常見的辦法就是將該模組抽成通用依賴並分別安裝在各自專案中。
2、微前端方式共享模組
微前端:micro-frontends (MFE) 也是最近比較火的模組共享管理方式,微前端就是要解決多專案並存問題,多專案並存的最大問題就是模組共享,不能有衝突。
3、模組聯邦方式
作為 Webpack5 內建核心特性之一的 Federated Module:
從圖中可以看到,這個方案是直接將一個應用的包應用於另一個應用,同時具備整體應用一起打包的公共依賴抽取能力。
讓應用具備模組化輸出能力,其實開闢了一種新的應用形態,即 “中心應用”,這個中心應用用於線上動態分發 Runtime 子模組,並不直接提供給使用者使用。
二、如何使用Webpack5 新特性模組聯邦
webpack5 引入聯邦模式是為了更好的共享程式碼。 在此之前,我們共享程式碼一般用npm發包來解決。 npm發包需要經歷構建,釋出,引用三階段,而聯邦模組可以直接引用其他應用程式碼,實現熱插拔效果。對比npm的方式更加簡潔、快速、方便。
使用方法:
1、引入遠端js
2、webpack配置
3、模組使用
1、引入遠端 js
假設我們有app1,app2兩個應用,埠分別為3001,3002。 app1應用要想引用app2裡面的js,直接用script標籤即可。
// 例如app1應用裡面index.html引入app2應用remoteEntry.js
<head>
<script src="http://localhost:3002/remoteEntry.js"></script>
</head>
2、webpack配置
app1的webpack配置:
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
//....
plugins: [
new ModuleFederationPlugin({
name: "app1",
library: { type: "var", name: "app1" },
remotes: {
app2: "app2",
},
shared: ["react", "react-dom"],
}),
],
};
對於app2的webpack配置如下
plugins: [
new ModuleFederationPlugin({
name: "app2",
library: { type: "var", name: "app2" },
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/Button",
},
shared: ["react", "react-dom"],
})
],
可以看到app1和app2的配置基本相同,除了app2 多了filename和exposes以外。引數解釋:
name 應用名,全域性唯一,不可衝突。
library。UMD標準匯出,和name保持一致即可。
remotes 宣告需要引用的遠端應用。如上圖app1配置了需要的遠端應用app2.
filename 遠端應用時被其他應用引入的js檔名稱。對應上面的remoteEntry.js
exposes 遠端應用暴露出的模組名。
shared 依賴的包。
1、如果配置了這個屬性。webpack在載入的時候會先判斷本地應用是否存在對應的包,如果不存在,則載入遠端應用的依賴包。
2、以app2來說,因為它是一個遠端應用,配置了["react", "react-dom"] ,而它被app1所消費,所以webpack會先查詢app1是否存在這兩個包,如果不存在就使用app2自帶包。 app1裡面同樣申明瞭這兩個引數,因為app1是本地應用,所以會直接用app1的依賴。
3、模組使用
對於app1/App.js程式碼使用app2的元件,程式碼如下:
import React from "react";
const RemoteButton = React.lazy(() => import("app2/Button"));
const App = () => (
<div>
<h1>Basic Host-Remote</h1>
<h2>App 1</h2>
<React.Suspense fallback="Loading Button">
<RemoteButton />
</React.Suspense>
</div>
);
export default App;
// 具體這一行
// const RemoteButton = React.lazy(() => import("app2/Button"));
使用方式為:import('遠端應用名/暴露的模組名'),對應webpack配置裡面的name和expose。使用方式和引入一個普通非同步元件無差別。
4、應用例項:vue3中應用
(1)案例1 home專案
new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
remotes: {
home: "home@http://localhost:3002/remoteEntry.js",
},
exposes: {
"./Content": "./src/components/Content",
"./Button": "./src/components/Button",
},
}),
(2)案例2 layout專案
new ModuleFederationPlugin({
name: "layout",
filename: "remoteEntry.js",
remotes: {
home: "home@http://localhost:3002/remoteEntry.js",
},
exposes: {},
}),
(3)layout中可以用home專案中的元件
import { createApp, defineAsyncComponent } from "vue";
import Layout from "./Layout.vue";
const Content = defineAsyncComponent(() => import("home/Content"));
const Button = defineAsyncComponent(() => import("home/Button"));
const app = createApp(Layout);
app.component("content-element", Content);
app.component("button-element", Button);
三、適用範圍
1、由於share這個屬性的存在,所以本地應用和遠端應用的技術棧和版本必須相容,統一用同一套。比如js用react,css用sass等。
2、聯邦模組和微前端的關係:
因為expose這個屬性即可以暴露單個元件,也可以把整個應用暴露出去。
同時由於share屬性存在,技術棧必須一致。
所以加上路由,可以用來實現single-spa這種模式的微前端。
3、使用場景:
新建專門的元件應用服務(中心應用)來管理所有元件和應用,其他業務層只需要根據自己業務所需載入對應的元件和功能模組即可。
模組管理統一管理,程式碼質量高,搭建速度快。特別適用矩陣app,或者視覺化頁面搭建等場景。
就是上面所說的:中心應用用於線上動態分發 Runtime 子模組,並不直接提供給使用者使用。
關於如何使用模組聯邦主要參考文章:《Webpack5 新特性模組聯邦介紹和應用 - https://www.haorooms.com/post/webpack5_new_featrue》