【AMP】部分的ですが AMP について調査したことを共有させてください

本記事では、ドキュメントにある記述や公式のサンプルだけではわからなかったことについて 2020 年 11 月の間に調査したことを綴ります。
また、私が AMP を適用したページが HTML x CSS x JavaScript とシンプルなシンプルな構成であったため読者の方の状態に合わない記載があるかもしれませんがご了承ください。

# AMP とは

Google が提供している HTML Framework の open source プロジェクトです。通常の HTML に AMP 仕様を付与することで実現ができます。
AMP では、いくつかの制約が設けられていてこの制約に従い実装を行うことでユーザに高速なページを提供することができます。

ampproject/amphtml

AMP を導入して嬉しいことや仕組みに関する詳細は、ドキュメントやキュレーションメディアに詳しく書いてあるのでここでは割愛いたします。

- amp の仕組み
- 【AMP】Google が推進する AMP とは?概要と対応方法まとめ

# AMP の制約

AMP には、制約があることに触れました。概ねドキュメントを参照することで要件を満たせるはずですが、調査しないとわからないことがあったのでユースケース (以下: UC) を元に対応について記載します。

## CSS

AMP における CSS の制約は下記の通りです。

- 外部のスタイルシートを参照することができない
- 特定のスタイルが使用できない
- 記述できる CSS の上限が 75kB

サポートされる CSS

### UC : 外部の CSS フレームワークを読み込んで使用したい

例えば bootstrap のようなフレームワークを使用したい場合は、CDN で配信されているものをライセンスの箇所を含めて AMP HTML にペーストするような対応になるかと思います。

ただし、AMP で記述できる CSS の上限は決まっているのでフレームワークの記述量に注意する必要があります。

仮に制限を超えた場合は、<style amp-custom></style>の中を minify するとか、AMP と互換性のあるCSS フレームワークの使用を検討する必要があるのかと考えています。

Which CSS framework is compatible with AMP?

## JavaScript

AMP にて、任意の JavaScript を使用する場合には、amp-script ( カスタム JavaScript ) を使用します。
詳細については、後述の amp-script の箇所でまとめて触れます。

## Web Font

- Link タグで web フォントを使用する場合は、ホワイトリストに登録されているプロバイダのみ
    - カスタム フォントの追加 <link>
- @font-face (で指定する場合は制限がない)
    - カスタム フォントの追加 @font-face

制限がないことについては、公式のドキュメントには記載がありませんので () で閉じてあります。下記の記事では、どんな font でも使えると述べられていますが私は、実装して確かめることができていないのですが参考に共有いたします。

2. Through @font- face (no limits, all fonts enabled).

AMP Font

# AMP Components

AMP には組み込み済みのamp-imgや拡張コンポーネントのamp-carouselなどが用意されています。

後で言及しますが、amp-script で外部のプラグインやフレームワークを使用することもできますが AMP Components で代用できることも十分あると考えています。

The AMP Component Catalogue

本章では、私が調査で使用したコンポーネントの使用に際してドキュメントだけでは少々手が届かなかったことなどを書き留めます。

## amp-carousel

基本的な操作は下記のドキュメントを参考にしていただければいいと思いますが、カルーセルを簡単に実装できます。

amp-carousel はバージョン 0.2 がリリースされているので、0.2 を使用していただればいいと思います。検索ではまだ 0.1 の方が先に出てくることもあったので、一応両方リンクを共有します。

- amp-carousel version0.1
- amp-carousel version0.2
- カルーセルを追加する

### スライダーの速度について

カルーセルのスライダースピードに違いを感じることがあったので、気になって調査したところ下記の issue を見つけました。

amp-carousel animation duration attribute #23494

We are moving to use the browser's native smooth scrolling timing for carousel in 0.2, which is not configurable. We currently have no plans to implement our own transitions.

カルーセルのスクロールの挙動は`scroll-behavior: smooth`に任せてあるのでブラウザごとにスクロールスピードは変わり、コントロールすることはできないようです。ちなみに今後、開発者がスクロールスピードをチューニングできるようになる予定もないようです。

注意点として、Safari は safari14 からの対応なので認識しておく必要があります。
MDN scroll-behavior

### style について

