WEBフロントエンドパフォーマンス要約 基礎編 2020年

# WEBフロントエンドパフォーマンス前編 2020年
本職ではバックエンドエンジニアとして仕事をしているのですが、最近ではWEBフロントエンドの仕事も増えてきました。

ただ、WEBフロントエンドパフォーマンスについてしガッツリ勉強したことがなかったので、[Webフロントエンド ハイパフォーマンス チューニング](https://amzn.to/40tgfbU)書籍で学びました。

内容を忘れない為にも、こちらに備忘録を残します。

**基礎編と発展編に分けた２部構成にします。**

この記事を読むだけで、ある程度はWEBフロントエンドパフォーマンスについての基礎レベルは理解できます。

より詳しい内容を知りたい方は書籍の購入をオススメします。

■ 対象読書

```
- WEBフロントエンドエンジニア
- WEBフロントエンドパフォーマンスについてよく知らない
- ネットワーク基礎知識について取得済み
```

# WEBパフォーマンスとは何か？

## パフォーマンスを定義する

WEBパフォーマンスの定義とは以下のように定義します。

```
ユーザーの様々な振る舞いに対してWebページが応答する速さ
```

WEBパフォーマンスを改善することで、ユーザーの目的達成のために費やす時間やリソースを節約することが可能になります。

## パフォーマンスの重要性

昨今ではインターネットの回線速度の改善やPCスペックが向上しましたが、モバイル環境や低スペックのマシンではパフォーマンスが落ちるという問題点はいまだに存在します。

よって、WEBパフォーマンスのチューニング作業は現在でも重要な作業と言えるでしょう。

また、WEBパフォーマンスを向上させることにより、ユーザー満足度の向上も期待できます。

## 新たに重要になった描画パフォーマンス

JavaScriptの進化やAjaxの進化により、現在のウェブアプリケーションは複雑なインタラクションを持つアプリが増えてきました。

したがって、ウェブページ内でのインタラクション（描画パフォーマンス）の最適化にも気をつけるべきです。

## ハイブリットアプリの存在
Apache Cordova(Android, Ios)やElectron(Windows,Mac)等のハイブリット開発フレームワークの存在も描画パフォーマンスを考えるときには見逃せません。

ハイブリッドアプリでは、初期ロードのパフォーマンスが比較的問題になりやすいので描画パフォーマンスを意識する必要があります。

## パフォーマンスチューニングの第一歩
**WEBアプリケーションのパフォーマンスチューニングを理解する上では、ブラウザの仕組みをまず理解することが大事です。**

仕組みの理解が曖昧のままパフォーマンスチューニングしても、なぜそこが問題になっていうるのか、解消する為にどうすれば良いのかが、あやふやになってしまいます。

# ブラウザのレンダリングの仕組み

ブラウザのレンダリングの仕組みを知ることは、パフォーマンスチューニングの重要な基礎です。

この基礎があるかどうかでウェブパフォーマンスの問題に立ち向かう際の効率が大きく変わります。

## ブラウザののコンポーネント

ブラウザは通常いくつかのソフトウェアコンポーネントによって構成されています。

チューニングをする為に知っておきたいコンポーネントは以下の２つです。

```
1. レンダリングエンジン
2. JavaScriptエンジン
```

■ **レンダリングエンジン** 
レンダリングエンジンとはブラウザの内部で利用されるHTMLの描画エンジンを指します。

レンダリングエンジンは、HTMLや画像ファイル やCSSやJavaScriptなどの各種リソースを読み取って、それを画⾯上の実際のピクセルとして描画します。

描画のみを担当しているので、ユーザーが普段 利⽤しているWEBブラウザのユーザーインターフェイス※１を持っていません。

```
※１ブックマーク機能や戻るボタン、進むボタンなどのこと
```

■ **JavaScriptエンジン**
JavaScriptエンジンは、JavaScriptの実⾏環境を提供するソフトウェアコンポーネントです。

レンダリングエンジンとともに、ブラウザ内部で利⽤されます。

**各ブラウザのレンダリングエンジンとJavaScriptエンジンまとめ**
![スクリーンショット 2020-08-21 18.15.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/dc951be7-6f4d-e830-ee80-341d2981bc7d.png)


## ブラウザのレンダリングの流れ

例）ブラウザのアドレスバーにURLを打ち 込んで、ウェブページが表⽰されるまで

**レンダリングの大まかな流れ**
![スクリーンショット 2020-08-21 18.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/c4150280-1bae-3a13-4de7-5515fa226200.png)


**レンダリングの工程**
![スクリーンショット 2020-08-21 18.21.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/0ed7093d-c2d7-debe-267c-f6867826e16d.png)

### リソースの読み込み -Loading
まず最初に行われるのがリソースの読み込み処理です。

![スクリーンショット 2020-08-21 18.24.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/b8b9ea52-8557-a680-d9d9-185f487925a6.png)

リソースのダウンロードでウェブページからリソース※１をサーバーからダウンロードします。

次に、受け取ったリソースをパース（構文解析）してレンダリングエンジンの内部表現に変換します。※２

```
※１ リソースとはHTML,CSS、JavaScriptファイル、JPEG,PNGなど
```

```
※2 パースとはHTMLの場合はDOMツリーに、CSS(CSS Object Model)の場合はCSSOMツリーに変換すること
```


### JavaScriptの読み込み
リソース一式を読み込んだ後、JavaScriptのコードをJavaScriptエンジンに引き渡して実行させます。

![スクリーンショット 2020-08-23 1.12.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/08de53c4-58c4-d89e-e5ee-e7198158f3bb.png)

JavaScriptのコード解析は以下のような工程で実施されます。

![スクリーンショット 2020-08-23 1.13.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/5dda0e26-a26f-4807-f547-f3ab1b123b7b.png)


■ 字句解析と構文解析
与えられたコードを何らかの実行可能な形式に変換、コンパイした上でJavaScriptを実行します。

![スクリーンショット 2020-08-23 1.17.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/dae00c53-234d-2d2a-0791-f5f354d14dd1.png)

```
1. JavaScriptのソースコードを文字解析を通じて、トークンに変換
2. 変換されたトークンの列を構文解析にかけて抽象構文木に変換。
```

■ コンパイル
構文解析されたものを、コンパイラがコードをその処置系（chromeならv8）が動作しているマシーンのCPUが直接解釈できる機械語に変換します。

■ 実行
最終的に実⾏可能な形式にコンパイルされた JavaScriptのコードは、処理系内部の仮想マシン、もしくはCPUで実⾏されます。

### レイアウトツリー構築
JavaScriptの実行が終わると、今度はレイアウトツリー構築に移行します。

![スクリーンショット 2020-08-23 1.22.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/0f8df4e6-7c7d-90ac-b91d-a9ed4926393e.png)

■ Calculate Style

```
Styleでは、ドキュメントのDOMツリー内の すべてのDOM要素に対して、どのようなCSSプロパティが当たるのかを計算します。
```
■ Layout

```
DOMツリー内のすべてのノードの視覚的なレイアウト情報の計算、レイアウト（Layout）※1を⾏います。
```

```
※1 レイアウト情報とは、要素の大きさ、要素のマージン、要素のパディング、要素の位置、要素のZ軸の位置になります。
```

### レンダリング結果の描画
DOMツリーのレイアウト情報の算出が終わると、 最後のレンダリング結果の描画（Painting）に移ります。

![スクリーンショット 2020-08-23 1.29.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/8397bbc3-cb47-9041-bc73-1b4b1bbeae60.png)


■ Paint

```
Paintでは、内部の低レベルな2Dグラフィックエンジン向けの命令を⽣成します。
```
■ Resterize

```
Rasterizeで、⽣成された命令を⽤いて実際にピクセ ル（ビットマップ）へと描画します。
```
■ Composite Layers

```
Layersの処理は、ピクセルにしたレイヤーを合成して最終的なレンダリング結果を⽣成します。
```


# チューニングの基礎

ブラウザレンダリングの仕組みを理解したので、次にWEBパフォーマンスチューニングの基本的な考え方とチューニングを行うのに必要なパフォーマンスの計測方法について解説します。（やっと本題に入れる...)

