モノリスをどのように芋たか。パヌト3、フレヌムなしのフレヌムマネヌゞャヌ

ねえ。では最埌の蚘事、フロント゚ンドアプリケヌションのオヌケストレヌタヌ- Iは、フレヌムマネヌゞャに぀いお話したした。説明されおいる実装は倚くの問題を解決したすが、欠点がありたす。



アプリケヌションがiframeにロヌドされるため、レむアりトに問題が発生し、プラグむンが正しく機胜せず、アプリケヌションずフレヌムマネヌゞャヌのAngularのバヌゞョンが同じであっおも、クラむアントはAngularで2぀のバンドルをダりンロヌドしたす。そしお、2020幎にiframeを䜿甚するこずは悪いマナヌのようです。しかし、フレヌムをあきらめお、すべおのアプリケヌションを1぀のりィンドりにロヌドするずどうなるでしょうか。



これが可胜であるこずが刀明したので、次にそれを実装する方法を説明したす。







可胜な解決策



シングルスパ「フロント゚ンドマむクロサヌビス甚のjavascriptルヌタヌ」-ラむブラリのWebサむトに瀺されおいたす。異なるフレヌムワヌクで蚘述されたアプリケヌションを同じペヌゞで同時に実行できたす。この゜リュヌションは機胜したせんでした。ほずんどの機胜は必芁ありたせんでした。たた、System.jsロヌダヌを䜿甚するず、Webpackを䜿甚しおビルドするずきに問題が発生する堎合がありたす。たた、webpackでモゞュヌルロヌダヌを䜿甚するこずは最善の解決策ではないようです。



Angular芁玠このパッケヌゞを䜿甚するず、AngularコンポヌネントをWebコンポヌネントでラップできたす。アプリケヌション党䜓をラップできたす。次に、叀いブラりザ甚にポリフィルを远加する必芁がありたす。独自のルヌティングを䜿甚しおアプリケヌション党䜓からWebコンポヌネントを䜜成するこずは、むデオロギヌ的に間違った決定のように芋えたす。



フレヌムマネヌゞャヌの実装



䟋を䜿甚しお、フレヌムマネヌゞャでフレヌムなしでアプリケヌションをロヌドする方法を芋おみたしょう。



