Closed10

single-spaについてまとめる

ebi_yuebi_yu

single-spaとは

JavaScriptのマイクロフロントエンドフロントエンドフレームワーク

https://single-spa.js.org/

マイクロフロントエンドとは

  • バックエンドのマイクロサービスの考え方をフロントエンドに流用した概念。
  • 従来のフロントエンドアプリケーションでは、1つのアプリケーションに複数のコンポーネントのコードを含める(モノリシック)
  • そのため、プロジェクトが大きくなるにつれ、 コードの複雑化/バンドルサイズの増大が起こる。

  • マイクロフロントエンドではそれぞれのコンポーネントを、独立した別々のアプリケーションとして作成し、それらをルーティングして1つのページに表示する。
  • コンポーネントごとにプロジェクトを分けれるため、コードの複雑さやバンドルサイズが低下する。

マイクロフロントエンドのメリット

1. 独立性

  • チームは他の部分に影響を与えることなく、自分たちのセクションを更新・デプロイできる。
  • 異なるフレームワークやライブラリを使用できる(別のアプリなので互いに影響しない)。

2. スケーラビリティ

  • アプリケーションを小さな部分に分割することで、大規模なチームでも効率的に作業できる。
  • 各チームが特定の機能やページに集中できるため、生産性が向上する。

3. 保守性とアップデートの容易さ

  • 小さなコードベースは、理解、テスト、デバッグが容易。
  • 新しい技術への移行や既存のコードの更新が、アプリケーション全体に影響を与えずに行える。

4. 再利用性

  • コンポーネントやモジュールを他のプロジェクトやチーム間で再利用できる。

5. 耐障害性

  • 一部のコンポーネントに障害が発生しても、アプリケーション全体が影響を受ける可能性が低い。

マイクロフロントエンドのデメリット

1. 複雑性の増加

  • アプリケーションの異なる部分間での統合と通信の管理が複雑になる場合がある。
  • 複数のチーム間での調整とコミュニケーションが必要。

2. パフォーマンスの懸念

  • 異なるマイクロフロントエンド間でのリソースの重複や非効率なローディングが生じる可能性がある。

3. セキュリティリスク

  • 独立したコンポーネント間でのデータの流れと認証の管理が難しくなる場合がある。

4. 共通機能の重複

  • 各マイクロフロントエンドが独自の依存関係を持つと、共通機能の重複が生じる可能性がある。

5. デプロイメントの複雑化

  • 複数のマイクロフロントエンドのデプロイメントを管理する必要があり、プロセスが複雑になる。

どういうときに使うべきか

大規模なフロントエンドプロジェクトにはマイクロフロントエンドは向いている。
小さなサービスや、開発初期のアプリでは返って邪魔になりそう。

ebi_yuebi_yu

siongle-spaの構成

single-spaでは、マイクロフロントエンドアプリケーションのルーティングやライフサイクルを管理する。
single-spaは以下で構成される。

ルート構成(root-config)

  • single-spaで管理したいマイクロフロントエンドアプリケーションを読み込む。
  • single-spaにマイクロフロントエンドアプリケーションを登録する。
  • single-spaの本体を起動する。

マイクロフロントエンドアプリケーション(application/parcel/utils)

  • single-spaにroot-configにおいて登録されるアプリケーションの本体。
ebi_yuebi_yu

ルート構成(root-config)

root-configではマイクロフロントエンドアプリケーションの読み込みや、依存関係の解決、single-spa本体の開始を行う。
root-configは以下の2つのファイルで構成される。

1. ルート HTML

  • すべての single-spaアプリケーションに共有されるHTMLファイル。
  • SystemJS Import Map によってマイクロフロントエンドアプリケーショやライブラリの読み込みなどを行う。

2. main.ts(main.js)

  • ルート HTMLによって呼び出される。
  • マイクロフロントエンドアプリケーションの登録、single-spa本体の起動を行う。

ルート HTMLの例

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Single-SPA</title>
    <script type="systemjs-importmap">
    {
        "imports": {
            "single-spa": "https://unpkg.com/single-spa/lib/system/single-spa.min.js",
            "vue-app": "https://path.to/vue-app.js",
            "vue-parcel": "https://path.to/vue-parcel.js",
            "vue": "https://unpkg.com/vue@next"
            "react-app": "https://path.to/react-app.js",
            "react": "https://unpkg.com/react@16/umd/react.production.min.js",
            "react-dom": "https://unpkg.com/react-dom@16/umd/react.production.min.js"
        }
    }
    </script>
    <script src="https://unpkg.com/systemjs/dist/system.min.js"></script>