## チューニングのトレードオフ

チューニングのトレードオフとして主に２つあります。

```
1. 開発の時間的コスト
2. コードの単純さ（可読性、保守性、拡張性）※1
```

```
※1 SPAなどウェブアプリケーションで JavaScriptのコードが⼤きくなると単純さが失われます。その結果、ウェブアプリケーションの保守性や 拡張性が損なわれることになります。
```

無駄なチューニングをすることで、より複雑性が増す可能性があるので、知識のないままチューニングするのはリスクが高いです。（なので、チューニングの前にレンダリングの説明をしました）


## 推測するな、計測せよ

パフォーマンスチューニンで重要なのはまず計測することです。

多分パフォーマンスが向上しただろうと終わらずに必ず計測して見える化することが大事です。

計測するにはまずボトルネック（パフォーマンスを落としている問題箇所）を探し出します。

次に目指すべきパフォーマンス指標を設定します。

パフォーマンス指標としてRAILという指標があります。

RAILは、Response、Animation、Idle、Loadの各単語の頭文字をとって名付けられました。

```
Response: 100ミリ秒
Animation: 16ミリ秒
Idle: 50ミリ秒
Load: 100ミリ秒
```

RAILSではこのように４つの項目に対して、それぞれ守るべき基準時間を設けています。
この数値を超えると、パフォーマンス改善が必要になってきます。

