23
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

詳解 AMP Optimizer

この記事では AMP Optimizer v2.6.0 について説明しています。

AMP Optimizer とは

AMP Optimizer は AMP ページの作成をシンプルにし、AMP のレンダリングパフォーマンスを最適化するツールです。AMP Cache でも行われている AMP のサーバーサイドレンダリングをサポートしています。

npm のパッケージで配布されていますが、単体で使うよりは Next.js の AMP 機能WordPress の AMP プラグインとして間接的に使うことの方が多いかと思います。

動かし方

CLI

CLI のコマンド一つで試すことができます。

$ cat <<EOF > index.html
> Test AMP Optimizer
> EOF

1行の html ファイルに以下のコマンドで AMP Optimizer を動かします。

npm install @ampproject/toolbox-cli -g
amp optimize index.html 

amp の文字がたくさん書かれたファイルが出力されます。

CLI では url を指定することもできます。example.com は AMP 準拠サイトではないので、AMP Valid なページは出力されません。

amp optimize https://example.com/

API

実際に使うのはこちらだと思います。

インストール

npm install @ampproject/toolbox-optimizer

html を文字列で渡せば最適化した html 文字列が得られます。

const AmpOptimizer = require('@ampproject/toolbox-optimizer');

const ampOptimizer = AmpOptimizer.create();

const originalHtml = `
<!doctype html>
<html ⚡>
  ...
</html>`;

ampOptimizer.transformHtml(originalHtml).then(optimizedHtml => {
  console.log(optimizedHtml); // 最適化された html
});

オプションの指定。以降で説明します。

const ampOptimizer = AmpOptimizer.create({
  verbose: true
});

オプションを指定せずにデフォルトで使ってもいくつかの最適化が行われます。以降で何が行われているのかについて説明します。

AMP Optimizer は何をやっているのか?

ドキュメントの冒頭にデフォルトで行われる処理が記載されています。

  • AMP layout のサーバーサイドレンダリング
  • 不足している必須 AMP タグの自動追加
  • amp-img、amp-iframe、amp-video、または amp-video-iframe からヒーロー画像を自動検出してプリロード
  • (可能であれば) AMP Boilerplate の削除
  • 不要なホワイトスペースの削除
  • CSS キーフレームアニメーションを抽出してページ下部に移動
  • AMP フレームワークとカスタムフォントの読み込みを最適化
  • インライン化された amp-script 用のCSPを生成

AMP の最適化については以下の記事で説明されています。

オプション

現時点で使えるオプションについて説明します。ドキュメント+αな内容です。

autoAddMandatoryTags

AMP として足りないマークアップを自動的に追加します。

  • name: autoAddMandatoryTags
  • valid options: [true|false]
  • default: true
  • used by: AddMandatoryTags

わかりやすいところでいうと、html タグの amp 属性や AMP 用の script タグ (v0.js) が自動的に追加されます。

autoExtensionImport

amp-carousel のような AMP 拡張(AMP コンポーネント)の script タグが自動的に追加されます。

  • name: autoExtensionImport
  • valid options: [true|false]
  • default: true
  • used by: AutoExtensionImport

script タグは 該当の AMP 拡張が使われているときのみに追加されるます。したがって、AMP Optimizer の使用を前提とするなら、手動で script タグを書かない運用も可能です。

format

AMP のフォーマットを指定する。

  • name: format
  • valid options: [AMP|AMP4EMAIL|AMP4ADS]
  • default: AMP
  • used by: AutoExtensionImport, AddMandatoryTags

通常の Web サイトだけなら使うことはない。AMP のEメールや広告のフォーマットでも使用可能。それぞれ必須 AMP コードが異なるので、それらの差分を Optimizer が埋めてくれる。

experimentEsm

AMP ランタイムとコンポーネントで JavaScript モジュールのサポートを有効にする。

<script async nomodule src="https://cdn.ampproject.org/v0.js"></script>
<script async src="https://cdn.ampproject.org/v0.mjs" type="module" crossorigin="anonymous"></script>