初期蚭定は次のようになりたす。メむンアプリケヌション-mainがありたす。垞に最初にロヌドされ、それ自䜓の䞭に他のアプリケヌションapp-1およびapp-2をロヌドする必芁がありたす。ng new <app-name>コマンドを䜿甚しお3぀のアプリケヌションを䜜成したしょう。次は、ようにプロキシの蚭定に必芁なアプリケヌションのHTMLずJSファむルがされおいるフォヌムの芁求に送ら/<app-name>/*.js、/<app-name>/*.html、およびメむンアプリケヌションの静力孊は、他のすべおの芁求に送信されたす。



proxy.conf.js
const cfg = [
  {
    context: [
      '/app1/*.js',
      '/app1/*.html'
    ],
    target: 'http://localhost:3001/'
  },
  {
    context: [
      '/app2/*.js',
      '/app2/*.html'
    ],
    target: 'http://localhost:3002/'
  }
];

module.exports = cfg;




アプリケヌションapp-1およびapp-2の堎合、それぞれ、angular.jsonapp1およびapp2でbaseHrefを指定したす。たた、ルヌトコンポヌネントセレクタヌをapp-1ずapp-2に倉曎したす。



これはメむンアプリケヌションがどのように芋えるかです




たず、少なくずも1぀のサブアプリケヌションをロヌドしたしょう。これを行うには、index.htmlで指定されおいるすべおのjsファむルをロヌドする必芁がありたす。



jsファむルのURLを確認したす。index.htmlのhttpリク゚ストを䜜成し、DOMParserを䜿甚しお文字列を解析し、すべおのスクリプトタグを遞択したす。すべおを配列に倉換しお、アドレスの配列にマップしたしょう。この方法で取埗したアドレスにはlocation.originが含たれるため、空の文字列に眮き換えたす。



private getAppHTML(): Observable<string> {
  return this.http.get(`/${this.currentApp}/index.html`, {responseType: 'text'});
}

private getScriptUrls(html: string): string[] {
  const appDocument: Document = new DOMParser().parseFromString(html, 'text/html');
  const scriptElements = appDocument.querySelectorAll('script');

  return Array.from(scriptElements)
    .map(({src}) => src.replace(this.document.location.origin, ''));
}


アドレスがありたす。スクリプトをロヌドする必芁がありたす。

private importJs(url: string): Observable<void> {
  return new Observable(sub => {
    const script = this.document.createElement('script');

    script.src = url;
    script.onload = () => {
      this.document.head.removeChild(script);

      sub.next();
      sub.complete();
    };
    script.onerror = e => {
      sub.error(e);
    };

    this.document.head.appendChild(script);
  });
}


コヌドは、必芁なsrcを含むスクリプト芁玠をDOMに远加し、スクリプトをダりンロヌドした埌、これらの芁玠を削陀したす。これはかなり暙準的な゜リュヌションであり、webpackずsystem.jsぞのロヌドも同様に実装されたす。



スクリプトをロヌドした埌理論的には、組み蟌みアプリケヌションを起動するためのすべおが揃っおいたす。しかし実際には、メむンアプリケヌションの再初期化を取埗したす。ロヌドされたアプリがメむンのアプリず䜕らかの圢で競合しおいるようですが、iframeにロヌドされたずきに発生したせんでした。



Webパックバンドルの読み蟌み



Angularはwebpackを䜿甚しおモゞュヌルをロヌドしたす。暙準構成では、Webpackはコヌドを次のバンドルに分割したす。



  • main.js-すべおのクラむアントコヌド。
  • polyfills.js-ポリフィル;
  • styles.js-スタむル;
  • vendor.js-Angularを含む、アプリケヌションで䜿甚されるすべおのラむブラリ。
  • runtime.js-webpackランタむム;
  • <module-name> .module.js-レむゞヌモゞュヌル。


これらのファむルのいずれかを開くず、最初に次のコヌドが衚瀺されたす。



(window["webpackJsonp"] = window["webpackJsonp"] || []).push([/.../])


そしおruntime.jsでは



var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);


これは次のように機胜したす。バンドルがロヌドされるず、配列webpackJsonpがただ存圚しない堎合は䜜成され、その内容がバンドルにプッシュされたす。webpackランタむムは、この配列のプッシュ機胜をオヌバヌラむドしお、埌で新しいバンドルをアップロヌドできるようにし、配列に既に存圚するすべおのものを凊理したす。



バンドルがロヌドされる順序が重芁にならないように、これはすべお必芁です。



したがっお、2番目のAngularアプリケヌションをロヌドするず、そのモゞュヌルを既存のWebpackランタむムに远加しようずしたす。これにより、せいぜいメむンアプリケヌションが再初期化されたす。



webpackJsonpの名前を倉曎したす



競合を回避するには、webpackJsonp配列の名前を倉曎する必芁がありたす。 Angular CLIは独自のwebpack構成を䜿甚したすが、必芁に応じお拡匵できたす。これを行うには、angular-builders / custom-webpackパッケヌゞをむンストヌルする必芁がありたす



npm i -D @ angle-builders / custom-webpack。



次に、プロゞェクト構成のangular.jsonファむルで、architect.build.builderを@ angle-builders / custom-webpackbrowserに眮き換え、architect.build.optionsに远加したす。



"customWebpackConfig": {
  "path": "./custom-webpack.config.js"
}


たた、これをdevサヌバヌずロヌカルで機胜させるには、architect.serve.builderを@ angle-builders / custom-webpackdev-serverに眮き換える必芁がありたす。



次に、䞊蚘のcustomWebpackConfigで指定されおいるWebpack構成ファむルを䜜成する必芁がありたす。custom -webpack.config.js



カスタム蚭定を定矩したす。詳现に぀いおは、公匏ドキュメントを参照しおください。jsonpFunctionに



興味がありたす。



ロヌドされたすべおのアプリケヌションでこのような構成を蚭定しお、競合を回避できたすその埌も競合が残っおいる堎合は、呪われおいる可胜性がありたす。



module.exports = {
 output: {
   jsonpFunction: Math.random().toString()
 },
};


ここで、䞊蚘の方法ですべおのスクリプトをロヌドしようずするず、゚ラヌが衚瀺されたす。



セレクタヌapp-1はどの芁玠ずも䞀臎したせんでした



アプリケヌションをロヌドする前に、そのルヌト芁玠をDOMに远加する必芁がありたす。



private addAppRootElement(appName: string) {  
  const rootElementSelector = APP_CFG[appName].rootElement;
  this.appRootElement = this.document.createElement(rootElementSelector);
  this.appContainer.nativeElement.appendChild(this.appRootElement);
}


もう䞀床詊しおみたしょう-䞇歳、アプリケヌションがロヌドされたした







アプリケヌションを切り替える



以前のアプリケヌションをDOMから削陀し、アプリケヌションを切り替えるこずができたす。



destroyApp () {
  if (!this.currentApp) return;
  this.appContainer.nativeElement.removeChild(this.appRootElement);
}


ただし、ここには欠陥がありたす。app-1→app-2→app-1に移動するず、app-1アプリケヌションのjsバンドルをリロヌドし、それらのコヌドを実行したす。さらに、以前にロヌドされたアプリケヌションを砎棄しないため、メモリリヌクや䞍芁なリ゜ヌス消費が発生したす。



アプリケヌションバンドルを再ダりンロヌドしない堎合、ブヌトストラッププロセスはそれ自䜓では実行されず、アプリケヌションはロヌドされたせん。ブヌトストラップ起動プロセスをメむンアプリケヌションに委任する必芁がありたす。



これを行うには、ロヌドされたアプリケヌションのmain.tsファむルを曞き盎しおみたしょう。



const BOOTSTRAP_FN_NAME = 'ngBootstrap';
const bootstrapFn = (opts?) => platformBrowserDynamic().bootstrapModule(AppModule, opts);

window[BOOTSTRAP_FN_NAME] = bootstrapFn;


bootstrapModule メ゜ッドはすぐには実行されたせんが、グロヌバル倉数にあるラッパヌ関数に栌玍されたす。メむンアプリケヌションでは、それにアクセスしお、必芁に応じお実行できたす。



アプリケヌションを砎棄しおメモリリヌクを修正するには、ルヌトアプリケヌションモゞュヌルAppModuleのdestroyメ゜ッドを呌び出したす。 platformBrowserDynamic。BootstrapModuleメ゜ッドは、それにリンクを返したす。これは、ラッパヌ関数を意味したす。



this.getBootstrapFn$().subscribe((bootstrapFn: BootstrapFn) => {
  this.zone.runOutsideAngular(() => {
    bootstrapFn().then(m => {
      this.ngModule = m;  //    
    });
  });
});

this.ngModule.destroy(); //   


ルヌトモゞュヌルでdestroyを呌び出した埌、すべおのサヌビスおよびアプリケヌションコンポヌネント実装されおいる堎合のngOnDestroyメ゜ッドが呌び出されたす。



すべおが機胜したす。ただし、ロヌドされたアプリケヌションに遅延モゞュヌルが含たれおいる堎合、それらはロヌドできたせん。







アドレスにアプリケヌションパスがないこずがわかりたす/app2/lazy-lazy-module.jsがあるはずです。この問題を解決するには、メむンアプリケヌションずロヌドされたアプリケヌションのベヌスhrefを同期する必芁がありたす。



private syncBaseHref(appBaseHref: string) {
  const base = this.document.querySelector('base');

  base.href = appBaseHref;
}


これで、すべおが正垞に機胜したす。



結果



メむンアプリケヌションにスクリプトをロヌドする前にconsole.timeを配眮し、メむンアプリケヌションのルヌトコンポヌネントのコンストラクタヌにconsole.timeEndを配眮しお、サブアプリケヌションのロヌドにかかる時間を芋おみたしょう。



app-1およびapp-2アプリケヌションが初めおロヌドされるず、次のようなものが衚瀺され







たす。かなり高速です。ただし、以前にダりンロヌドしたアプリケヌションに戻るず、次の番号が衚瀺







されたす。必芁なすべおのチャンクがすでにメモリにあるため、アプリケヌションは即座にロヌドされたす。ただし、アプリケヌションが砎棄された堎合でも、メモリリヌクが発生する可胜性があるため、未䜿甚のオブゞェクト参照ずサブスクリプションにはさらに泚意する必芁がありたす。



フレヌムなしのフレヌムマネヌゞャヌ



䞊蚘の゜リュヌションは、iframeの有無にかかわらずアプリケヌションのロヌドをサポヌトするフレヌムマネヌゞャヌに実装されおいたす。Tinkoff Businessのすべおのアプリケヌションの玄4分の1がフレヌムなしでロヌドされ、その数は絶えず増加しおいたす。



たた、説明した゜リュヌションのおかげで、Angularず、フレヌムマネヌゞャヌおよびアプリケヌションで䜿甚される䞀般的なラむブラリを操䜜する方法を孊びたした。これにより、読み蟌みず䜜業の速床がさらに向䞊したした。これに぀いおは次の蚘事で説明したす。



サンプルコヌドのあるリポゞトリ



All Articles