```
Response: ウェブページがユーザーインターフェイス上の変化を引き起こして、応答するまでの時間
Animation: アニメーション中に連続して⾏われる フレームの中で1フレームの処理の時間の⽬安を指します
Idle: アイドル状態に実⾏されるJavaScriptの処理時間を指します。※1
Load: ウェブページのコンテンツの読み込みにかかる時間を指します。
```

```
※1アイドル状態とは、⼀度Responseや AnimationやLoadが終了してから何らかのユーザーのアクションを待っている状態を指します。
```

## 計測する手段

```
1. Chrome DevToolsなどデベロッパーツールによる計測 
2. JavaScriptによる計測 
3. パフォーマンス診断ツールの利⽤ 
4. パフォーマンスの継続的監視
````

## 1. Chrome DevToolsなどデベロッパーツールによる計測 
Chrome DevToolsの主要な機能の以下３つについて説明します。

```
Network
Performance
Memory
```

### Network
Networkパネルでは、ネットワークを通じたリソース取得のタイムラインの様⼦を細かくみること可能です。

参考画像
![スクリーンショット 2020-08-24 15.29.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/b196a39d-1f1d-92f4-440e-ba1e68ef90a4.png)

Networkパネルの利用方法については下記のサイトを参照ください。

■ 公式サイト
https://developers.google.com/web/tools/chrome-devtools/network/resource-loading?hl=ja

### Performance
Performanceパネルではページの読み込み、操作に 関するすべてのイベントを分析できます。

参考画像
![スクリーンショット 2020-08-24 15.37.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/1c0666c3-2df1-756f-8b32-7b3e54aca8b9.png)


Performanceパネルの利用方法については下記のサイトを参照ください。

■ 公式サイト
https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool?hl=ja


### Memory

Memoryパネルではメモリの利⽤状況を、計測し ます。主にメモリリークの検知やメモリの使い過ぎ を把握するために活⽤します。

参考画像
![スクリーンショット 2020-08-24 15.40.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/1ce22001-a7f5-c766-6be2-0441d34d39f9.png)


## 2. JavaScriptによる計測 

ユーザーの実際のパフォーマンスを確認したい場合はJavaScript中にパフォーマンス計測用のコードを入れておく方法があります。

```
1. Navigation Timing APIによる計測 
2. User Timing APIによる計測 
3. Resource Timing APIによる計測
```
### 1. Navigation Timing APIによる計測 

Navigation Timing APIでは、ブラウザのナビゲーション時の詳細なパフォーマンス情報を取得できます。

Navigation Timing APIではwindow.performance.timingオブジェクトとwindow.performance.navigationオブジェクトを利⽤します。

window.performance.timingオブジェクトには、各段階の処理時間のタイムスタンプが得られます。

使用例

``` javascript
var timing = performance.timing;

