注意: まだHEADにすらマージされていない機能です。
僕の現状の理解で書いてます。細かいニュアンスは見落としてるかも。
今の課題
- 異なるチームで、異なるビルドプロセスのものを統合するような巨大なフロントエンド、いわゆるマイクロなフロントエンドやってると、同じようなライブラリが内部的に重複するよね
- webpack build (chunk) 間で、そういう自明なもののを重複を省いたり、一方向ではなく、相互に読み出せるようにしたいよね
ScriptedAlchemy氏の提案
異なるWebpackビルド同士が連携する、Federation という概念を作る。
- Host と Remote という概念を追加する。既存のものは Host となる(?)。 Remote は、それぞれに要求する chunk (react, vue など)と、他に外側に提供するインターフェースを明示する。
- (optimazation をしないビルドの場合?) 読み込まれるライブラリの内部に、どの node_module から構成されたかの chunk.id を、ヒントとして残す。これらは tree shake できるように、ESM の方式でインターフェースを残す。
- どのような経路から読み込まれたとしても、複数の webpack build から同一 chunk.id のものが読み込まれた時、インスタンスは共有される(注: ちょっと自信がない。ランタイムの話か、ビルドステップの話なのか)
- それぞれのビルドチャンクごとに、異なる publicPath を持てる
個人的には、異なる publicPath というのが大きい気がする。様々なホストから提供されるアプリ同士を連携できるようになる)ので。
npm わかってる人向けの要約
つまりは webpack federation を使えば、 webpack build (chunk) の水準で、peerDependencies 的な振る舞いが設定できるという話だと自分は理解した。
実際に動いているところ
これを clone して動かしてみるのが一番理解が早かった。
// plugins
new ModuleFederationPlugin({
name: "app_01",
library: { type: "var", name: "app_01" },
filename: "remoteEntry.js",
remotes: {
app_02: "app_02",
app_03: "app_03"
},
exposes: {
SideNav: "./src/SideNav",
Page: "./src/Page"
},
shared: ["react", "react-dom", "@material-ui/core", "react-router-dom"]
}),
// plugins
new ModuleFederationPlugin({
name: "app_02",
library: { type: "var", name: "app_02" },
filename: "remoteEntry.js",
remotes: {
app_01: "app_01",
app_03: "app_03"
},
exposes: {
Dialog: "./src/Dialog",
Tabs: "./src/Tabs"
},
shared: ["react", "react-dom", "@material-ui/core", "react-router-dom"]
}),
new ModuleFederationPlugin({
name: "app_03",
library: { type: "var", name: "app_03" },
filename: "remoteEntry.js",
remotes: {
app_01: "app_01"
},
exposes: {
Button: "./src/Button"
},
shared: ["react", "react-dom"]
}),
これを動かす packages/app-01 の HTML
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://localhost:3002/remoteEntry.js"></script>
<script src="http://localhost:3003/remoteEntry.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
<!-- html-plugin によって、app-01 の remoteEntry も挿入される -->
これで、相互に読み出しながら、react 等のインスタンスを共有しながら実行される
まだちょっとよくわかってないところ
- ビルド時の話なのか、ランタイムのときの話なのかが、自分はまだ混乱してるかも
- だれが明示的にチャンクを持つのか、指定できる? 複数にまたがる vendor chunk 的なものを作れるのか?
利点
マイクロフロントエンドのパフォーマンス上のメリットは↑の通り。
それ以外に、もっとエコシステム的な観点がある気がしていて、現在のフロントエンドライブラリは、 npm パッケージとして読み込み、特定のローダーで展開することを期待するようなライブラリ(style-loader, worker-loaderを前提とするようなやつ。例えば monaco-editor)があることが多いが、それが既存の設定とバッティングしたり、期待するwebpackのバージョンと一致しなくなったりして、動かせないことがある。
webpack federation が普及すれば、一つの振る舞いをする webpackのビルドチャンクが一塊になることで、他のwebpack設定から読み込ませつつ、 他の loader と干渉しないようなものとして、ビルドを提供できる。
つまりは、一つの振る舞いを持つUIコンポーネントという単位で、ビルド済みのものを配布しやすくなる。作者はそういうエコシステムを思い描いている気がする。
参考
- https://itnext.io/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669
- https://github.com/webpack/webpack/issues/10352
ここまで理解したが、ちょっとまだ自分もふわっとしてるので、コメントなどで教えてほしいです。