マイクロフロントエンド:Webpack 5によるモジュール連合


モジュール連邦とは


それは基本的にJavascriptアーキテクチャです.これは、JavaScriptアプリケーションを動的に別のアプリケーションから別のアプリケーション(別のwebpackビルド)を読み込むことができます.

これは通常、Webpack


Webパックを使用して、生産や開発のためのバンドルを生成します.Webpackは、フォルダを生成するためにdist とファイルmain.js このフォルダ内.これは、通常、フォルダ内にあるJavaScriptコードのすべての結果ですsrcあなたのコードを追加するsrc もっと重いフォルダですmain.js WebPackが生成するファイル.このファイルが重い場合は、ユーザーがあなたのページをロードするために長くかかることを意味し、これはあなたの生産環境とクライアントのブラウザにダウンロードするファイルであることを忘れないでください.
つまり、我々は我々の束のサイズについて気にすることを意味します、しかし、我々はまた、我々のプロジェクトに新しい特徴を加え続けたいです

この問題の解決策はありますか。


そこには、それを破る戦略がありますmain.js ファイルを最初のレンダリングですべてのコードを読み込むのを避けるために小さなファイルのチャンクに.これはコード分割と呼ばれます.https://webpack.js.org/guides/code-splitting/ )
これを達成するためのさまざまなテクニックがあります、1つはあなたのWebPack構成に1つ以上のエントリーポイントを定義しています、しかし、それは若干の落とし穴で来ます、時々、あなたはチャンクの間でモジュールを複製したでしょう、そして、両方のチャンクはこれらのモジュールを含みます、それで、それがあなたのチャンクのサイズを増やすでしょう.
もう一つの人気の、より受け入れられた方法がありますimport() JSで動的なインポートをするためにES提案に従う構文https://github.com/tc39/proposal-dynamic-import )
このアプローチを使うと以下のようになります.
function test() {
  import('./some-file-inside-my-project.js')
    .then(module => module.loadItemsInPage())
    .catch(error => alert('There was an error'))
}
我々は、使用してページに要素を怠惰に読み込むことができますimport() また、これは、要求に応じてロードされる新しいチャンクを作成します
しかし、私があなたに言ったならば、この主を壊すもう一つの方法があります.JSファイルを別のチャンクだけでなく、別のプロジェクトには?

ここでモジュール連盟が来る


モジュール連合を使用すると、リモートのWebパックビルドをアプリケーションにインポートできます.現在、これらのチャンクをインポートすることができますが、同じプロジェクトから来る必要があります.さて、別の起源からこれらのチャンク(Webpackビルド)を持つことができます.

モジュール同盟


これが何であるかを説明するために、Webpack設定のコードサンプルをModuleFederationPlugin そして、若干の反応.JSコード
このために、我々は現在バージョンベータ版であるWebpack 5を使用します.これがpackage.json ファイルは以下のようになります:
// package.json (fragment)

...

  "scripts": {
   "start": "webpack-dev-server --open",
   "build": "webpack --mode production"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "7.10.3",
    "@babel/preset-react": "7.10.1",
    "babel-loader": "8.1.0",
    "html-webpack-plugin": "^4.3.0",
    "webpack": "5.0.0-beta.24",
    "webpack-cli": "3.3.11",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }

...
我々は、反応アプリケーションのための基本的なセットアップを作成するすべてのWebpackモジュールが含まれている
これがwebpack.config.js 今までのルックス
// webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: './src/index',
  mode: 'development',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 3000,
  },
    output: {
    publicPath: "http://localhost:3000/",
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-react'],
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};
これは、Webpack
プロジェクトに反応コンポーネントを追加しましょう.
// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

function App() {
  return (
    <h1>Hello from React component</h1>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));
この時点で、このプロジェクトを実行すると、「反応コンポーネントからこんにちは」というメッセージが表示されます.今まで、ここに新しい何もない.
このプロジェクトまでのコードはこちらhttps://github.com/brandonvilla21/module-federation/tree/initial-project

第二のプロジェクトの作成


さて、我々は同じプロジェクトで2番目のプロジェクトを作成しますpackage.json Webpackの設定の下にいくつかの違いがあります.
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

