カテゴリー
 [Angular] ビルド後のファイルの出力サイズを試行錯誤しつつ減らしてng buildのコマンドオプションを最適化する方法を考える  
※ 当ページには【広告/PR】を含む場合があります。
2020/04/18
 2022/03/21 
Angularプロジェクトをビルドを最適化する際に、ビルドオプション
--build-optimizer=true --aot=trueAngularのビルドオプション自体は
angular.json旧angular-cli.jsonconfigurations通常は意識せずにAngularアプリをビルドできているのはこのためですが、このビルドオプションは直接コマンド引数として渡すこともできます。
今回は
とりあえず、Brotli などの圧縮してれるライブラリは使わず、無駄にimportしまくっているモジュールを削ったり、最適化をします。
なおこの記事の投稿時はAngular v8でしたが現行のv13までにビルドオプションに若干の変更があったので、記事内容もすこし更新しております。
ビルドオプションの詳細は公式のリファレンスからご確認ください。
TL;DR
今回の記事は、
Angularプログラマーが意識して積極的にコード最適化する必要はない結論としては、苦労して小技を繰り出すことで数十kBの程度はスリム化出来る可能性があるかもしれませんが、コスト対効果を考えると暗黙的にコンパイラーにお任せしといた方が幸せになれるはずです。
個人的にビルドオプションを弄った限りで一番スリムでサイズも出力もスッキリしたのが、
            $ ng b --configuration=production --build-optimizer=true --aot=true --output-hashing=none
        辺りが最も出力サイズが抑えられると思います。
では以下、具体的なビルドサイズの軽減のための実験の内容を一ステップづつ継ぎ足しながら検証してみます。
ng build 後の生成物に付くハッシュ値を消す方法
まず最初に、直接はビルド後の出力ファイルサイズを低減に関係はないですが、主力されるファイルについてまわるハッシュ値の消し方を説明しましょう。
Angular Project の設定そのままでprodフラグでビルドすると、出力させるファイルには、それぞれハッシュ値が以下のように自動で付与されます。
            $ ng build --configuration production --build-optimizer true --aot true
Date: 2019-06-24T07:45:43.041Z
Hash: 4470e19ad1e0b3dfa8cb
Time: 112933ms
chunk {0} runtime.26209474bfa8dc87a77c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.bda95d5896422d031328.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.a89343786a3a7a06abc2.js (main) 1.24 MB [initial] [rendered]
chunk {3} polyfills.7dc9f29e95cefa5190c3.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.602cfcb7b605501de325.css (styles) 84.8 kB [initial] [rendered]
        このハッシュ値は、例えば複数人で開発するなど、ビルドバーションをキチンと管理することを要求されている場合には便利ではあります。
個人でしか更新しないようなプロジェクトでは、むしろ無い方がスッキリしていいんじゃ無いかと個人的におもいます。
そんな時には、
--output-hasing none            $ ng build --configuration production --build-optimizer true --aot true --output-hashing none
Date: 2019-06-24T08:33:07.003Z
Hash: 5125e69c6cc20452a05c
Time: 67533ms
chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.js (main) 1.24 MB [initial] [rendered]
chunk {3} polyfills.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.css (styles) 84.8 kB [initial] [rendered]
        心なしか、ハッシュ値を与えないぶん、ビルド時間が早くなったような…たぶん気のせいでしょう。
ソースコードまわりを手動でスリム化
無駄だとは分かっていても、ソースコード内で使われていないまま放置されているライブラリなどをお手手で消して回ります。
個人的にコンパイラとかの予備知識など全くなくお恥ずかしいことですが、ソースコードに散らばった
はぐれライブラリ結果は分かっているのですが、とりあえず実験ということで、無駄に放置したリソースを見つけて手動削除を繰り返します。
削除対象1: インポートしながらも未使用のライブラリ
例えば手元の
app.module.ts            import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
                          ^^^^^^^^^^^^^^^^^
// どこにも使われて無いまま残るやつ
        みたいな、日頃から整理するのも面倒で放置したものがたくさんあります。
こいつらを根こそぎインポートのスコープから削除し、ビルドをしてみると…
            chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.js (main) 1.24 MB [initial] [rendered]
chunk {3} polyfills.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.css (styles) 84.1 kB [initial] [rendered]
        出力結果に変わる筈もなく…次いってみましょう。