Warning: Invalid AMP ページになる

  • name: experimentEsm
  • valid options: [true|false]
  • default: false
  • used by: RewriteAmpUrls

imageBasePath

ビルド時に画像パスを解決する際に使用するベースパスを指定します。(imgSrc, params) => '../img/' + imgSrc の形式で書く。

  • name: imageBasePath
  • valid options: STRING|FUNCTION
  • default: undefined
  • used by: Markdown

imageOptimizer

与えられた画像 src に対する srcset を計算する関数を提供して、画像 srcset の自動生成を有効にする。この関数は、srcset を返す。利用可能な画像がない場合は、falsy を返す。

ex.

const ampOptimizer = AmpOptimizer.create({
  imageOptimizer: (src, width) => `${src}?width=${width}`
});

lts

AMP ランタイムとコンポーネントで LTS バージョンを使うことができる。

- name: `lts`
- valid options: `[true|false]`
- default: `false`
- used by: `RewriteAmpUrls`

AMP は通常2週間おきにリリースされています。ロードしている JavaScript が変わるので、なにもしなくてもバージョンに追従することができます。一方で、バージョンアップ起因の不具合もときどき起こります。通常のリリースサイクルとは別に LTS バージョンも提供されているので、このオプションでより安定な AMP を使うことができます。現在の AMP のバージョンは https://cdn.ampproject.org/rtv/metadata でみることができます。

markdown

このオプションで Markdownファイルから AMP Valid な html を生成することができます。(少し語弊があるので後述)

README.md => HTML => AMP Optimizer => valid AMP

このオプションは <img> タグを amp-img または amp-anim タグに変換するだけです。実際のファイルから画像の寸法を解決しようとします。320pxより大きい画像は自動的に intrinsic レイアウトになります。画像検出を動作させるためには、dependency に probe-image-size が必要です。

  • name: markdown
  • valid options: [true|false]
  • default: false
  • used by: Markdown

実質 Markdown to html 変換器として使えます。

minify

html を minify する。

  • name: minify
  • valid options: [true|false]
  • default: true
  • used by: MinifyHtml, SeparateKeyframes

preloadHeroImage

ヒーロー画像の最適化を有効にします。ヒーロー画像は自動検出されるか、data-hero 属性で明示的にマークすることもできます。

<amp-img data-hero src="foo.jpg" ...>

data-hero でマークアップできるヒーロー画像の最大数は2枚です。

data-hero 属性が存在しない場合、amp-imgamp-iframeamp-video、または、amp-video-iframe から自動検出されます。画像のプリロードリンクは、既に存在しない場合にのみ生成されます。amp-img 要素の場合は、amp-img 要素内の img 要素もサーバーサイドでレンダリングします。これにより、画像のレンダリング性能が大幅に向上し、Core Web Vitals の Largest Contentful Paint (LCP) の改善に寄与します。

  • name: preloadHeroImage
  • valid options: [true|false]
  • default: true
  • used by: PreloadHeroImage

通常 amp-img 内の img はクライアントサイドでレンダリングされています。

verbose

ログを出力する

  • name: verbose
  • valid options: [true|false]
  • default: false

Transformer に踏み込む

実際に html ファイルの変換処理をしているのは Transformer です。前項の各オプションの used by にさりげなく書いてあるやつが Transformer です。デフォルトで使われる Transformer は決まっていますが、transformations オプションで自由に指定することもできます。Transformer を自作して使用することもできます。

const AmpOptimizer = require('@ampproject/toolbox-optimizer');
const {createElement, firstChildByTag, appendChild} = AmpOptimizer.NodeUtils;

class CustomTransformer {
  constructor(config) {
    this.log_ = config.log.tag('CUSTOM');
  }
  transform(tree, params) {
    this.log_.info('Running custom transformation for ', params.filePath);
    const html = firstChildByTag(tree, 'html');
    if (!html) return;
    const head = firstChildByTag(html, 'head');
    if (!head) return;
    const desc = createElement('meta', {
      name: 'description',
      content: 'this is just a demo',
    });
    appendChild(head, desc);
  }
}

