Help us understand the problem. What is going on with this article?

Predictive Prefetching、PrefetchとGuess.js、時々、Angular

こんにちは、川上です。

今年も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を使ってみてみましょう。
Image from Gyazo
 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で見てみましょう。
Image from Gyazo
※ preconnectはIEでは対応されておりません。
以下のように書くと、IEであってもdns-prefetchまでは行ってくれるようになります。

IEを意識した書き方

<link rel="preconnect dns-prefetch" href="//example.com">

ドメインを指定するのは一苦労

 dns-prefetch、preconnectで指定するURLは、そのページで指定する外部ドメインすべてを指定するのが望ましいです。しかし、そのドメインはHTMLにあったり、CSSにあったり、JSにあったりで調べるのが一苦労です。
 そこでおすすめは、WEBPAGETESTにはDomainsというタブがありまして、そこを見ていただくとそのサイトがアクセスしたドメインが一覧できるので、こちらからドメインを取得するのがおすすめです。

Image from Gyazo

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でみてみましょう。

Image from Gyazo
※また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を使ってみてみましょう。
Image from Gyazo

魅力的な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でみてみましょう。

Image from Gyazo

 いかがでしたでしょうか。
 以上で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
Angular Team / Senior developer program engineer at Google
Twitter
Github

 今年の夏にng-japan主催のイベントBuldersconで登壇されたりしていました。人柄も気さくで私のつたない英語の質問にも真摯に聞いてくれて大変うれしかったです。

Yutubeビデオの紹介

 今年のChrome Dev Summitにはセッションはありませんでしたが、後日YoutubeのGoogle Chrome DevelopersチャンネルにPredictive Prefetchingというタイトルで紹介しております。
Image from Gyazo
 このビデオではPredictive Prefetchingの紹介や事例などが紹介されております。なんとMicrosoftやYoutubeもPredictive Prefetchingを使ってパフォーマンス改善を行っているそうです。
 試してみたくなってきました!!

Giess.jsをwebpackに導入してみる

それではguess.jsをサイトに導入するまでを紹介いたします。まずはAngularアプリへではなく、Webpackを使ったサイトへの導入の仕方を紹介します。

install

npm i guess-webpack --save-dev

webpackへGuessPluginの設定

webpack configで読み込みます

webpack.config.js
const { GuessPlugin } = require('guess-webpack');

module.exports = (env, argv) => {
    ...

webpackのplugin内にて、GussPluginを設定します

webpack.config
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して実行するだけです。

index.js
import {guess} from 'guess-webpack/api';

guess()

しかし、guess()はPredictした候補のオブジェクトを返すだけなので、それからLink Elementを作成するなどの処理が必要となります。サンプルとしては以下のようになります。

index.js
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

ルーティングを設定します

ページ移動ができるようにルーティングを設定します

src/app/app-routing.module.ts
const routes: Routes = [
  { path: '', component: OneComponent},
  { path: 'two', component: TwoComponent},
  { path: 'three', component: ThreeComponent}
];

予測ファイルを作成します

今回はGoogle Analyticsの代わりに自分で作成した予測ファイルを使用したいと思います。
プロジェクトのルートディレクトリにroutes.jsonを作成します

/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を下記の通り変更します

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ファイルをプロジェクトのルートディレクトリに作成します。そして、以下のように設定を書きます

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さんの担当です! お楽しみに!
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away