amp-carousel に限ったことではありませんが、AMP Components のスタイルを調整することができます。やり方は、下記のドキュメントにあるように、amp-carousel に元々ある定義を上書きします。github の css のファイルもリンクしておきます。

amp-carousel Styling

amp-carousel version 0.1 の css

amp-carousel version 0.2 の css

ただし、AMP Components のスタイリングの拡張にも制限があるので注意する必要があります。

To prevent usage of attribute selectors to circumvent class name limitations it is generally not allowed for CSS selectors to contain tokens and strings starting with -amp- and i-amp-.

https://github.com/ampproject/amphtml/blob/master/spec/amp-html-format.md#selectors

上記のように、`-amp-`と`i-amp-`は予約されており、`<style amp-custom></style>`内に記述すると AMP Validator にてエラーとなりますので、注意してください。

## amp-script

amp-script は、worker-dom を使用することで AMP でカスタム JavaScript を実行することを可能にしています。

AMP ページでのカスタム JavaScript の使用

AMP のパフォーマンスを保証しつつ、任意の JavaScript を実行するために amp-script にも制約があります。

ユーザーのジェスチャーが発生しないとページのコンテンツを変更することはできません。

 ページの読み込み時にコンテンツを変更することもできません。

スクリプトは、150 kB 以下である必要がある。また、JQuery や Vue.js などの JS フレームワークを使うことはできるがスクリプトが 150 kB の制限内に収まっている必要があります。

 カスタム JavaScript は、AMP コンポーネントによって別の Worker スレッドで実行されますが、Web Worker ですべての API がサポートしてないので、許可されている API のリスト にて確認する必要があります。

amp-script: AMP ❤️ JS

### 記法と制約

カスタム JavaScript の呼び出し方法は下記の2パターンがあります。詳しい書き方は、ドキュメントを参考にしてください。

Loading JavaScript
An amp-script element can load JavaScript in two ways:
- `remotely, from a URL`
- `locally, from a <script> element on the page`

- URL で JS ファイルを呼び出す方法
    - URL のプロトコルが HTTPS であること
    - 絶対パスであること
    - 150,000bytes までのスクリプトであること
- AMP HTML にスクリプトを書き込み呼び出す方法 ( inline script )
    - 10,000bytes までのスクリプトであること
    - mata タグに script hash を追加すること

#### パスの指定について

- URL で JS ファイルを呼び出す方法: URL のプロトコルが HTTPS であること・絶対パスであること

202011 月末現在、ドキュメントには明確な言及がないのですが、URL で JS ファイルを呼び出す場合に相対パスで定義すると下記のエラーが AMP Validator をより検出されます。

## error msg

The relative URL ‘/my-script.js‘ for attribute 'src' in tag 'amp-script' is disallowed. 

ただし、ローカルの環境では AMP Vaildator よりエラーが検出されていますが相対パスでのスクリプトの読み込みはできますので開発段階では一時的に相対パスで記述して作業するのがいいのではないかと考えています。

プロトコルが HTTPS である必要もあるので http://localhost:8000/my-script.js のような指定では、AMP Validator 検証にてエラーとなります。

## error msg

Invalid URL protocol 'http:' for attribute 'src' in tag 'amp-script'.
The URL of a JS file that will be executed in the context of this <amp-script>. The URL's protocol must be HTTPS. The HTTP response's Content-Type must be application/javascript or text/javascript.

絶対パスに関する記述はドキュメントに記載がありませんでしたので、amp-script の issue で関連してそうなものを探したところ下記の issue がありました。

<amp-script> sample: why fetch requires an absolute URL #3643

最後の方に、絶対パスである・プロトコルは HTTPS が必須であることを書いたとあるのですが未だにドキュメント内でそのような記載は見つけることができていません。もし見たと言う方がいらっしゃいましたら共有していただけるとありがたいです。

I've actually just changed our docs to say this happens because AMP always requires absolute URLs and HTTPS.

また、開発の仕方にも依存するかと思いますがローカルの開発環境ではエラーは無視すればいいと言っているので、前述した開発段階で一時的に相対パスで進める方法は的外れではないようでした。

When I develop locally, I just ignore the warnings. Of course this could be a problem for people who run the validator as part of their pre-commit hooks.