// it's best to run custom transformers first
const customTransformations = [CustomTransformer, ...AmpOptimizer.TRANSFORMATIONS_AMP_FIRST];

// pass custom transformers when creating the optimizer
const optimizer = AmpOptimizer.create({
  transformations: customTransformations,
});
// you can add custom parameters on a per document basis
const transformedHtml = await optimizer.transformHtml(html, {
  filePath,
});

AMP First の場合 v2.6.0 ではデフォルトで使われる Transformer は以下です。Paired AMP の場合は少し異なります。Paired AMP は v2.6.0 時点で experiment feature みたいです。

  • AddMandatoryTags
  • Markdown
  • AutoExtensionImporter
  • OptimizeImages
  • PreloadHeroImage
  • ServerSideRendering
  • AmpBoilerplateTransformer
  • ReorderHeadTransformer
  • RewriteAmpUrls
  • GoogleFontsPreconnect
  • PruneDuplicateResourceHints
  • SeparateKeyframes
  • AddTransformedFlag
  • MinifyHtml
  • AmpScriptCsp

Transformer を読めば実際の変換処理を理解することができます。以降ですべての Transformer について説明します。

AddAmpLink

Valid AMP への参照を link タグを追加する。Paired AMP のデフォルトです。

<link rel=amphtml href=${ampUrl}>

AddBlurryImagePlaceholders

amp-imgamp-video のポスター画像のプレースホルダとしてぼかし画像を追加します。jpeg のみ対応です。ぼかし画像の作成は計算コストがかかるのでキャッシュ前提のオプションです。Paired AMP のデフォルトです。

使えるオプションは以下。

  • blurredPlaceholders: ぼかし画像プレースホルダの有効化。 デフォルトは false
  • imageBasePath: 画像のベースパス
  • maxBlurredPlaceholders: ぼかし画像の最大数。デフォルトは 5
  • blurredPlaceholdersCacheSize: キャッシュするぼかし画像の最大数。キャッシュを無効化する場合は 0、すべてのプレースホルダをキャッシュする場合は -1 を指定する。デフォルトは 30

ex.

const optimizer = AmpOptimizer.create({
  // blurry image placeholders are currently not considered valid AMP
  // hence it's recommended to setup paired AMP mode when enabling this feature.
  transformations: AmpOptimizer.TRANSFORMATIONS_PAIRED_AMP,
  blurredPlaceholders: true,
});

キャッシュは LRU キャッシュです。

AddMandatoryTags

必須 AMP タグを追加します。AMP のスタートガイドにあるようなタグが追加されると考えればいいです。後述しますが、あえて Invalid AMP にしたい場合は要注意です。

AddTransformedFlag

html 要素に transformed="self;v=1" 属性を追加します。これは AMP Validator に AMP Optimizer で変換した html であることを伝えるためのものです。

AMP Optimizer が登場した当初は AMP Optimizer で処理した html は AMP Valid ではありませんでした。現在は 変換後の html も AMP Valid です。

AmpBoilerplateTransformer

ServerSideRendering が適用されている場合、<style amp-runtime> が存在する。このタグに i-amphtml-version 属性を追加して、タグ内に https://cdn.ampproject.org/v0.css をインライン展開する。

ServerSideRendering に依存しているので、Transformer で指定するときは ServerSideRendering より後に書く。

AmpScriptCsp

amp-script に CSP を追加する。meta タグが追加される。

AutoExtensionImporter

AMP コンポーネントのタグを走査して、足りてない AMP extension (script タグ)を自動的に追加します。

GoogleFontsPreconnect

Google フォントが使われていたら、preconnect 用のコードを挿入します。ReorderHeadTransformer の後に指定する必要があります。

