Angular+Express.jsで構築したプロジェクトで、初回表示時に10秒前後かかってしまっていた問題を解決するために対応したことをメモっておこうと思います。
ちなみに、改善前はmain~.jsが1.5MBくらいあったのが、400KBくらいまで減らすことに成功し、初回起動時間も3秒程度まで短くなりました
Compressionを使ってGZIPに圧縮する
Express.jsやNest.jsを使ってるなら必須で入れるべき対応かと思います。
今回の対応の中で一番効果がありました!!
npm install --save compression
const app = express();
app.use(compression({level: 6}));
圧縮レベルも指定できるみたいです。
level:6で1.5MB⇒400MBくらいまで減りました。
圧縮が効いてると、Response HeaderでContent-Encoding: gzip
が指定されるようになります。
遅延ロードを活用する
遅延ロードは対象のモジュールを使うときにはじめてコードを読み込むというものです。
ビルドしたときにjsファイルが分割されて作成され、対象の画面を表示する際に分割したjsファイルを読みます。
const routes: Routes = [
{ path: '', loadChildren: () => import('./child/child.module').then(m => m.ChildModule) }
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Angular8からは動的インポートが使えるようになりました。
サービスの@InjectableでprovidedIn: 'root'
を使わない
サービスのリファレンスを見ると、以下のように@InjectableにprovidedIn: 'root'
を設定していたので、何も疑わずにマネしてました。
ただ、これやるとmain~.jsに取り込まれるため、あるComponentでしか使わないようなサービスで指定してしまうとかなり無駄です。。。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
対策
全画面共通のサービス以外は@InjectableでprovidedIn
を指定しない。
遅延ロードを使っている場合、子Moduleの@NgModule内でprovidersに指定することで利用個所を限定しましょう。
スタイルガイドの全体構造のガイドラインに書いてあるようなディレクトリ構造できっちりサービスやモジュールの範囲を限定できると良いかな?と思います。
ShareModuleはよく検討する
NgxBootstrapをつかってたんですが、何を思ったのかShareModuleを一つ作成し、その中にNgxBootstrapの各部品Moduleをインポートし、すべてのModuleでShareModuleをインポートしてました。
なんのためにModuleが分割されてるのか。。。
不要なModuleをインポートしてしまうので、jsサイズが大きくなってしまいます。
むやみにShareModuleを作成しないようにしましょう
外部のcssはCDNを使う
PrimeNGのように、angular.jsonでcss取り込みをするように書かれているパッケージは多いと思います。
ただ、使っていないスタイルも取り込まれてしまうので、styles~.cssのサイズが数百kBレベルで大きくなります。
そこで、cssをプロジェクト内に取り込まず、CDN経由で読み込むことでstyles~.cssのサイズが大きくなるのを避けます。
PrimeNGで試してみる
cssをプロジェクト内に取り込む場合
まずはスタートガイド通りにangular.jsonでcssファイルの指定をします。
"styles": [
"src/styles.scss",
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/themes/nova-light/theme.css",
"node_modules/primeng/resources/primeng.min.css",
],
$ ng build --prod --aot
Date: 2019-08-01T23:40:34.679Z
Hash: 9a4ed634ff38e222f351
Time: 16220ms
chunk {0} runtime-es5.741402d1d47331ce975c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es5.3c79bd2946c435722b96.js (main) 242 kB [initial] [rendered]
chunk {2} polyfills-es5.7f43b971448d2fb49202.js (polyfills) 111 kB [initial] [rendered]
Date: 2019-08-01T23:40:47.038Z
Hash: 0c5b82dc37f1d83ef447
Time: 12328ms
chunk {0} runtime-es2015.858f8dd898b75fe86926.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es2015.0249a613624c51fa5f34.js (main) 209 kB [initial] [rendered]
chunk {2} polyfills-es2015.27661dfa98f6332c27dc.js (polyfills) 36.4 kB [initial] [rendered]
chunk {3} styles.0221bcaaedadb37503af.css (styles) 166 kB [initial] [rendered]
CDN経由で読み込む場合
次に、index.htmlでCDNのcssを取り込むようにします。
npmパッケージはjsDelivrで公開されてるので、探してタグに指定します。
"styles": [
"src/styles.scss"
],
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/primeicons@2.0.0/primeicons.css" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/primeng@8.0.2/resources/primeng.min.css" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/primeng@8.0.2/resources/themes/nova-light/theme.css" type="text/css">
ビルドしてみます。
$ ng build --prod --aot
Date: 2019-08-01T23:43:09.112Z
Hash: 9a4ed634ff38e222f351
Time: 15315ms
chunk {0} runtime-es5.741402d1d47331ce975c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es5.3c79bd2946c435722b96.js (main) 242 kB [initial] [rendered]
chunk {2} polyfills-es5.7f43b971448d2fb49202.js (polyfills) 111 kB [initial] [rendered]
Date: 2019-08-01T23:43:19.891Z
Hash: a80bac2d47e20a33bfdb
Time: 10748ms
chunk {0} runtime-es2015.858f8dd898b75fe86926.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es2015.0249a613624c51fa5f34.js (main) 209 kB [initial] [rendered]
chunk {2} polyfills-es2015.27661dfa98f6332c27dc.js (polyfills) 36.4 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
styles~.cssのサイズが166kBも変わってきます。
◆注意
インストールするパッケージのバージョンは固定しましょう。
うっかりバージョンアップして、<link>で取得するcssのバージョンと食い違ってしまうと画面崩れが起きる可能性があります。
"dependencies": {
・・・
"primeicons": "2.0.0",
"primeng": "8.0.2",
・・・
},
IVY
IVYとはAngular9で正式リリースされるレンダリングエンジンです。
詳細はリファレンスをご覧下さい。
Angular8ではまだプレビュー版ですが、使うことができます。
{
"compilerOptions": { ... },
"angularCompilerOptions": {
"enableIvy": true
}
}
IVYなしの場合
$ ng build --prod --aot
Date: 2019-08-01T23:43:09.112Z
Hash: 9a4ed634ff38e222f351
Time: 15315ms
chunk {0} runtime-es5.741402d1d47331ce975c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es5.3c79bd2946c435722b96.js (main) 242 kB [initial] [rendered]
chunk {2} polyfills-es5.7f43b971448d2fb49202.js (polyfills) 111 kB [initial] [rendered]
Date: 2019-08-01T23:43:19.891Z
Hash: a80bac2d47e20a33bfdb
Time: 10748ms
chunk {0} runtime-es2015.858f8dd898b75fe86926.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es2015.0249a613624c51fa5f34.js (main) 209 kB [initial] [rendered]
chunk {2} polyfills-es2015.27661dfa98f6332c27dc.js (polyfills) 36.4 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
IVYありの場合
既存プロジェクトの場合、tsconfig.app.jsonにenableIvy:true
を追加するだけです。
"angularCompilerOptions": {
"enableIvy": true
}
※新規プロジェクトの場合はng new project-name --enable-ivy
のように--enable-ivy
を指定すればOKです
$ ng build --prod --aot
Compiling @angular/core : module as esm5
Compiling @angular/common : module as esm5
Compiling @angular/platform-browser : module as esm5
Compiling @angular/platform-browser-dynamic : module as esm5
Compiling @angular/router : module as esm5
Date: 2019-08-01T23:53:57.269Z
Hash: a549293d080c890e1895
Time: 53919ms
chunk {0} runtime-es5.741402d1d47331ce975c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es5.0fc1fd74f3bbc9926e4d.js (main) 231 kB [initial] [rendered]
chunk {2} polyfills-es5.7f43b971448d2fb49202.js (polyfills) 111 kB [initial] [rendered]
Compiling @angular/core : es2015 as esm2015
Compiling @angular/common : es2015 as esm2015
Compiling @angular/platform-browser : es2015 as esm2015
Compiling @angular/platform-browser-dynamic : es2015 as esm2015
Compiling @angular/router : es2015 as esm2015
Date: 2019-08-01T23:54:38.351Z
Hash: 1fa8b85dbae59a28d8b1
Time: 41034ms
chunk {0} runtime-es2015.858f8dd898b75fe86926.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es2015.5e9ea7828c1754ddb647.js (main) 207 kB [initial] [rendered]
chunk {2} polyfills-es2015.27661dfa98f6332c27dc.js (polyfills) 36.4 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
es2015のmain~.jsで209kBから 207kBに減っています。
今回はテンプレートプロジェクトなので、効果は小さいですが大規模プロジェクトになると効果は大きいんじゃないかと思います。
【番外編】AOTビルド
基本的にはデフォルトでAOTビルド有効なので、気にする必要はないと思いますが一応書いておきます。
以下のように--aot
をつけてビルドします。
AOTビルドに関してはリファレンスを見てください。
ng build --aot --prod
--aot
なしで本番ビルドした場合のサイズ
ng new
したばかりのもので試してみます。
AOTを無効にする設定をします
・・・
"configurations": {
"production": {
・・・
"aot": false,
"buildOptimizer": false,
}
}
・・・
$ ng build --prod
Date: 2019-08-01T23:13:57.002Z
Hash: 7261057f8d9a98bd00a0
Time: 21086ms
chunk {0} runtime-es5.fd090508f518df362df3.js (runtime) 1.42 kB [entry] [rendered]
chunk {1} main-es5.5bf5a74163dfcb858ef4.js (main) 874 kB [initial] [rendered]
chunk {2} polyfills-es5.2aba62e787b3d3e95c6a.js (polyfills) 118 kB [initial] [rendered]
Date: 2019-08-01T23:14:13.404Z
Hash: 1a64fef5e665ce26dc1e
Time: 16346ms
chunk {0} runtime-es2015.6799f3bffa293d78b1fe.js (runtime) 1.42 kB [entry] [rendered]
chunk {1} main-es2015.e69fd787690d8f424782.js (main) 736 kB [initial] [rendered]
chunk {2} polyfills-es2015.3d5c9d6e99ad1936257a.js (polyfills) 60.5 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
--aot
ありで本番ビルドした場合のサイズ
$ ng build --prod --aot
Date: 2019-08-01T23:12:24.355Z
Hash: 9a4ed634ff38e222f351
Time: 14796ms
chunk {0} runtime-es5.741402d1d47331ce975c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es5.3c79bd2946c435722b96.js (main) 242 kB [initial] [rendered]
chunk {2} polyfills-es5.7f43b971448d2fb49202.js (polyfills) 111 kB [initial] [rendered]
Date: 2019-08-01T23:12:35.747Z
Hash: a80bac2d47e20a33bfdb
Time: 11364ms
chunk {0} runtime-es2015.858f8dd898b75fe86926.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} main-es2015.0249a613624c51fa5f34.js (main) 209 kB [initial] [rendered]
chunk {2} polyfills-es2015.27661dfa98f6332c27dc.js (polyfills) 36.4 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
es2015向けのビルドファイルを見るとmain~.jsが874kBから209kBまで小さくなっています。
ただ、上でも書きましたがAOTビルドはデフォルトで有効になっているので、まったく恩恵にはあずかれませんでした。。。