私は調査だけで終わったのですが、運用を考えると少々ややこしい制限だと感じました。例えば、ステージングと本番が存在する場合だと、commit するときに環境に合わせたパスに書き換えるだとかの処理を施す必要があるのでしょうか。

### Worker DOM について

AMP でカスタム JavaScript を実行するには amp-scritpt を使用しますが、ドキュメントが表現している仮想 DOM を実現し、Worker として処理を実行しているのは worker-domです。

worker-dom は、Web Worker として働くのですが通常の Web Worker とは異なり DOM の操作が可能です。これを実現しているのは、worker-dom 内で DOM を実装しているからとのことです。使用できない JavaScript API があることはこのような実装が裏でなされていることが理由であると認識しました。

この辺りの解釈は、下記の code reading を参考にさせていただきました。最新のコードは、ファイルが分散していたりクラス化されているので若干オブジェクト名が変わっている箇所もありましたが、とても参考になりました。
https://gist.github.com/mizchi/e9c4131b53a9edba59aab3da4f7d6f67

リポジトリの demo にあるように amp-script 経由でなくても動かすことができます。

worker-dom demo

### UC : JS フレームワークやプラグインを使用したい

例えば Vue.js を AMP で使用したい場合は、Vue.js のスクリプトを amp-script で呼び出すファイルに埋め込みます。
このケースはサンプルがあるので、公式のサンプルを参照してください。その他のフレームワークも同様で、amp-script で呼び出す JavaScript のファイルにフレームワークのソースコードを埋め込みます。

フレームワークのスクリプトに Worker DOM でサポートされていない API が使用されている可能性もあるので注意する必要があります。私の場合は、プラグインの箇所を amp-script で実行させようとしたときに、サポートされていない API でハマったので、代用できる箇所は AMP Components で書き換える対応をしました。
実装の内容にもよるかと思いますが、外部のプラグインではなく AMP が提供しているコンポーネントで代用できそうならこちらを使用する判断でいいのではないかと考えています。

AMP HTML
Custom Script
amp-script で Vue を使いたい

### UC : JavaScript で DOM を変更したい

既に述べていますが、amp-script では Worker DOM によって別スレッドで JavaScript を実行することで DOM の書き換えを行うことができます。

ただし考慮するべきこととして、いつ DOM を変更するのか考える必要があります。大きく下記の2パターンがあるかと思います。

- ユーザのアクションに基づいて DOM を変更する
    - `<button>`のクリックなどがあったときなど
- 過去にユーザがアクションを起こした際のデータをもとにレンダリング時に DOM を変更する
    - LocalStorage にデータ格納して DOM を変更するなど

amp-script に指定する layout の種類によって DOM の変更が許可されるタイミングが変わるので、ドキュメントのUser gesturesの箇所を読むとスムーズに実装できると思います。

responsiveやcontainerなどの layout はドキュメント上で variable-sized containersと表現されており、サイズが変更できるコンテナであると定義されています。variable-sized containersは、ユーザのジェスチャーがあったときにしか DOM の書き換えができない仕様となっています。この仕様は、コンテンツのレイアウトのずれによる UX の低下を防ぐようことが目的のようです。Cumulative Layout Shift (CLS)も合わせて読むと分かりやすいです。

反対に、fillやfixed-heightなどのfixed-size containersと表現されているサイズが変更できない amp-script コンテナの場合は、このルールの制限が緩和されていてどのタイミングでも DOM の変更が可能になります。

In some cases, amp-script will not apply DOM changes triggered by your JavaScript code unless the code was triggered by a user gesture - say, a button tap. This helps keep content layout shift from causing a poor user experience.

The rules are less restrictive for amp-script containers whose size cannot change. This is the case when the layout is not container and when the dimensions are specified in HTML. Let's call such containers "fixed-size". Containers whose size can change, we'll call "variable-sized".

User gestures

つまり、「ユーザのアクションに基づいて DOM を変更する」場合はvariable-sized containers・fixed-size containersどちらでも実装可能です。しかし、LocalStorage などのデータを元にレンダリング時に DOM を書き換える場合には、fixed-size containersである必要があります。

あとは、公式のサンプルなどを参考にコーディングしていただければいいと思います。

この記事が気に入ったらサポートをしてみませんか?