href が https://fonts.googleapis.com ではじまる link タグがあれば、<link rel="dns-prefetch preconnect" href="https://fonts.gstatic.com" crossorigin=""> を head 内の viewport meta タグの次に挿入します。https://fonts.gstatic.com は Google フォントの CSS 内に書かれているフォント URL のオリジンです。

Markdown

Markdown という名前がついていますが、この Transformer は <img> タグを amp-img または amp-anim タグに変換するだけです。ServerSideRendering より前に指定します。

典型的変換フローは以下のようになります。

README.md => HTML => AMP Optimizer => valid AMP

md ファイルから html への変換は markdown-it 等を使う必要があります。

MinifyHtml

html の最小化をする。インラインの JSON や amp-script も含めて最小化されます。

OptimizeImages

srcset の変換関数をもとに amp-img の srcset 属性を生成します。指定された width や画面幅をもとに srcset を計算しているようです。PreloadHeroImage の前に指定する必要があります。

PreloadHeroImage

ヒーロー画像の img タグをサーバーサイドレンダリングします。サーバーサイドレンダリングすることにより AMP ランタイムがロードされる前に画像を表示することができます。img タグは通常 amp-img によりクライアントサイドでレンダリングされます。

ヒーロー画像は自動検出されるか、data-hero属性で明示的に指定したものが採用されます。

PreloadImages

PreloadHeroImage のプロトタイプみたいな位置づけに見えます。機能が被っているので、積極的に使われてないかも。

PruneDuplicateResourceHints

Resource Hints (dns-prefetchpreconnectprefetchpreloadprerender)の重複をなくします。ReorderHeadTransformer の後に指定する必要があります。

RemoveAmpAttribute

html タグの AMP 属性 (amp⚡4adsamp4ads⚡4emailamp4email) を削除します。最小で Invalid AMP を作ることができます。限りなく Valid AMP で開発したいが、AMP Cache には載せたくないという用途に使えます。

RemoveCspNonce

script タグの CSP nonce を削除します。

ReorderHeadTransformer

head 内のタグをソートします。ServerSideRendering の後に指定します。

以下のようにタグをソートする

  • (0) <meta charset>
  • (1) <style amp-runtime>
  • (2) <meta charset> 以外の <meta>
  • (3) AMP ランタイム JS (<script>)
  • (4) render delaying な <script>
  • (5) extension の <script>
  • (6) favicon の <link>
  • (7) resource hints の <link>
  • (8) <link rel=stylesheet>
  • (9) <style amp-custom>
  • (10) AMP に許可されてる他のタグ
  • (11) AMP boilerplate

RewriteAmpUrls

AMP ランタイムの URL を書き換える。ReorderHeadTransformer の後に指定する。

ex. https://cdn.ampproject.org/v0.js
https://cdn.ampproject.org/rtv/[ランタイムバージョン]/v0.js に変換

使えるオプション

  • ampRuntimeVersion: ランタイムバージョンを指定 ex. 001515617716922
  • ampUrlPrefix: ランタイムの URL プレフィックスを指定。ランタイムのセルフホスティングで使える
  • lts: true で LTS バージョンになる。 ampRuntimeVersionampUrlPrefix とは非互換

SeparateKeyframes

CSS の keyframes、 media、 supports を amp-custom の style タグから amp-keyframes の style タグに分離する。 amp-keyframes は body の最後に追加。

ServerSideRendering

sizes 属性をメディアクエリに変換する ApplyCommonAttributeslayout 属性を処理する ApplyLayout があります。 AMP boilerplate を削除する処理もこの Transformer に含まれています。AMP Boilerplate が削除されると html タグに i-amphtml-no-boilerplate が付きます。 AMP Boilerplate については後述します。

ApplyCommonAttributes

sizes 属性を CSS のメディアクエリに変換する。

ApplyLayout

