LoginSignup
5
1

More than 1 year has passed since last update.

AngularでModule Federationをやってみよう!!!

Posted at

Module Federationとは

独立してビルド、デプロイされた複数のプロジェクト間でコードを共有できるようにするWebpack5の機能です。中央の「連合体」モジュールを設定し、他のモジュールが接続できるハブとして機能させることができます。連携モジュールは、そのコードの特定の部分を他の連携モジュールに公開し、そのモジュールを自分のプロジェクトの一部であるかのようにインポートして使用することができます。これにより、複数のプロジェクト間で共通のコードを簡単に共有することができ、各プロジェクトでそのコードを複製する必要はありません。by ChatGPT

つまり、独立した別のアプリから、独立したまた別のアプリへ、特定のコンポーネントを公開し、そして取得することが出来るというハチャメチャクールな新技術というわけです!

実際に仕事でModule Federationを使用させていだいたので、最も簡易的な実装を解説できればと思います。
本来ではModule Federationと言うくらいなので、モジュールの公開が一般的なケースなのですが、
今回は比較的あまり解説されていないコンポーネントの公開を取り扱いたいと思います。

要件

Angular 15 (提供側・取得側ともに同じバージョンが好ましい)
@angular-architects/module-federation 15.0.3

手順

  1. 提供側・取得側のAngularプロジェクトを作成します。
  2. 双方のプロジェクトにng add @angular-architects/module-federation@^15.0.3 (ng addが設定してくれるので便利)
  3. Webpack.config.jsが自動的に作成されます。

ここで提供側のwebpack.config.jsをいじります。

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, 'tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
  output: {
    uniqueName: "remote",
    publicPath: "auto",
    scriptType: 'text/javascript'
  },
  optimization: {
    runtimeChunk: false
  },   
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        //library: { type: "module" },

        // For remotes (please adjust)
        name: "remote",
        filename: "remoteEntry.js",
        exposes: {
            './Component': './src/app/remote/remote.component.ts',
        },        
        
        // For hosts (please adjust)
        // remotes: {
        //     "mfe1": "http://localhost:3000/remoteEntry.js",

        // },

        shared: share({
          "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },

          ...sharedMappings.getDescriptors()
        })
        
    }),
    sharedMappings.getPlugin()
  ],
};

ポイントはscriptType: 'text/javascript'の追加とlibrary: { type: "module" }のコメントアウト。

あとは下記のように、提供したいコンポーネントのパスを設定してあげるだけ!(./Component:の部分は自分で決めて良いです)

        name: "remote",
        filename: "remoteEntry.js",
        exposes: {
            './Component': './src/app/remote/remote.component.ts',
        }

これで提供側の設定は終了です。簡単!

次に取得側のwebpack.config.jsをいじります。

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("@angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;

const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
  path.join(__dirname, 'tsconfig.json'),
  [/* mapped paths to share */]);

module.exports = {
  output: {
    uniqueName: "host",
    publicPath: "auto",
    scriptType: 'text/javascript'
  },
  optimization: {
    runtimeChunk: false
  },   
  resolve: {
    alias: {
      ...sharedMappings.getAliases(),
    }
  },
  experiments: {
    outputModule: true
  },
  plugins: [
    new ModuleFederationPlugin({
        //library: { type: "module" },

        // For remotes (please adjust)
        // name: "host",
        // filename: "remoteEntry.js",
        // exposes: {
        //     './Component': './/src/app/app.component.ts',
        // },        
        
        // For hosts (please adjust)
        remotes: {
            "remote": "remote@http://localhost:4200/remoteEntry.js",

        },

        shared: share({
          "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },

          ...sharedMappings.getDescriptors()
        })
        
    }),
    sharedMappings.getPlugin()
  ],
};

提供側と同じように、scriptType: 'text/javascript'の追加とlibrary: { type: "module" }のコメントアウト。

今度は取得する提供名とアドレス(remoteEntry.js)を設定するだけ。

        remotes: {
            "remote": "remote@http://localhost:4200/remoteEntry.js",
        },

最後に実際にコンポーネントを取得し、レンダリングしましょう。

import { loadRemoteModule } from '@angular-architects/module-federation';
import {
  Component,
  ComponentFactoryResolver,
  OnInit,
  ViewContainerRef,
} from '@angular/core';
import { RemoteCompDirective } from './remote-comp.directive';

@Component({
  selector: 'app-root',
  template: ``,
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private viewContainerRef: ViewContainerRef
  ) {}

  ngOnInit(): void {
        loadRemoteModule({
          remoteEntry: 'http://localhost:4200/remoteEntry.js',
          remoteName: 'remote',
          exposedModule: './Component',
        }).then((m) => {
          const { RemoteComponent } = m;
          const componentRef = this.viewContainerRef.createComponent(
            this.componentFactoryResolver.resolveComponentFactory(RemoteComponent)
          );
        });
    }
}

提供されたコンポーネント名を指定すればそのまま取り出せる!素晴らしい!
何より良いのは提供側のコンポーネントの変更が取得側に即座に反映されること。
これは一つのアプリケーションを作りながら同時に別のアプリケーションの機能の開発を可能にする!
こんな簡単に並列開発が出来るのだ!最高!

以上!

質問等、アドバイスなどあればどしどしでおねがいします!では!

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1