すでに AMP を採用している場合は、プログレッシブ ウェブアプリ(PWA)の構築をおすすめします。AMP ページを使用すると、PWA を非常に簡単に開発できます。ここでは、PWA 内に AMP を埋め込み、既存の AMP ページをデータソースとして使用する方法について説明します。

JSON から AMP に

PWA として最も一般的なのは、Ajax で JSON API にアクセスする単一ページのアプリケーションです。この場合、ナビゲーションに使用するデータと、記事をレンダリングするための実際のコンテンツは、JSON API から返されます。

JSON API から返されるコンテンツは未加工ですので、クライアントにレンダリングする前に HTML に変換する必要があります。この処理が大変なことが、PWA の維持管理を難しくしています。これに代わる方法として、既存の AMP ページをコンテンツ ソースとして再利用することをおすすめします。AMP を使用すれば、数行のコードを追加するだけでこの処理を実装できます。

PWA に「Shadow AMP」をインクルードする

最初のステップとして、「Shadow AMP」という特別な AMP を PWA に含めます。これにより、最上位のページに AMP ライブラリが読み込まれます。これは上位のコンテンツの管理ではなく、指定したページの一部を「増幅」するためだけに使用します。

ページの先頭に次のように記述して Shadow AMP をインクルードします。

<!-- Asynchronously load the AMP-with-Shadow-DOM runtime library. -->
<script async src="https://cdn.ampproject.org/shadow-v0.js"></script>

Shadow AMP API が使用可能になったことを確認する方法

Shadow AMP ライブラリは、async 属性を指定して読み込むことをおすすめします。ただしその場合は、ライブラリが完全に読み込まれて使用可能な状態になったかどうかを、何らかの方法で確認する必要があります。

Shadow AMP ライブラリが使用可能になったかどうかは、グローバル AMP 関数が使用できるかどうかで判断できます。Shadow AMP では、「関数を非同期で読み込む方法」を使用します。次のコードを挿入することを検討してください。

(window.AMP = window.AMP || []).push(function(AMP) {
  // AMP is now available.
});

コードが正常に動作し、いくつかのコールバックが追加されれば、AMP は使用可能な状態になっています。以下にその理由を説明します。

このコードは次のように解釈されます。

  1. 「window.AMP が存在しない場合は、その位置を確保するために空の配列を作成する」
  2. 「その配列に、AMP が使用可能になったときに実行すべきコールバック関数をプッシュする」

Shadow AMP ライブラリが実際に読み込まれると、すでに window.AMP にコールバックの配列があることを認識してキュー全体を処理します。この関数は、もう一度実行しても正常に動作します。Shadow AMP によって window.AMP が置き換えられ、カスタムの push メソッドによって直ちにコールバックが処理されるからです。

PWA のナビゲーションを処理する

この手順は手動で実装する必要があります。ユーザーをどのようにナビゲートしたいかに応じて、コンテンツへのリンクを柔軟に表示できるようするためです。リスト形式やカード形式も選択できます。

一般的な方法としては、指定した URL とメタデータを返す JSON をフェッチします。最終的には、ユーザーがリンクをクリックしたときに関数コールバックが発生します。このコールバックには、リクエストした AMP ページの URL が含まれます。ここまで来ればあと一息です。

AMP API を使用してページ インラインをレンダリングする

最後に、ユーザーの操作に応じてコンテンツを表示します。そのためには、関連する AMP ドキュメントをフェッチして Shadow AMP に引き継ぐ必要があります。まず次のようなコードを記述して、ページをフェッチする関数を実装します。

function fetchDocument(url) {

  // unfortunately fetch() does not support retrieving documents,
  // so we have to resort to good old XMLHttpRequest.
  var xhr = new XMLHttpRequest();

  return new Promise(function(resolve, reject) {
    xhr.open('GET', url, true);
    xhr.responseType = 'document';
    xhr.setRequestHeader('Accept', 'text/html');
    xhr.onload = function() {
      // .responseXML contains a ready-to-use Document object
      resolve(xhr.responseXML);
    };
    xhr.send();
  });
}

これで Document オブジェクトが準備できたので、AMP に引き継いでレンダリングします。次のように、AMP ドキュメントのコンテナとして機能する DOM 要素への参照を取得し、AMP.attachShadowDoc() を呼び出します。

// This can be any DOM element
var container = document.getElementById('container');

// The AMP page you want to display
var url = "https://my-domain/amp/an-article.html";

// Use our fetchDocument method to get the doc
fetchDocument(url).then(function(doc) {
  // Let AMP take over and render the page
  var ampedDoc = AMP.attachShadowDoc(container, doc, url);
});

これで、リクエストした AMP ページが PWA の子としてレンダリングされます。

クリーンアップ

クリーンアップに最適なタイミングは、ユーザーが PWA 内で AMP から AMP にナビゲートするときです。以前レンダリングした AMP ページを破棄する際は、必ず次のような形で AMP に伝えてください。

// ampedDoc is the reference returned from AMP.attachShadowDoc
ampedDoc.close();

これにより、このドキュメントをもう使用しないことが AMP に伝わり、メモリや CPU のオーバーヘッドが解放されます。

サンプルアプリ

AMP を埋め込んだ PWA の動作を実際にご覧いただくため、React サンプルを作成しました。ここで説明した手順をシンプルな React コンポーネントにまとめてあります。柔軟にカスタマイズできる JavaScript の PWA と、コンテンツを瞬時に提供できる AMP の長所を兼ね備えており、ナビゲーション時の遷移もスムーズです。

Polymer フレームワークを使用した PWA と AMP のサンプルも用意しました。このサンプルでは、amp-viewer を使用して AMP を埋め込んでいます。