AMP コンポーネントの layout、 width、 および height 属性をもとにレイアウトを確定させます。AMP コンポーネントが現在サポートしている layout 属性は 8種類 (nodisplayfixedfixed-heightresponsivecontainerfillflex-itemintrinsic) です。 layout 属性の種類と width、height 属性の値をもとに実際の width、height を計算して、該当要素の style 属性に追加します。このとき layout 属性がある要素に i-amphtml-layout-[layout の属性値] 属性がつけれられます。layout 属性値が fixedfixed-heightresponsiveflex-itemintrinsic の場合、i-amphtml-layout-size-defined 属性も付与されます。nodisplaycontainerfill の3種類はサイズを定義する layout ではないので付与されないのだと思います。

Best Practices

AMP Boilerplate を削除する

AMP Boilerplate とは <style amp-boilerplate> のことです (ref. https://amp.dev/ja/documentation/guides-and-tutorials/start/create/basic_markup/?format=websites )。ここに書かれているスタイルは、最大 8秒間画面を真っ白 (body の visibility を hidden) にするものです。AMP ランタイムである v0.js がロードされて、実行されると visibility: hidden が解除されて画面が見えるようになります。つまり、ランタイムが実行されるまでは何も表示されないのでパフォーマンス面で懸念があります。しかし、AMP は JavaScript が実行されないと UI のレイアウトが固定されないので必要なスタイルであると言えます。

このパフォーマンス劣化を解決するために、AmpOptimizer のサーバーサイドレンダリングが使えます。 ServerSideRendering Transformer を使って、レイアウトをサーバーサイドで確定しておきます。すると、AMP Boilerplate のタグは不要になるので削除することができます。この機能には制限があって、以下のコンポーネントが使われていると AMP Boilerplate を削除することができなくなります。

  • amp-experiment
  • amp-story
  • amp-dynamic-css-classes

可能であれば静的ページ生成する

AMP Optimizer の処理は計算コストが高く、リクエストごとに都度最適化していてはサーバーの負荷が高くなり、パフォーマンスも悪くなります。可能であればビルド時に静的ページを生成しておきます。

CDN でキャッシュする

静的ページ生成とも関連しますが、可能であれば最適化した html を CDN でキャッシュさせます。AMP Cache も同様の方式です。AMP Optimizer は最適でも CDN キャッシュ前提の処理をしていると考えた方がいいと思います。

Experimental Features

AMP ランタイムのバージョン固定

ampRuntimeVersion オプションでランタイムのバージョンを固定することができます。変換すると以下のコードが、

https://cdn.ampproject.org/v0.js

以下のように変換されます。

https://cdn.ampproject.org/rtv/001515617716922/v0.js

AMP の更新の影響を受けたくないときに使えます。lts オプションと同時に使うことはできません。

const ampOptimizer = require('@ampproject/toolbox-optimizer');
const ampRuntimeVersion = await runtimeVersion.currentVersion();

// The input string
const originalHtml = `
<!doctype html>
<html ⚡>
...
`

// Additional options can be passed as the second argument
const optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {
  ampUrl: 'canonical.amp.html',
  ampRuntimeVersion: ampRuntimeVersion
});

console.log(optimizedHtml);

AMP コンポーネントのセルフホスティング

AMP フレームワークやコンポーネントのインポートを cdn.ampproject.org とは別のドメインに書き換えることが可能です。

const ampOptimizer = require('@ampproject/toolbox-optimizer');

// The input string
const originalHtml = `
<!doctype html>
<html ⚡>
...
`

// Additional options can be passed as the second argument
const optimizedHtml = await ampOptimizer.transformHtml(originalHtml, {
  ampUrl: 'canonical.amp.html',
  // this will rewrite https://cdn.ampproject.org/v0.js to /amp/v0.js
  ampUrlPrefix: '/amp'
});

console.log(optimizedHtml);

AMP CDN にも依存しなくなるので、AMP の影響を完全に排除することができそうです。

さいごに

AMP Optimizer の Transformer コードをみていると、Web のパフォーマンスのベストプラクティスを学べるような気がします。

できればこの記事で AMP Optimizer の更新に対応していきたい。

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
23
Help us understand the problem. What are the problem?