読者です 読者をやめる 読者になる 読者になる

albatrosary's blog

Azure と Angular と Wercker CI とか

AngularJS+Bootstrapを使ってもPageSpeed Insightsの成績をとにかく上げたい

AngularJS

AngularJSとかBootstrapを使うとサイズも大きく初期ロードがちょっともっさりするという印象があります。「PageSpeed Insights」で測定するとAngularJSとかbootstrapなんて使うなってくらい点数が悪い。指摘事項は大体次の通り:

  • スクロールせずに見えるコンテンツのレンダリングをブロックしている JavaScript/CSS を排除する
  • JavaScript を縮小する
  • HTML を縮小する
  • 画像を最適化する
  • CSS を縮小する

縮小に関してはgruntで行えるので問題ないのだが、レンダリングブロックするJavaScript/CSSを排除するというのは難しい。どこまで点数が良くなるか実験してみた。実際に云々とか細かい話は今回は考えず、とにかく「PageSpeed Insights」の点数を上げることだけを考えてみました。

f:id:albatrosary:20150108201435p:plain

PageSpeed Insights

参考までに、モバイルフレンドリーであるかのチェックツールもあります:

Mobile-Friendly Test

使ったリソース

このサイトは次のライブラリを使用しています(chartjsはグラフのサンプル画面を作りたく入れてます):

  • bootstrap-sass-official@3.2.0
  • angular@1.3.8
  • angular-ui-router@0.2.13
  • jquery@2.1.3
  • chartjs@1.0.1

コンテンツは

  • home
  • products
  • about
  • contact
  • charts

の5つですが性能測定は「www.ashiras.xyz」のファーストビューである home 画面のみで行っています。サーバはgithubページを使ってます。もちろんYEOMANのgenerator-webappから始めました。

やったこと

やったことは

  • ヘッダー、フッター等のincludeの排除
  • AngularJSの実行をファーストビューで遅延させても見た目正しく表示されるように工夫
  • ファーストビューに必要なCSSをインライン化する
  • bootstrapとAngularJSのリソースを排除しこれらライブラリを遅延ロードさせる。

ヘッダー、フッター等のincludeの排除

はじめにヘッダー、フッターで良く使うパターンincludeをやめました。

<div ng-include="'components/header/header.html'"></div>

ng-includeはAngularJSのモジュールであるためAngularJSが読み込まれるまで実行されないからです。

AngularJSの実行をファーストビューで遅延させても見た目正しく表示されるように工夫

もしかすると最大の工夫かもしれません。通常、ルータを使う場合は状態「home」「about」「contact」に対してテンプレートであるHTMLを作成します。ファーストビューの「home」は次のようなテンプレートです:

      <div class="informations">
        <div class="resp_image"></div>
      </div>

      <div class="container">
        <div class="marketing">
          <div>
            <h4>HTML5 Boilerplate</h4>
            <p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.</p>

            <h4>Sass</h4>
            <p>Sass is a mature, stable, and powerful professional grade CSS extension language.</p>


            <h4>Bootstrap</h4>
            <p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>

            <h4>AngularJS</h4>
            <p>AngularJS is a toolset for building the framework most suited to your application development. It is fully extensible and works well with other libraries. Every feature can be modified or replaced to suit your unique development workflow and feature needs. Read on to find out how.</p>

            <h4>ui-router</h4>
            <p>The new $stateProvider works similar to Angular's v1 router, but it focuses purely on state.</p>
            <ul>
              <li>
              A state corresponds to a "place" in the application in terms of the overall UI and navigation.
              </li>
              <li>
              A state describes (via the controller / template / view properties) what the UI looks like and does at that place.
              </li>
              <li>
              States often have things in common, and the primary way of factoring out these commonalities in this model is via the state hierarchy, i.e. parent/child states aka nested states.
              </li>
            </ul>
          </div>
        </div>
      </div>

これを状態「home」のときに次のように読み込むのが普通です:

'use strict';

angular.module('webapp')
  .config(['$stateProvider', 
  function ($stateProvider) {
    $stateProvider
      .state('home', {
        url: '/',
        templateUrl: 'scripts/home/home.html',
        controller: 'HomeCtrl'
      });
  }]);

ここではhome.htmlからui-routerの入れ込み先の「<div ui-view=""></div>」の中に入れ込みます。home.htmlはブランクページになります。結果は次の通りで、home.htmlは

からっぽ

「<div ui-view=""></div>」の部分は

    <div ui-view="">
      <div class="informations">
        <div class="resp_image"></div>
      </div>

      <div class="container">
        <div class="marketing">
          <div>
            <h4>HTML5 Boilerplate</h4>
            <p>HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.</p>

            <h4>Sass</h4>
            <p>Sass is a mature, stable, and powerful professional grade CSS extension language.</p>


            <h4>Bootstrap</h4>
            <p>Sleek, intuitive, and powerful mobile first front-end framework for faster and easier web development.</p>

            <h4>AngularJS</h4>
            <p>AngularJS is a toolset for building the framework most suited to your application development. It is fully extensible and works well with other libraries. Every feature can be modified or replaced to suit your unique development workflow and feature needs. Read on to find out how.</p>

            <h4>ui-router</h4>
            <p>The new $stateProvider works similar to Angular's v1 router, but it focuses purely on state.</p>
            <ul>
              <li>
              A state corresponds to a "place" in the application in terms of the overall UI and navigation.
              </li>
              <li>
              A state describes (via the controller / template / view properties) what the UI looks like and does at that place.
              </li>
              <li>
              States often have things in common, and the primary way of factoring out these commonalities in this model is via the state hierarchy, i.e. parent/child states aka nested states.
              </li>
            </ul>
          </div>
        </div>
      </div>
    </div>

