カテゴリー
[Angular] ビルド後のファイルの出力サイズを試行錯誤しつつ減らしてng buildのコマンドオプションを最適化する方法を考える
※ 当ページには【広告/PR】を含む場合があります。
2020/04/18
2022/03/21
Angularプロジェクトをビルドを最適化する際に、ビルドオプション
--build-optimizer=true --aot=true
Angularのビルドオプション自体は
angular.json
旧angular-cli.json
configurations
通常は意識せずに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: 無駄にクラスに実装しているインターフェイス
ここもかなりのリソースの無駄づかいをしております…
まず、あとで使うつもりであった
OnInit
OnDestory
AfterViewInit
例えば、以下のように、何もさせない
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/common
rxjs
削除対象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:
ビルド生成物に対し最適化させます
安直に考えると、両方ビルドオプションに指定しとけば、ビルド後の生成コードが最適化されるのでは...と期待しておりました。
実際やってみると
buildOptimizer
optimization
やるならどちらか一つの最適化オプションを試すべきです。
ちなみに
buildOptimizer
aot
一方で、
optimization
webpack
babel
いずれにせよ、
--aot=true --buildOptimizer=true
結論
Angularのコンパイラは、相当賢いので、ほぼお任せでも大分スリム。
別方向の最適化でファイル圧縮などがあります。
なおファイル圧縮に関しては別記事ではありますが、
参考
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー