季節のAMPテンプレート

こんにちは。UXデザインの土橋です。

すっかり暖かくなり行楽シーズンになってきました。
お子様のいらっしゃる方は特に外出する機会も増えていると思います。

そんな中旅行やピクニックの計画を立てながらお弁当箱や水筒、レジャーシートなど買い揃える際
ついついAMP対応したHTMLテンプレートを用意し忘れてしまう..という方も多いのではないでしょうか。

そんな方のために今回はAMP規格に準拠したHTMLテンプレートの生成タスクをgulpで構築してみようと思います。

AMPとは

AMP(Accelerated Mobile Pages)はモバイルサイトのUX向上を目的にGoogleが主導で開発・推進しているオープンソースのプロジェクトでありフレームワークの名称でもあります。
2016年2月にGoogle、2017年11月にYahoo!のモバイル検索アルゴリズムに組み込まれたことで浸透・注目され始めたのですが、ASKULでも表示速度の鈍重さが課題として挙がる事も多くなりUX改善の打開案として無視できなくなってきています。

AMP対応ページは、サーバーリクエスト数の削減や検索結果画面におけるファインダビリティ向上などwebコンテンツのボトルネックを解消するため独自のルールに準拠した実装が求められますが、厳密なルールが存在するということはフローを順序立てれば実装手順も明瞭に整理できるということです。
そこで、実装手順を整理・フロー化しAMPテンプレートを生成するタスクをつくってみます。

実装の流れ

Node.js、gulpと併せ各モジュールとファイルを用意(インストール)し順にタスクを回します。
流れとしてはjsonから基本構成、scssからスタイル情報を吸上げejsに流し込んで生成するというものになります。

取り入れるルール

  • スタイルは特定箇所にインラインで挿入
  • AMP js(独自コンポーネント)の利用
  • 検索結果スニペット用JSON-LDによる構造化データの記載
  • 独自仕様のマークアップ

環境とモジュール

Node.js
gulp
gulp-sass
gulp-autoprefixer
gulp-rename
gulp-ejs
run-sequence

ファイル構成

下記ファイルを用意します。

ejs/template.ejs

<% var content = content; var extra = extra; %>
<!doctype html>
<html amp lang="ja">
<head>
<meta charset="utf-8">
<meta content="width=device-width,maximum-scale=1.0,initial-scale=1.0,minimum-scale=1.0,user-scalable=yes" name="viewport">
<%- extra.module %>
<title><%= content.title %></title>
<script type="application/ld+json">
<%- extra.schema %>
</script>
<style amp-boilerplate><%- extra.style %></style>
</head>
<body>
<%- extra.element %>
</body>
</html>

基礎フレームです。
処理自体は行わず流し込む受け皿として用意します。

scss/style.scss

@charset "utf-8";

body, div, p, ul, ol, li, dl, dt, dd, table, th, td, img, figure, h1, h2, h3, h4, h5, h6, form, select { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img { border: 0; vertical-align: bottom; }
svg, input, select, textarea, label { vertical-align: middle; }
    ・
    ・
    ・
    .amp-social-share{
        margin-top:20px;
    }
}

スタイルです(内容は仮のもの)。

json/content.json

{
    "title" : "title",
    "name" : "index",
    "schema" : {
        "@context" : "http://schema.org",
        "@type" : "Product",
        "name" : "name"
    },
    "module" : [
        "sidebar",
        "bind",
        "social-share"
    ],
    "element" : "required"
}

基本構成情報です。
タイトルや構造化データ、利用するコンポーネント、マークアップモジュールの挿入判定を記載します。

json/module.json

{
    "def" : {
        "path" : "<script async src=\"https://cdn.ampproject.org/v0.js\"></script>\n"
    },
    "sidebar" : {
        "url" : "https://cdn.ampproject.org/v0/amp-sidebar-0.1.js",
        "elm" : "<div class=\"amp-sidebar\">\n<amp-sidebar id=\"menu\" layout=\"nodisplay\" side=\"left\">\n<ul>\n<li>111</li>

\n<li>222</li>\n<li>333</li>\n</ul>\n</amp-sidebar>\n<button on=\"tap:menu.toggle\">sidebar open</button>\n</div><!--/.sidebar end-->"
    },
    "bind" : {
        "url" : "https://cdn.ampproject.org/v0/amp-bind-0.1.js",
        "elm" : "<div class=\"amp-bind\">\n・・・</div><!--/.bind end-->"
    },
    "social-share" : {
        "url" : "https://cdn.ampproject.org/v0/amp-social-share-0.1.js",
        "elm" : "<div class=\"amp-social-share\">\n・・・</div><!--/.social-share end-->"
    }
}

コンポーネント、マークアップモジュール参照用です。
ここでは固定ですが内容を常に更新する仕組みが理想です。

gulpfile.js