削除対象2: 無駄なコンポーネント
もしもの為に、サードパーティのjwtを受け取るように作っていた
Callback            // import { CallbackComponent } from './components/callback/callback.component';
@NgModule({
    declarations: [
        AppComponent,
        HeaderToolbarComponent,
        // CallbackComponent,
        // ...
        Routerからも弾きます。
            // ...
// import { CallbackComponent } from './components/callback/callback.component';
const routes: Routes = [
    // ...
    // {
    //     path: 'callback',
    //     component: CallbackComponent,
    // },
        どうやらCallbackコンポーネントごと消さないとビルド出来ないようで、根こそぎ削除して、
            chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.js (main) 1.24 MB [initial] [rendered]
chunk {3} polyfills.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.css (styles) 84.1 kB [initial] [rendered]
        どうやら、中身カスカスのコンポーネントは、いてもいなくてもサイズ変わりません!
削除対象3: 無駄にクラスに実装しているインターフェイス
ここもかなりのリソースの無駄づかいをしております…
まず、あとで使うつもりであった
OnInitOnDestoryAfterViewInit例えば、以下のように、何もさせない
AfterViewInit            import { Component, OnInit, AfterViewInit } from '@angular/core';
...
@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, AfterViewInit {
    title = 'tacosKingdom';
    opened: boolean;
    constructor(
        @Inject(PLATFORM_ID) private platformId: any,
        @Inject(DOCUMENT) private document: any
    ) {
    }
    public ngOnInit(): void {
        if (!isPlatformBrowser(this.platformId)) {
            const bases = this.document.getElementsByTagName('base');
            if (bases.length > 0) {
                bases[0].setAttribute('href', environment.baseHref);
            }
        }
    }
    public ngAfterViewInit() {}
    public innerToggle() {}
}
        とにかく使ってないものは剥ぎとります。
            chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.js (main) 1.24 MB [initial] [rendered]
chunk {3} polyfills.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.css (styles) 84.1 kB [initial] [rendered]
        ...変化なし!
ここまで記事を読んでただいて、申し訳ないですが、なんと生半可にコードに残ったゴミを消しただけでは、何も変わらないということが分かりました…
逆に言えば、この程度の修正でできる最適化って、コンパイラがほぼほぼ全て全てやってくれてるんですね。
次に巨大なライブラリとされる
@angular/core@angular/commonrxjs削除対象4: 重い外部ライブラリ(Angular Materialなど)
Angular Material一方、デメリットとして、モジュールを読み込めば読み込むほど、最終的なビルドサイズが肥大化していきます。
そんな
Angular Material使わないコンポーネントモジュールは消していきます。
            import {
    // MatSidenavModule, <-- Useless
    // MatCheckboxModule, <-- Useless
    MatButtonModule,
    MatIconModule,
    MatCardModule,
    MatFormFieldModule,
    MatInputModule,
    MatToolbarModule,
    // MatDialogModule, <-- Useless
    // MatGridListModule, <-- Useless
    MatListModule
} from '@angular/material';
@NgModule({
    ...
    imports: [
        ...
        MatButtonModule,
        // MatCheckboxModule, <-- Useless
        // MatDialogModule, <-- Useless
        MatCardModule,
        // MatSidenavModule, <-- Useless
        MatIconModule,
        MatFormFieldModule,
        MatInputModule,
        MatToolbarModule,
        // MatGridListModule, <-- Useless
        MatListModule,
        結果は以下でmain.jsは0.06MB減量しました。
            chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.js (main) 1.18 MB [initial] [rendered]
chunk {3} polyfills.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.css (styles) 84.1 kB [initial] [rendered]
        削除対象5: 重い標準ライブラリ(ReactiveFormsModuleなど)
Angular標準のモジュールなどでも、
ReactiveFormsModuleそんな無駄なモジュールがリソースを食っているかも...と疑って、これらを必要なモジュール以外削除することにしました。
            import { ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
...
@NgModule({
    ...
    imports: [
        ...
        // BrowserAnimationsModule, <-- Useless
        // ReactiveFormsModule, <-- Useless
        // HttpClientModule, <-- Useless
        ...
        その結果、
            chunk {0} runtime.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.js (main) 1.18 MB [initial] [rendered]
chunk {3} polyfills.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.css (styles) 84.1 kB [initial] [rendered]
        プロジェクトのコンポーネント、サービス、モジュールを綺麗にしたところで、全くスリム化する気配なし!
なんと全然…変わりませんでした…
これらの脇役モジュールって、コンパイラが賢く圧縮していたのでしょうか。
以上手作業でのダイエットは、ここいらで潮時かもしれません。
コンパイラの有能さ恐るべし。
【おまけ】 --optimizationオプションに関して
build            --buildOptimizer:
    --aotオプションが有効化していた場合に、
    内部の@angular-devkit/build-optimizerを利用して最適化させます
--optimization:
    ビルド生成物に対し最適化させます
        安直に考えると、両方ビルドオプションに指定しとけば、ビルド後の生成コードが最適化されるのでは...と期待しておりました。
実際やってみると
buildOptimizeroptimizationやるならどちらか一つの最適化オプションを試すべきです。
ちなみに
buildOptimizeraot一方で、
optimizationwebpackbabelいずれにせよ、
--aot=true --buildOptimizer=true結論
Angularのコンパイラは、相当賢いので、ほぼお任せでも大分スリム。
別方向の最適化でファイル圧縮などがあります。
なおファイル圧縮に関しては別記事ではありますが、