興味深いのは状態が「about」「contact」のときには「<div ui-view=""></div>」を上書きしそれぞれのコンテンツを表示しています。「home」のときには上記コンテンツが表示されます。

ファーストビューに必要なCSSをインライン化する

ファーストビューで利用するカスケード・スタイル・シートで定義されている部分をすべて取り出しインライン化します。このサイトではメニュー部やリンク部、ベースとなるhtmlやbodyにスタイルが適用されていますのですべて取り出してインライン化します。書ききれませんので省略しますが「すべて」抜き出しインライン化しないと画面がちらつきますので根気よく作業する必要があります。

  <head>
    <meta charset="utf-8">
    <meta http-equiv="Expires" content="600">
    <base href="/">
    <title>ashiras</title>
    <meta name="description" content="「ashiras」は、HTML5などのWebプラットフォーム技術を使った「ものづくり」に関わる組織です。">
    <meta name="viewport" content="width=device-width">
    <link rel="shortcut icon" href="/favicon.ico">
    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
    <!-- build:css(.) styles/vendor.css -->
    <!-- bower:css -->
    <!-- endbower -->
    <!-- endbuild -->

    <style>
html {
  font-size: 10px;
  -webkit-tap-highlight-color: transparent; }
body {
  margin: 0;
}
body {
  padding-top: 20px;
  padding-bottom: 20px;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.42857;
  color: #333333;
  background-color: #fff;
}
a {
  background: transparent; }

a:active,
a:hover {
  outline: 0; }

…省略…

p {
  margin: 0 0 10px; 
}
.text-muted {
  color: #777777; }
    </style>
  </head>

bootstrapとAngularJSのリソースを排除しこれらライブラリを遅延ロードさせる

遅延ロードさせますので async キーワードをscriptタグとlinkタグに追加します。ここでgenerator-webappを利用した場合ですが、ビルドすると

  • vender.js
  • main.js

に分かれます。これを一つにする必要があります。そのための工夫として次のように書き換えます

<!-- build:js({.,app,.tmp}) scripts/main.js -->
<!-- bower:js -->
<script src="bower_components/jquery/dist/jquery.js"></script>

…省略…

<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>
<!-- endbower -->
<script src="scripts/main.js"></script>
<script src="components/header/header.controller.js"></script>
<script src="scripts/home/home.controller.js"></script>
<script src="scripts/home/home.js"></script>
<script src="scripts/about/about.controller.js"></script>
<script src="scripts/about/about.js"></script>
<script src="scripts/contact/contact.controller.js"></script>
<script src="scripts/contact/contact.js"></script>
<!-- endbuild -->

ファイルがひとつにまとまりましたのでasyncキーワードを入れます。Gruntfile.jsを変更します。useminにキーワードを追加します:

    usemin: {
      options: {
        assetsDirs: [
          '<%= config.dist %>',
          '<%= config.dist %>/images',
          '<%= config.dist %>/styles'
        ],
        blockReplacements: {
          css: function (block) {
            return '<link async defer rel="stylesheet" href="' + block.dest + '">';
          },
          js: function (block){
            return '<script async defer src="' + block.dest + '"><\/script>';
          }
        }
      },
      html: ['<%= config.dist %>/{,*/}*.html'],
      css: ['<%= config.dist %>/styles/{,*/}*.css']
    },

得点はいかに?

こうして作ったサンプルサイトがこちらです:

f:id:albatrosary:20150110215507p:plain

得点ですが結構良いのではと思います:

f:id:albatrosary:20150108202828p:plain

f:id:albatrosary:20150108202833p:plain

性能測定には他にもツールがあり、次のものも使ってみました:

WebWait - Benchmark Your Website

結果は次の通りです:

f:id:albatrosary:20150108204638p:plain

最後に

今回とにかく点数を上げるという観点で工夫しました。記載していませんがGruntfile.jsも色々と触ってます。ルーティングも若干工夫していてURL「www.ashiras.xyz/about.html」はルーティングによるテンプレートを表示させています:

'use strict';

angular.module('webapp')
  .config(['$stateProvider', 
  function ($stateProvider) {
    $stateProvider
      .state('about', {
        url: '/about.html',
        templateUrl: 'scripts/about/about.html',
        controller: 'AboutCtrl'
      });
  }]);

ダミーのabout.htmlも配置していてダミーはルートにリダイレクトするよう定義しています。リロードボタンを押されたときの対応です。

<!DOCTYPE html>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; URL=http://www.ashiras.xyz/">
<title>ashiras</title>

結果的に97点とれたので満足しました。