</head>
<body>
    <script src="./main.ts"></script>
</body>
</html>

1. SystemJSライブラリのインポート

2. Import Mapによる依存関係の読み込み

  • script type="systemjs-importmap"Import Mapを読み込み、依存関係をSystemJSで解決する。
  • 動的に読み込みたいライブラリや、マイクロフロントエンドアプリケーションのURLを指定する。

Import Mapについては下記を参考。
https://zenn.dev/link/comments/09ea844d2a9dce

3. main.tsの呼び出し

main.tsの例

import { registerApplication, start } from 'single-spa';

// マイクロフロントエンドアプリケーションの登録
registerApplication({
    name: 'vue-app',
    app: () => System.import('vue-app'),
    activeWhen: ['/vue'],
    customProps: {},
});

//  single-spaの開始
start({
    urlRerouteOnly: true,
});

1. マイクロフロントエンドアプリケーションの登録

  • index.htmlで読み込んだ マイクロフロントエンドアプリケーション(vue-app)をregisterApplicationで読み込む。
// @param name : アプリケーションの名前
// @param app : 登録したいsingle-spaアプリケーションを渡す
// @param activeWhen : 指定したURLにアクセスした時のみ、マイクロフロントエンドアプリケーションが活性化する。
// @params customProps : 任意の値をアプリケーション渡せる。 
registerApplication<{}>(config: RegisterApplicationConfig<{}>): void

2. single-spaの開始

  • single-spaの start() を呼び出すことで、single-spa本体を起動する。
  • vue3を用いる場合はurlRerouteOnly:trueを引数に渡す。
ebi_yuebi_yu

マイクロフロントエンドアプリケーション

single-spaにおいてマイクロフロントエンドアプリケーションとは single-spaによって管理される、個々の独立したフロントエンドアプリケーションである。

マイクロフロントエンドアプリケーションにはApplicationParcelUtilityという三つのタイプが存在する。

トピック Application Parcel Utility
ルーティング ルーティングによる起動制御ができる できない できない
UIレンダリング される される ロジックだけ含めてUIを含めないこともできる
ライフサイクル single-spaで管理されるライフサイクル カスタム管理されたライフサイクル 直接のsingle-spaのライフサイクルなし
いつ使用するか マイクロフロントエンドアプリケーションの作成 複数のマイクロフロントエンドアプリケーション内で共用される コンポーネントを作成したい場合。 ロジックのみをアプリケーション間で共有したい場合

https://single-spa.js.org/docs/module-types

ebi_yuebi_yu

Application

  • applicationroot-configregisterApplciation() によって登録され、特定のURLへのアクセスが起きた際に起動するフロントエンドアプリケーション。
  • applicationにはhtmlファイルが存在しなく、root-configindex.htmlを使用する。
  • ライフサイクル関数によってライフサイクルをフックして、処理を行える。

https://single-spa.js.org/docs/building-applications

applicationの作成 : vue3

  • single-spa-vueというライブラリを使用する。
npm install --save single-spa-vue
  • applciationのエントリーポイントを下記のように設定する。
import './set-public-path';
import { h, createApp } from 'vue';
import singleSpaVue from '../lib/single-spa-vue.js';
import router from './router';
import App from './App.vue';

// singleSpaVueでApp.vueインスタンスをラップする。
const vueLifecycles = singleSpaVue({
  createApp,
  appOptions: {
    render() {
      return h(App, {
        // single-spa props are available on the "this" object. Forward them to your component as needed.
        // https://single-spa.js.org/docs/building-applications#lifecycle-props
        name: this.name,
        mountParcel: this.mountParcel,
        singleSpa: this.singleSpa,
      });
    },
  },
  handleInstance: (app) => {
    app.use(router);
  }
});

// ライフサイクル関数を定義
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;

1. singleSpaVue()の呼び出し

  • singleSpaVue() を用いて、single-spaアプリケーションとしてvueアプリを定義する。
  • vueのレンダー関数(h)に、渡したい引数(props)を渡す。

2. ライフサイクル関数を定義する

  • アプリケーションが起動(bootstrap)マウント(mount)アンマウント(unmount) されたときに実行したい関数をexportする。

3. ビルドする

  • viteやwebpakなどのツールでapplicationをビルドし、その成果物をroot-configにおいてImport Mapから読み込む。

https://single-spa.js.org/docs/ecosystem-vue

applicationの登録/起動