// Import Plugin
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/index',
  mode: 'development',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    // Change port to 3001
    port: 3001,
  },
    output: {
    publicPath: "http://localhost:3001/",
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: ['@babel/preset-react'],
        },
      },
    ],
  },
  plugins: [
    // Use Plugin
    new ModuleFederationPlugin({
      name: 'app2',
      library: { type: 'var', name: 'app2' },
      filename: 'remoteEntry.js',
      exposes: {
        // expose each component you want 
        './Counter': './src/components/Counter',
      },
      shared: ['react', 'react-dom'],
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};
私たちは、構成の上にModulefFederationPluginを輸入しています
const { ModuleFederationPlugin } = require('webpack').container;
また、両方のアプリケーションを同時に実行するので、ポートを変更する必要があります
port: 3001,
そして、これはプラグインの設定がどのように見えるかです.
new ModuleFederationPlugin({
  name: 'app2', // We need to give it a name as an identifier
  library: { type: 'var', name: 'app2' },
  filename: 'remoteEntry.js', // Name of the remote file
  exposes: {
    './Counter': './src/components/Counter', // expose each component you want 
  },
  shared: ['react', 'react-dom'], // If the consumer application already has these libraries loaded, it won't load them twice
}),
これは、この2番目のプロジェクトの依存関係を最初のものと共有するための構成の主要な部分です.
最初のアプリケーションからこの2番目のアプリケーションを消費する前に、「カウンター」コンポーネントを作成します.
// src/components/Counter.js

import React from 'react'

function Counter(props) {
  return (
     <>
       <p>Count: {props.count}</p>
       <button onClick={props.onIncrement}>Increment</button>
       <button onClick={props.onDecrement}>Decrement</button>
     </>
  )
}

export default Counter
これは非常に一般的な例ですが、ここでのポイントは、このコンポーネントをどのように使用して、いくつかの小道具を
この時点で2番目のアプリを実行しようとすると、基本を追加するindex.js 最初のアプリケーションで行ったように、次のようなメッセージが表示されます.
Uncaught Error: Shared module is not available for eager consumption
エラーとして、アプリケーションを熱心に実行しています.アプリケーションを読み込むための非同期方法を提供するには、次の手順を実行します.
クリエイトアbootstrap.js ファイルからすべてのコードを移動index.js このファイルに
// src/bootstrap.js

import React from 'react';
import ReactDOM from 'react-dom';

function App() {
  return <h1>Hello from second app</h1>;
}

ReactDOM.render(<App />, document.getElementById('root'));
そしてインポートindex.js このようにimport() ここの構文
// src/index.js

import('./bootstrap')
今、この時点で2番目のプロジェクトを実行する場合は、メッセージを

最初のプロジェクトへのカウンタコンポーネントのインポート


我々は、更新する必要がありますwebpack.config.js 最初に、第2のアプリケーションからカウンターコンポーネントを消費するために
// webpack.config.js (fragment)

...
plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      library: { type: 'var', name: 'app1' },
      remotes: {
        app2: 'app2', // Add remote (Second project)
      },
      shared: ['react', 'react-dom'],
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
...
このWebPackの設定と他のexpose and remote . 最初のアプリでは、我々は我々は最初のアプリから取るので、このアプリでは、リモートアプリケーションの名前を指定するコンポーネントを公開します
また、指定する必要がありますremoteEntry.js リモートホストからのファイル:
<!-- public/index.html (fragment)-->

...
<body>
  <div id="root"></div>
  <script src="http://localhost:3001/remoteEntry.js"></script>
</body>
...

リモートプロジェクトからのコンポーネントのインポートのインポート


さて、2番目のプロジェクトからカウンターコンポーネントを最初のプロジェクトに使用するときです.
// src/bootstrap.js

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

const Counter = React.lazy(() => import('app2/Counter'));

function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <h1>Hello from React component</h1>
      <React.Suspense fallback='Loading Counter...'>
        <Counter
          count={count}
          onIncrement={() => setCount(count + 1)}
          onDecrement={() => setCount(count - 1)}
        />
      </React.Suspense>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
カウンタコンポーネントを怠惰にロードする必要があります.そして、我々は、フォールバックでコンポーネントをロードするために、反応サスペンスを使用することができます
それだ!最初のプロジェクトからカウンターコンポーネントを読み込むことができます

結論


あなたのアプリケーションにリモートWebpackビルドを読み込む可能性は、新しいフロントエンドアーキテクチャを作成するための可能性の新しい世界を開きます.作成可能です.

マイクロフロント


JavaScriptの別々のバンドルを別々のプロジェクトにすることができますので、それぞれのアプリケーションに対して別々のビルドプロセスを持つ可能性があります.
あなたは、単一のウェブサイトの感覚と完全に独立したアプリケーションを持つことができます.これはビッグチームがフロントエンドからバックエンドのチームに垂直にスケールされる小さく、より効率的なチームに分解することができます.
このように、我々は新しい特徴を届けるために他に依存しない自治チームを持ちます
このように表現できます:

Source Image

ランタイムでの設計システム


現在、ビルド時間(NPM/ヤーンパッケージ、Githubパッケージ、Bit . dev)でデザインシステムを実装するための複数の方法がありますが、これはいくつかのプロジェクトの問題を表す可能性があります.あなたのデザインシステムからいくつかのコンポーネントを更新する必要があるときはいつでも、あなたのアプリケーションを再構築して、あなたのデザインシステムの最新バージョンを生産するために再びそれを展開しなければなりません.
ランタイムでデザインシステムを使用すると、別の起源とランタイムでコンポーネントを取得するため、アプリケーション全体のビルドと再配備プロセスを実行せずに、アプリケーションに最新のバージョンを取得できます.
これら2つは、連合モジュールの可能性のほんの一部です.

完全な例のリポジトリ


github.com/brandonvilla21/module-federation