const gulp = require('gulp');
const sass = require('gulp-sass');
const autoprefixer = require('gulp-autoprefixer');
const ejs = require('gulp-ejs');
const rename = require('gulp-rename');
const fs = require('fs');
const runSequence = require('run-sequence');

const contentjson = require('./json/content.json');
const modulejson = require('./json/module.json');

const template = 'ejs/template.ejs';
const scss = 'scss/style.scss';

gulp.task('sass', function() {
  return gulp
    .src(scss)
    .pipe(sass({ outputStyle: 'compressed' }))
    .pipe(
      autoprefixer({
        browsers: ['last 2 versions'],
        cascade: false
      })
    )
    .pipe(gulp.dest('css'));
});

gulp.task('ejs', function() {
  const name = contentjson.name;
  const element = contentjson.element;
  const schema = JSON.stringify(contentjson.schema).replace(/,/g, ',\n');
  const css = scss.replace(/scss/g, 'css');
  const style = fs.readFileSync(css, 'utf8');

  let fullmod = modulejson['def'].path;
  let fullelm = '';

  for (const modname of contentjson.module) {
    const modpath = modulejson[modname].url;
    const modelm = modulejson[modname].elm;

    fullmod += `<script async custom-element="amp-${modname}" src="${modpath}"></script>\n`;

    if (element === 'required') {
      fullelm += `${modelm}\n`;
    }
  }

  gulp
    .src(template)
    .pipe(
      ejs({
        content: contentjson,
        extra: {
          style: style,
          schema: schema,
          module: fullmod,
          element: fullelm
        }
      })
    )
    .pipe(rename(`${name}.html`))
    .pipe(gulp.dest('amp'));
});

gulp.task('default', function() {
  runSequence('sass', 'ejs');
});

構成要素の吸上げと整形、書出しを行います。

AMPではcssの外部読込みが禁止されているため、一度コンパイルし書き出した後に再度インラインに読込み直します(クロスブラウザ用にautoprefixerも噛ませます)。
その際、gulpの特性上非同期で処理が追い越されるため先行タスクのstreamは必ずreturnします。

またjsもサードパーティ製のものが禁止されているため専用コンポーネントの読込が必要です。
コンポーネント用jsonを参照し該当機能とそれに紐付いた要素(一応要/不要を指定可にします)を吸上げタイトルや構造化データは簡単に整形し最後に全データをejsに渡します。

実行

amp/index.html

<!doctype html>
<html amp lang="ja">
<head>
<meta charset="utf-8">
<meta content="width=device-width,maximum-scale=1.0,initial-scale=1.0,minimum-scale=1.0,user-scalable=yes" name="viewport">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async custom-element="amp-sidebar" src="https://cdn.ampproject.org/v0/amp-sidebar-0.1.js"></script>
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
<script async custom-element="amp-social-share" src="https://cdn.ampproject.org/v0/amp-social-share-0.1.js"></script>
<title>title</title>
<script type="application/ld+json">
{"@context":"http://schema.org",
"@type":"Product",
"name":"name"}
</script>
<style amp-boilerplate>body,div,p,ul,ol,li,dl,dt,dd,table,th,td,img,figure,h1,h2,h3,・・・body .amp-social-share{margin-top:20px}
</style>
</head>
<body>
<div class="amp-sidebar">
<amp-sidebar id="menu" layout="nodisplay" side="left">
<ul>
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
</amp-sidebar>
<button on="tap:menu.toggle">sidebar open</button>
</div><!--/.sidebar end-->
<div class="amp-bind">
<amp-state id="state">
<script type="application/json">
{"index":1}
</script>
</amp-state>
<ul class="ctl">
<li class="on" on="tap:AMP.setState({state:{index:0}})" [class]="state.index==0?'on':''">ctl 0</li>
<li on="tap:AMP.setState({state:{index:1}})" [class]="state.index==1?'on':''">ctl 1</li>
</ul>
<ul class="tgt">
<li class="on" [class]="state.index==0?'on':''">tgt 0</li>
<li [class]="state.index==1?'on':''">tgt 1</li>
</ul>
</div><!--/.bind end-->
<div class="amp-social-share">
<amp-social-share type="facebook" data-param-app_id="xxx"></amp-social-share>
<amp-social-share type="twitter"></amp-social-share>
</div><!--/.social-share end-->
</body>
</html>

コマンドでgulpを実行すると規格に準拠した簡易テンプレート(とコンパイル済css)が書き出されます。
急な出先でコンテンツの高速化が求められた際などに是非ご活用下さい。

次回はこのテンプレートを使って実際にLOHACO/ASKUL内ページのAMP化をしてみたいと思います。

参考

https://www.ampproject.org
https://qiita.com/moyashiharusamen/items/97daa8d4915fea57e62b

ASKUL Engineering BLOG

2018 © ASKUL Corporation. All rights reserved.