マイクロフロントエンド(singleSpa+React)試遊


前言


私达のチームは1つのXXシステムをして、技术のスタックはReactで、今このシステムは日に日に膨大で、开発とメンテナンスのコストは増大して、しかも毎回プロジェクト全体を一绪に梱包しなければならなくて、时间と労力がかかります.考慮した結果,複数のプロジェクトに分割し,それらを組み合わせて完全なシステムにすることを決定し,マイクロフロントエンドアーキテクチャは非常に良い選択である.
マイクロフロントエンドの差は多くありません.以下のメリットがあります.
  • 単一プロジェクトメンテナンス:例えば 単一プロジェクトを引き出して1つのプロジェクトを形成し、それは1つのグループによって単独でメンテナンスすることができ、良好なデカップリング
  • を実現する.
  • 複雑度の低下:集積式の膨大なシステム全体で開発する必要がなく、巨大なコード量を避け、開発時のコンパイル速度が速く、開発効率を高める
  • フォールトトレランス:個別プロジェクトでエラーが発生してもシステム全体に影響しません
  • 技術スタック柔軟性:vue、react、angularなど他のフロントエンド技術スタックを含めて使用でき、vueを学ぶ必要はありませんreact
  • 私たちにとって最大のメリットは です.

    ディスプレイ


    UIサンプル


    マイクロフロントエンド全体を2つの部分に分けます.
  • メインプロジェクト(Main):赤枠部分は、プロジェクト全体の親として、メニューモジュール、ヘッダモジュール
  • を展示する
  • サブプロジェクト(Sub-apps):青い枠の部分で、サブプロジェクトの役割は具体的な業務展示
  • です.

    動図展示

    /app1/xxx/app2/xxxを含むアドレスバーの変化に注意してください.一見、これは1つのプロジェクトの2つのページの切り替えであり、実際には2つの独立したプロジェクトから来ており、app 1とapp 2は異なるgit倉庫から来ています.

    マイクロフロントエンドアーキテクチャ図


    全体のプロセスは、ユーザーがindexにアクセスすることです.htmlは、モジュールローダJsを実行します.ローダはシステム全体のプロファイル(project.config)に基づいて各プロジェクトを登録します.システムはまずメインプロジェクト(Main)をロードし、それからルーティング接頭辞に基づいて対応するサブプロジェクトを動的にロードします.
    私たちのこのアーキテクチャもネット上の多くの良い文章を参考にして、その中の核心の文章は参考にすることができますhttps://alili.tech/archive/11...

    プロジェクトについてconfig


    だいたい次のようです
    [
     {
        isBase: false,
        name: 'app1',
        version: '1.0.0',
        //                 
        hashPrefix: '/app1',
        //    
        entry: 'http://www.xxxx.com/app1/dist/singleSpaEntry.js',
        //  Store
        store: 'http://www.xxxx.com/main/dist/store.js'
      }
      ......
    ]

    テクノロジーの詳細


    single-spa


    マイクロフロントエンドを実現する倉庫を探して、比較してsingle-spaを使うことにしました.
    我々のテクノロジースタックはreactであり、サブプロジェクトのエントリでsingle-spa-reactを使用して構築する必要があります.キーコードは以下の通りです.
    import singleSpaReact from 'single-spa-react';
    
    const reactLifecycles = singleSpaReact({
      React,
      ReactDOM,
      rootComponent: Root,
      domElementGetter
    });
    
    export function bootstrap(props) {
      return reactLifecycles.bootstrap(props);
    }
    
    export function mount(props) {
      return reactLifecycles.mount(props);
    }
    
    export function unmount(props) {
      return reactLifecycles.unmount(props);
    }

    vueを使用する場合はsingle-spa-vueを使用できます
    次に、システム・エントリ・ファイルにすべてのアイテムを登録します.
    import * as singleSpa from 'single-spa';
    
    singleSpa.registerApplication(
        'app1',
        () => SystemJS.import('app1-entry.js'),
        () => location.hash.startsWith(`#/app1`),
        props
      );

    詳細はsingle-spa公式サイトを参照してくださいhttps://single-spa.js.orgここには多くの例がある.

    WebpackとSystemJs


    私たちが使用しているlernaは、すべてのプロジェクトの依存パッケージを統一的に管理し、すべての依存パッケージのバージョンを統一することで、メンテナンスが非常に便利です.
    Webpackのdll機能を使用して、react、react-dom、react-router、mobxなど、すべてのプロジェクトの共通依存パッケージを抽出します.
    プロジェクトのダイナミックロードを容易にするために、私たちもネット上の大物の考えを参考にして、systemjsを使用しましたが、私たちは0.20.19バージョンを使用しています.systemjsに合わせて、WebpackでlibraryTargetを変更する必要があります.
    output: {
        publicPath: 'http://www.xxxxx.com/',
        filename: '[name].js',
        chunkFilename: '[name].[chunkhash:8].js',
        path: path.resolve(__dirname, 'release'),
        libraryTarget: 'amd', //        amd    
        library: 'app1'
      },

    私たちはumd仕様を使用していないし、systemjsのImport Maps機能も使用していないし、直接projectを通過しています.configは、モジュールエントリを動的にロードします.

    app間通信


    これについてもいくつかの大物の案を見て、たぶんすべてのプロジェクトにstoreがあり、登録入口ですべてのstoreをキューに入れ、storeの状態を更新する必要がある場合、dispatchを呼び出してすべてのstoreを同期します.
    私のやり方は伝統的な1ページの応用と同じで、1つのシステムは1つのトップストレージしかないはずです.トップストレージに保存されているのは一般的にシステム全体の共通状態、例えばメニュー、ユーザー情報などです.私はそれをMainプロジェクトに入れましたが、パッケージするとき、このストレージは単独で抽出されました.
    entry: {
        singleSpaEntry: './src/singleSpaEntry.js',
        store: './src/store' //      
      },

    このStoreは、登録時に各アイテムに転送されます.
    //  Store
    const mainStore = await SystemJS.import(storeURL);
    
    singleSpa.registerApplication(
        'app1',
        () => SystemJS.import('http://www.x.com/app1/entry.js'),
        hashPrefix('/app1'),
        { mainStore }
    );
    singleSpa.registerApplication(
        'app2',
        () => SystemJS.import('http://www.x.com/app2/entry.js'),
        hashPrefix('/app1'),
        { mainStore }
    );

    これにより、このStoreを1つだけ管理することができ、非常に便利です.注意:ステータス管理としてMobxを使用しています

    フロントエンドの導入


    私たちの配置方法は非常に簡単で、私は自分でwebpackプラグインを書いて、パッケージされたdistをOSSに伝えてからプロジェクト情報をサービス側に伝えて、サービス側は私が伝えたプロジェクト情報に基づいてprojectに組織しました.config、ユーザーはindexにアクセスします.htmlでプロジェクトが取得されます.configでは、single-spaが構成に基づいてすべてのアイテムを登録し、ルーティングに基づいて対応するアイテムエントリファイルjsファイルを引き出します.

    サブプロジェクトのマウントDOMをMainプロジェクトに入れる


    私たちのニーズはMainがプロジェクト全体のLayoutとして、その中性子プロジェクトのマウントDomもMainプロジェクトにあります.これはMainプロジェクトが完全にレンダリングされてから、サブプロジェクトをマウントする必要があります.私はネット上のいくつかのマイクロフロントエンドの実現を参考にして、domElementGetterの方法を参考にしました.
    function domElementGetter() {
      let el = document.getElementById('sub-module-wrap');
      if (!el) {
        el = document.createElement('div');
        el.id = 'sub-module-wrap';
      }
      let timer = null;
      timer = setInterval(() => {
        if (document.querySelector('#content-wrap')) {
          document.querySelector('#content-wrap').appendChild(el);
          clearInterval(timer);
        }
      }, 100);
    
      return el;
    }

    demo


    demoアドレス:https://github.com/Vibing/mic...

    終わりの言葉


    これは私たちが初めてマイクロフロントエンドを游ぶので、完璧ではないところがたくさんあるかもしれませんが、皆さん、どうぞお許しください.