root-configregisterApplciation() によって登録され、特定のURLへのアクセスが起きた際に起動する。

https://zenn.dev/link/comments/48e0e06025588b

ebi_yuebi_yu

Parcel

  • parcelは特定のDOM要素にマウントして起動するアプリケーション。
  • ルーティングでの起動制御はできない。
  • parcelapplicationの子要素として定義することもできる。
    • applicationの子要素として定義した場合、applicationがunmountされると、parcelも自動的にunmountされる。

https://single-spa.js.org/docs/parcels-overview

parcelの作成

applicationの場合と同様の方法で作成できる。

https://zenn.dev/link/comments/9829497ca02111

parcelの登録/起動

applicationの子要素として定義する場合 (vue3 parcel)

// App.vue
<script setup lang="ts">
improt { unmount } from 'vue'
// mountParcelの受け取り
const props = defineProps({
    mountParcel: {
        type: Function,
        required: true,
    },
});

const domElement = document.getElementById('place-in-dom-to-mount-parcel');
const parcelProps = { domElement, customProp1: 'foo' };
// parcelのマウント
const parcel = mountParcel(System.import('vue-parcel'), parcelProps);

// parcelのアンマウント
window.addEventListener('ON_ERROR_CAPTURED', (event: any) => {
    parlce.unmount()
});
</script>

1. mountParcel()の受け取り

  • applicationのエントリーファイルの singleSpaVue() で渡した mountParcel() をApp.vue内で受け取る。

2. parcelのマウント

  • mountParcel() でparcelをマウントする。
  • mountParcel() の引数には以下を渡す。
    • ビルドされたparcelの本体コード
    • マウントしたいhtml要素
    • 渡したいprops

3. parcelのアンマウント

  • mountParcel呼び出し時の戻り値(parcelオブジェクト)から**unmount()**メソッドを呼ぶ
  • unmountを呼ばなくても、親のapplicationがunmountされると、parcelも自動的にunmountされる。
  • 他のparcelオブジェクトに含まれる値は以下を参考
    https://single-spa.js.org/docs/parcels-api/#parcel-object

独立の要素として定義する場合(vue3 parcel)

application内またはroot-configで**mountRootParcel()**を呼ぶ

const parcel = singleSpa.mountRootParcel(System.import('vue-parcel'), {
  prop1: 'value1',
  domElement: document.getElementById('a-div'),
});
  • applicationなどに依存しないので、single-spaクライアントが起動した後ならどこで呼んでも良い
  • 自動でunmountされないので、明示的にunmount()を呼ぶ必要がある。
ebi_yuebi_yu

Utility

  • アプリケーション間で共有できるJavaScriptモジュール
  • UIをレンダリングしないもので、共通のデータフェッチ、ヘルパー関数、設定、ビジネスロジックなどを含むことができる。
  • Import Mapで導入するライブラリ(vue,react)などもUtilityである。
  • 詳しくは、 「SystemJSでのImport mpaの使用例」 参照

https://zenn.dev/link/comments/09ea844d2a9dce

ebi_yuebi_yu

single-spa本体でのライフサイクル管理

  • parcel/applicationごとにbootstrap/mount/unmountなどのイベントが管理されている。
    • これらのイベント発火時にはあらかじめ、parcelやapplicationでexportされた関数がよばれる。
  • single-spa本体においてはルートが変更されるたびに、windowイベントが発火する

single-spa本体で発火するwindowイベント

発火の順番 イベント名 発火条件
1 single-spa:before-app-change
または
single-spa:before-no-app-change
ステータスが変更されるアプリケーションはある場合
2 single-spa:before-routing-event
3 single-spa:before-mount-routing-event
4 single-spa:before-first-mount アプリケーションをマウントするのが初めての場合
5 single-spa:first-mount アプリケーションがマウントされたのが初めての場合
6 single-spa:app-change
または
single-spa:no-app-change
ステータスが変更されたアプリケーションがある場合
7 single-spa:routing-event

https://single-spa.js.org/docs/api/#custom-events

ebi_yuebi_yu

まとめ

  • single-spaはマイクロフロントエンドのためのフレームワーク
    • 複数のフロントエンドアプリケーションを一つのアプリケーション内に表示する。
  • single-spaにおいて、アプリケーションやライブラリはsystemJSで読み込む(ことが推奨)。
  • 読み込んだアプリケーションをsingle-spaに登録される。
  • single-spaは登録されたアプリケーションの起動/停止のライフサイクルや、ルーティングを制御する。
このスクラップは2ヶ月前にクローズされました