// リダイレクトにかかる時間 
console.log(timing.redirectEnd - timing.redirectStart); 

// ドメインの解決にかかる時間 
console.log(timing.domainLookupEnd - timing.domainLookupStart); 

// 接続の確⽴にかかる時間 
console.log(timing.connectEnd - timing.connectStart); 

// HTTPリクエストの送信からHTTPレスポンスの受信までかかる時間
console.log(timing.responseEnd - timing.requestStart); 

// ドキュメントの解析時間 
console.log(timing.domInteractive - timing.domLoading);
```

![スクリーンショット 2020-08-24 15.51.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/0f9b8a31-5bd0-88d8-1efa-d345d1216585.png)

![スクリーンショット 2020-08-24 15.52.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/be9fd5c5-3de9-218d-015c-f7802af3fea3.png)


### 2. User Timing APIによる計測 

User Timing APIを⽤いると、開発者が任意の処理にかかる時間を計測することができます。

``` javascript
var measure = performance.getEntriesByName('something-time') [0]; 

// ミリ秒単位の処理時間 
console.log(measure.duration); 

// 測定結果の種類。ここでは'measure'が⼊る。
console.log(measure.entryType); 

// 宣⾔した計測地点間の名前。ここでは'something'が⼊る。 
console.log(measure.name); 

// 開始時点でのミリ秒単位のタイムスタンプ 
console.log(measure.startTime);
```

### 3. Resource Timing APIによる計測

個別のリソースの取得にかかる時間を知りたい場合はこれがオススメ。


``` javascript
// ここで取得したPerformanceResourceTimingオブジェク トの配列には、現在のドキュメント内で読み込まれ たすべてのリソースの情報が含まれています。
var entries = window.performance.getEntriesByType('resource');

// リダイレクト処理にかかった時間 
console.log(entry.redirectEnd - entry.redirectStart);

// 接続の確⽴にかかった時間 
console.log(entry.connectEnd - entry.connectStart); 

// ドメイン名解決にかかった時間 
console.log(entry.domainLookupEnd - entry.domainLookupStart);
```

![スクリーンショット 2020-08-24 15.58.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286094/51ced9c2-9109-4658-d20c-7893a1ecf857.png)


## 3. パフォーマンス診断ツールの利⽤ 

パフォーマンス診断ツールとしてオススメは以下２つ

```
1. Audits ※1
2. PageSpeed Insights、 Lighthouse ※2
```

```
※1.AuditsとはGoogle ChromeのChrome DevToolsにはAuditsとい うパフォーマンス上の課題を検出する機能
※2.PageSpeed Insightsは実際のページを読み込んで、課題を検出します。Auditsとは違ってオンライン上で利⽤します
```
■ Audits利用方法
https://qiita.com/fumihiko-hidaka/items/d1355ce4ea34975ad3bc

パフォーマンス診断ツールを利用する最大の理由は、改善案を提示してくることです。
どのようチューニングをそもそも始めれば良いのか迷う場合は導入してみるのアリだと思います。


## 4. パフォーマンスの継続的監視
ウェブサイトのパフォーマンスを改善したからといって、それでOKということではありません。

継続的な監視を行い、いつの間にかパフォーマンスが劣化して問題が起きていたということを防ぐことが大事です。

定期的に調査を行いましょう。

オススメなのがパフォーマンス監視ツールのNewRelicBrowserというサービスです。

これを導入することで、パフォーマンスの見える化を実現します。

仕組みとしてはNew Relicの提供するJavaScriptを ウェブサイトに埋め込むと、Navigation Timing API などで得た計測値をNew Relicのサーバーへと送信して、開発者は送信された結果をダッシュボードで確認できます。

■ NewRelic公式サイト
https://newrelic.com/products/browser-monitoring


# まとめ

ここまでの内容を理解すれば、WEBフロントエンドパフォーマンスの最低限の知識は理解していると思います。

発展編も内容まとめて、そのうち公開します。