こんにちは、川上です。
今年もAngularアドベントカレンダー盛り上がっていますね!
僕もAngularカレンダーに参加することができてたいへん興奮しております。
この記事で紹介したいこと
Predictive PrefetchingライブラリであるGuess.jsをAngularアプリに導入するまでを紹介いたします。しかし、そもそもPredictive Prefetchingがわからないので、その前提にある仕様から紹介していこうと思います!
それでは少しの購読時間、あずからせていただきます!
記事のタイトルについて
タイトルに深い意味はなくて記事を書き進めていくうちにキーワードが多くなってしまい。。記事タイトルを悩んでいた僕へは某映画のタイトルしか降ってきませんでした。
Resource Hintと4つのAPI
Webの仕様の中には、リソースを事前に取得してページの読み込みを向上させるための仕様があります。それはResource Hintsというもので、それに沿ったAPIが4つの存在します。順番に紹介していきます。
dns-prefetch
ドメインの名前解決を事前に解決するAPIになります。外部サイトのリソース(JS、CSS、画像など)に適用すると効果的です。
書き方
<link rel="dns-prefetch" href="//example.com">
それでは、実際の効果の程をWEBPAGETESTを使ってみてみましょう。
dns-prefetchはなるべく早く解決したほうが効果的となるため、Elementの設置場所は、<head>
内のなるべくはじめの方に書くと良いでしょう(Content-typeやTitleの直後など)
※ちなみにアクセスしているサイト(この場合でいうとkawakami.dev)に対してdns-prefetchを指定しても何も変化はありません、サイトにアクセスした時点で、すでに解決されているためになります。
preconnect
preconnectは、dns-prefetchの処理に加えTCPのハンドシェイク、TLSのネゴシエーションも事前に行うAPIです
書き方
<link rel="preconnect" href="//example.com">
効果をWEBPAGETESTで見てみましょう。
※ preconnectはIEでは対応されておりません。
以下のように書くと、IEであってもdns-prefetchまでは行ってくれるようになります。
IEを意識した書き方
<link rel="preconnect dns-prefetch" href="//example.com">
ドメインを指定するのは一苦労
dns-prefetch、preconnectで指定するURLは、そのページで指定する外部ドメインすべてを指定するのが望ましいです。しかし、そのドメインはHTMLにあったり、CSSにあったり、JSにあったりで調べるのが一苦労です。
そこでおすすめは、WEBPAGETESTにはDomainsというタブがありまして、そこを見ていただくとそのサイトがアクセスしたドメインが一覧できるので、こちらからドメインを取得するのがおすすめです。
prefetch
ここまで紹介してきたAPI(dns-prefetch,preconnect)は、アクセスしているページで使用する外部リソースのためのAPIでした。ここからはアクセスしていない別ページのリソースを扱うAPIになります。
prefetchは、別ページのリソースを取得するためのAPIになり、これから訪れるであろうページのリソースを先に読み込んでおくために使います
書き方
<link rel="prefetch" href="second.html" as="document">
<link rel="prefetch" href="assets/js/second.js" as="script">
<link rel="prefetch" href="assets/styles/second.css" as="style">
<link rel="prefetch" href="assets/images/pic03.jpg" as="image">
as属性は任意で指定することができまして、リソースに応じたコンテキストを指定することができます。as属性の一覧はコチラ
それではその効果をWEBPAGETESTでみてみましょう。
※またcrossorigin属性も任意でつけることができまして、CORSポリシーを設定することができます。
<link rel="prefetch" href="//example.com/next-page.html" as="document" crossorigin="use-credentials">
prerender
prerenderは、preconnectに加えて読み込んだリソースをキャッシュ上にレンダリングまで行ってしまいます。それにより、後からそのページにアクセスしたときに素早くページを表示することができます。
<link rel="prerender" href="second.html">
それでは動きをWEBPAGETESTを使ってみてみましょう。
魅力的なAPIですが、prerenderは1つしか設置することはできません
preloadの紹介
Resource Hintsの仕様には含まれないのですが、preloadというprefetchに似たAPIもありますので紹介します。
preloadはprefetchによく似ているのですが、prefetchは外部リソースの先読みに用いてたのに対して、preloadは現在アクセスしているページのリソースをなるべく早く読み込ませるために使用するAPIです。
Web Fontsなどなるべく早く読み込ませたほうが良いリソースに使うのがおすすめです。
<link rel="preload" as="font" type="font/woff2" href="//fonts.gstatic.com/s/pacifico/v16/FwZY7-Qmy14u9lezJ-6I6MmBp0u-zK4.woff2" crossorigin>
動きをWEBPAGETESTでみてみましょう。
いかがでしたでしょうか。
以上でAPIの紹介は終わりになります
prefetch、prerender指定の難しさ
Resource Hintsの中でも、prefetch、prerenderは魅力的なAPIですが、サイトの全てのリソースを指定しておけば良いというわけではありません。(prerenderはそもそも1つしか指定できない制約があります)、ユーザーが次に訪れるであろうページを予測して読み込ませて置く必要があります。
リンク先数が少ないページであれば予測もしやすいですが、リンク先が多いページでは予測するのも困難です。また、予測が行なえても数日後もその予測が当たっているという事はまず無いでしょう。
Predictive Prefetching
このようにメンテナンスが難しいprefetch、prerenderを、Analyticsデータと機械学習を使って解決してくれるライブラリがあります。それがguess.jsになります。
作者であるMinkoは、この予測してprefetchする方式をPredictive Prefetchingと命名し解説しております
Minko Gechevの紹介
![Image from Github]
(https://avatars2.githubusercontent.com/u/455023?s=200&v=4)
Angular Team / Senior developer program engineer at Google
Twitter
Github
今年の夏にng-japan主催のイベントやBuldersconで登壇されたりしていました。人柄も気さくで私のつたない英語の質問にも真摯に聞いてくれて大変うれしかったです。
Yutubeビデオの紹介
今年のChrome Dev Summitにはセッションはありませんでしたが、後日YoutubeのGoogle Chrome DevelopersチャンネルにPredictive Prefetching
というタイトルで紹介しております。
このビデオではPredictive Prefetchingの紹介や事例などが紹介されております。なんとMicrosoftやYoutubeもPredictive Prefetchingを使ってパフォーマンス改善を行っているそうです。
試してみたくなってきました!!
Giess.jsをwebpackに導入してみる
それではguess.jsをサイトに導入するまでを紹介いたします。まずはAngularアプリへではなく、Webpackを使ったサイトへの導入の仕方を紹介します。
install
npm i guess-webpack --save-dev
webpackへGuessPluginの設定
webpack configで読み込みます
const { GuessPlugin } = require('guess-webpack');
module.exports = (env, argv) => {
...
webpackのplugin内にて、GussPluginを設定します
plugins: [
new GuessPlugin({
GA: '自身のView IDを指定してください',
debug: true,
})
View IDというのは、Google AnalyticsのIDとは違うものです。コチラのリンク先から取得できます。
GuessPluginにはその他にAdvance Optionがあります。詳しくはコチラからご確認ください。
ここでwebpackを動かすとbuild前にブラウザが立ち上がりGoogle Analyticsへのアクセス許可が求められます。ここで許可することでGuess.jsがGoogle Analyticsのデータを使用できるようになります。
しかし、build時に毎回この認証が動いてしまうので、production build時にのみ動くように設定を変更してあげるのが良いでしょう。
フロント側の設定
フロント側の設定では、以下のようにguessをimportして実行するだけです。
import {guess} from 'guess-webpack/api';
guess()
しかし、guess()はPredictした候補のオブジェクトを返すだけなので、それからLink Elementを作成するなどの処理が必要となります。サンプルとしては以下のようになります。
import {guess} from 'guess-webpack/api';
window.addEventListener('DOMContentLoaded', () => {
addPrefetchToHead();
});
function addPrefetchToHead() {
if (typeof window !== 'undefined') {
for (const url of Object.keys(guess())) {
let hint = document.createElement('link');
hint.rel = 'prefetch';
hint.href = url;
hint.as = 'html';
hint.crossorigin = 'use-credentials';
document.head.appendChild(hint);
}
}
}
AngularにGuess.jsを導入してみる
いよいよ、ここからはAngularにGuess.jsを導入するまでを紹介したいと思います。
Angular CLIは入っている前提になります
Projectの作成
とりあえずプロジェクトを新規作成します。また、今回はルーターを使うのでそのように設定しておきます
$ ng new guess-angular
? Would you like to add Angular routing? Yes
コンポーネントを追加
3ページ新たに追加したいので、コンポーネントを3つ追加します
$ ng g component one
$ ng g component two
$ ng g component three
ルーティングを設定します
ページ移動ができるようにルーティングを設定します
const routes: Routes = [
{ path: '', component: OneComponent},
{ path: 'two', component: TwoComponent},
{ path: 'three', component: ThreeComponent}
];
予測ファイルを作成します
今回はGoogle Analyticsの代わりに自分で作成した予測ファイルを使用したいと思います。
プロジェクトのルートディレクトリにroutes.json
を作成します
{
"/": {
"/two": 80,
"/three": 20
},
"/two": {
"/": 20,
"/three": 80
},
"/three": {
"/": 80,
"/two": 20
}
}
webpackを拡張できるようライブラリを追加
npm i -D @angular-builders/custom-webpack @angular-devkit/build-angular
angular.jsonの変更
追加したライブラリを使うようにangular.json
を下記の通り変更します
...
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser", // 変更
"options": {
...
"customWebpackConfig": { // 追加
"path": "./extend.webpack.config.js"
}
...
Guess.jsを追加
npm i -D guess-webpack guess-parser
Guess.jsの設定
Guess.jsをbuild時に動かすための設定をwebpack上に書きます。そのために、まずextend.webpack.config.js
ファイルをプロジェクトのルートディレクトリに作成します。そして、以下のように設定を書きます
const { GuessPlugin } = require('guess-webpack');
const { parseRoutes } = require('guess-parser');
module.exports = {
plugins: [
new GuessPlugin({
// Alternatively you can provide a Google Analytics View ID
// GA: 'XXXXXX',
reportProvider() {
return Promise.resolve(JSON.parse(require('fs').readFileSync('./routes.json')));
},
runtime: {
delegate: false
},
routeProvider() {
return parseRoutes('.');
}
})
]
};
build
そして、buildを実行します。
npm run build
アプリの実行
ビルド後Guess.jsが適応されたファイルは、distディレクトリに出力されますので実際に起動する際には、angular-http-server
などのライブラリを使用してdist/[プロジェクト名]
をWeb Rootに立ち上げる必要があります。
$ cd dist/guess-angular
$ angular-http-serve
Angular 参考URL
ここまでの内容は、コチラのページを参考にしております
その他のサンプル
Guess.jsでは、他にもたくさんのサンプルがありますので是非チェックしてみてください
まとめ
- Resource Hintをうまく活用してサイトのパフォーマンス向上を計画してみてください。ただし注意が必要でprefetchはユーザーが意図していないリソースをダウンロードさせる行為です。そのため無闇矢鱈とprefetchを設定するのではなく必要最小限に設定するのが良いでしょう。
- Prefetch、Prerenderはメンテナンスが大変です。そこでサイトのAnalyticsデータなどを活用して自動的にメンテナンスが行えることを検討しましょう。またそれに適したライブラリGuess.jsがあります
- Guess.jsはWebpack版やAngular版も用意されていてきっとあなたの環境へも導入出来るはずです
- 明日のアドベントカレンダーは、noxi515さんの担当です! お楽しみに!