JavaScript
angular
webpack
PWA

AppShellモデルを適用してmain.bundleのサイズ削減する

試したこと

  • AppShellモデルでモジュール構成を組み直す。1
  • そのために共有リソースをCoreModuleとSharedModuleに分割する。2
  • main.bundleのファイルサイズ削減を計測する。

環境

  • Angular:5.2.6
  • Angular-CLI:1.7.1

モジュール構成の戦略

AppModuleをAppShellモデルにする

スクリーンショット 2018-03-12 0.07.54.png

AppShellモデルのために、AppModuleは以下の役目だけを持たせます。

  • application shell
  • 各content(LazyLoadingModule)へのルーティング
  • DIコンテナの最上位ルート

これにより初回ロードに必要なmain.bundleは、AppShellだけになります。
アプリ全体だと次のようになります。

スクリーンショット 2018-03-13 15.25.25.png

各contentはLazyLoadingModuleへ回して、できるだけ共有リソースもAppModuleから外すことで、ユーザー作成ソースだけでなくベンダー系ソースも後送りにします。結果を見ると、ベンダー系ソースではAngularMaterialの効果が絶大です。

以上の変更によりmain.bundleがサイズ削減されて起動時間が短縮します。

元の構成

スクリーンショット 2018-03-11 22.22.22.png

  • 共有リソースをSharedModuleに全て突っ込んでいた
  • SharedModuleをAppModule含めたあらゆるモジュールでインポートする
  • 最上位のAppModuleでforRoot()によるシングルトンサービスを生成する
  • Rxはrxjs/add/からインポート

新しい構成

スクリーンショット 2018-03-11 22.19.46.png

  • SharedModuleとCoreModuleに分ける
  • AppModuleにCoreModuleをインポートする
  • 各LazyLoadingModuleにSharedModuleをインポートする
  • Rxをpipe化する

CoreModuleとSharedModuleの分け方

CoreModule

  • ナビゲーション系component
  • ナビゲーション系componentで使うdirective/pipe、materialモジュール
  • アプリ全体で使用するAngularパッケージ
    • BrowserAnimationsModule
    • HttpClientModule
    • ServiceWorkerModule
  • アプリ全体で使用するservice
  • app-routingで使用するguard

SharedModule

  • フィーチャーモジュールで使用するAngularパッケージ
    • CommonModule
    • FormsModule/ReactiveFormsModule
  • フィーチャーモジュールで使用する共有リソース
    • directive
    • pipe
    • guard
    • materialモジュール

共有serviceは全てCoreModuleに入れています。特定のフィーチャーモジュールだけで使うserviceなら特定のフィーチャーモジュールでprovideします(SharedModuleには入れません)。複数モジュールに跨るservideをSharedModuleに入れてしまうと、フィーチャーモジュール毎にコピーを生成します。そうなるとアプリ全体で見た時にシングルトンのサービスにならないため、データの共有できないことに注意です。

バンドルファイルのサイズ変化

サマリー

  • 後送りにした共有リソース分がmain.bundleからcommon.chunkに移動する。
  • --prodビルドのmain.bundleは、--aotビルドでのmain.bundleとvendor.bundleで構成されるが、--aotビルドでは以下の変化となる。
    • SharedModuleに回したユーザー作成リソースがmain.bundleからcommon.chunkに移動する。
    • Angularパッケージ系(FormsModuleやAngularMaterial)のソースがvendor.bundleからcommon.chunkに移動する。
    • RxJSはvendor.bundleのままで移動しない。これはvendor.bundle内のAngularパッケージが使用しているからだと思われる。
  • 微量だが、合計サイズも減った。要因は不明である。

集計方法

  1. 採取用のサンプルソースを作成した。
    • 元の状態はコミットrefactor: divide feature module from app module3である
    • 新しい状態はコミットrefactor: reconstruct module and reorganize rxjs4である
    • フィーチャーモジュールが一つのためcommon.chunkは作成されない。demo.chunkがsharedModuleを内包している。
  2. 上記2つのコミットに対して、--aotビルドと--prodビルドでそれぞれビルド結果を採取した。

--aotビルドでの変化

元の構成でのビルド結果

Time: 12854ms
chunk {demo.module} demo.module.chunk.js, demo.module.chunk.js.map () 51.4 kB  [rendered]
chunk {inline} inline.bundle.js, inline.bundle.js.map (inline) 5.83 kB [entry] [rendered]
chunk {main} main.bundle.js, main.bundle.js.map (main) 61.4 kB [initial] [rendered]
chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 325 kB [initial] [rendered]
chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 63.7 kB [initial] [rendered]
chunk {vendor} vendor.bundle.js, vendor.bundle.js.map (vendor) 3.88 MB [initial] [rendered]

新しい構成でのビルド結果

Time: 12936ms
chunk {demo.module} demo.module.chunk.js, demo.module.chunk.js.map () 2.04 MB  [rendered]
chunk {inline} inline.bundle.js, inline.bundle.js.map (inline) 5.83 kB [entry] [rendered]
chunk {main} main.bundle.js, main.bundle.js.map (main) 30.6 kB [initial] [rendered]
chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 325 kB [initial] [rendered]
chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 63.7 kB [initial] [rendered]
chunk {vendor} vendor.bundle.js, vendor.bundle.js.map (vendor) 1.88 MB [initial] [rendered]
ファイル 元の構成 新しい構成
main.bundle.js 61.4kB 30.6kB
vendor.bundle.js 3.88MB 1.88MB
demo.module.chunk.js 51.4kB 2.04MB
合計 3992.8kB 3950.6kB

特にvendor.bundleが2MBと大きく移動していることがわかる。今回のサンプルソースでは、ほぼAngularMaterialになる。

--prodビルドでの変化

元の構成でのビルド結果

Time: 28226ms
chunk {0} 0.aa68a6ffd1d68364462f.chunk.js () 8.04 kB  [rendered]
chunk {1} main.212ed5e5d1c0616288fd.bundle.js (main) 563 kB [initial] [rendered]
chunk {2} polyfills.57c88da6a6d2a800f78f.bundle.js (polyfills) 126 kB [initial] [rendered]
chunk {3} styles.60f2c673d6a13f4d5d86.bundle.css (styles) 46.8 kB [initial] [rendered]
chunk {4} inline.735dc2ac6d85cc81d4a0.bundle.js (inline) 1.4 kB [entry] [rendered]

新しい構成でのビルド結果

Time: 11336ms
chunk {0} 0.aaac215c03d911a8600c.chunk.js () 213 kB  [rendered]
chunk {1} polyfills.6a4c24b5ca42039de4fd.bundle.js (polyfills) 126 kB [initial] [rendered]
chunk {2} main.4a3d80967dfb1e57f705.bundle.js (main) 326 kB [initial] [rendered]
chunk {3} styles.60f2c673d6a13f4d5d86.bundle.css (styles) 46.8 kB [initial] [rendered]
chunk {4} inline.30a8d86ae20b87488c1f.bundle.js (inline) 1.4 kB [entry] [rendered]
ファイル 元の構成 新しい構成
main.bundle.js 563kB 326kB
0.chunk.js 8.04kB 213kB
合計 571.04kB 539kB

初回ロードで読み込まれるmain.bundleが約2/3に減った。