Developing Visual Stories at Ameba

## はじめに

昨年10月ごろに僕が内定先のCyberAgentのアメーバブログで内定者アルバイトをしていた際に、AmebaブログのブログランキングページをAMP Storiesとして公開する試み、また、個人ブログのAMP Stories版の開発を行いました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">本日のAmebaオススメブログランキングは🍱お弁当づくり🍱<br><br>美味しそうな「お弁当写真」は見ているだけで幸せな気分になります😊🍙　毎日の <a href="https://twitter.com/hashtag/%E3%81%8A%E5%BC%81%E5%BD%93?src=hash&amp;ref_src=twsrc%5Etfw">#お弁当</a> づくり、 <a href="https://twitter.com/hashtag/%E9%81%8B%E5%8B%95%E4%BC%9A%E5%BC%81%E5%BD%93?src=hash&amp;ref_src=twsrc%5Etfw">#運動会弁当</a> の参考にもしてみてください✨<br><br>ランキングを動画でチェック→<a href="https://t.co/hwZhLnpd7I">https://t.co/hwZhLnpd7I</a></p>&mdash; アメブロトピックス (@ameba_official) <a href="https://twitter.com/ameba_official/status/1174922129185234944?ref_src=twsrc%5Etfw">September 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

UI/UXのためのパフォーマンスの最適化やアクセシビリティの観点での開発は [Designing Visual Stories at Ameba](https://developers.cyberagent.co.jp/blog/archives/24105/) で紹介されているため、この記事では、技術スタック等についての紹介を行います。

（それと、卒論からの解放感で完全に書くのを忘れてしまって、書く書く詐欺してしまってましたが遂に書きました！！🙏）

## 開発の始める際の温度感
開発を始める際のプロダクトに対する温度感は次のような状態でした。

- アメーバブログでAMP Storiesを導入してみたい
- 実際にプロダクションで導入されるかはまだ決まっていない
- そもそもエンジニア・デザイナーともにどんなUI/UXを提供できるかわかっていない
- PugベースのAMP Storiesでの表現を試すためのsandboxのみある

## 技術選定に対する方針
これらの条件を元にして、技術選定に対する方針を次のように考えていました。

- AMP Storiesでどんな表現ができるのかを調査し、デザイナーに説明する段階では、既存のPugベースの構成で一旦試す
- AMP Storiesの技術特性や、デザイナーがデザインを作れる段階になっても、実際にプロダクションで大規模に展開していくかはわからないのでスモールスタート
    - 大規模なビルドシステムや過度な抽象化は行わず、最短で変更に対応していけるようにする

## 使用した技術スタック

### [AMP Stories](https://amp.dev/documentation/components/?format=stories)
AMP Storiesの開発の基本となる、コンポーネント群です。
[先日の記事](https://qiita.com/Shagamii/items/c0c696d349e6dea8b9ba#amp-stories-%E3%81%A8%E3%81%AF)のように、決められたフォーマットに従い、アニメーションのattributeを設定することで簡単にストーリーを作成することが出来ます。

![AMP Storiesの基本的な要素の構造](https://amp.dev/static/img/docs/tutorials/amp_story/story_parts.png)
![AMP Storiesの基本的なHTML構造](https://amp.dev/static/img/docs/amp-story-tag-hierarchy.png)

### React(JSX)
開発する上でのテンプレートとして使用しました。
APIから取得したデータをReactに渡して、SSR([renderToStaticMarkup](https://ja.reactjs.org/docs/react-dom-server.html#rendertostaticmarkup))し、その文字列を静的なHTMLとして書き出しています。
そのため、アプリケーション上でReactが動いている訳ではありません。(今後、本記事で登場するSSRという文言はrenderToStaticMarkUpを意味します。)
もともと、Pugでの開発環境が用意されていましたが、自身がPugに慣れていないことや、同じチームの他のプロダクトではReactが使われているため、基本的にチーム全員がReactを書くことができる、そして、個人的に全てがJavaScriptの世界で完結する書き味が好きで採用しました。
Function Componentをベースにして、Componentを積み上げていくだけですので、普段のReactを書く際と同じようなDXを得ることが出来ます。

<img alt="React(JSX)を使った際のコード例" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129548/256f5c3a-14a3-b8ae-f4de-765757124133.png" width="480" />


### styled-components
テンプレートとしてのReactを書いていく上で、CSSはstyled-componentsを採用しました。
AMPではCSSをインラインで記述する必要があります。
styled-componentsでは、SSR時に使用されたCSSを集めて、文字列として書き出すことが出来ます。([styled-components/Server Side Rendering](https://styled-components.com/docs/advanced#server-side-rendering))
そのため、書き出されたCSSの文字列をHTMLのヘッド内に追加できるような構造しておくことで、webpack等で別途CSSファイルを書き出しておく必要もなく、HTMLのビルドをnodeのランタイムで完結させることが出来ます。

<img alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129548/8adb3bd6-eedf-6ecc-be93-b035e7759f83.png" width="550" />

### TypeScript
スモールスタートのプロダクトではありましたが、やはり型定義によるアノテーションがあると開発速度がかなり上がりますので採用しました。

特に返ってくるAPI周りのデータ型やReactコンポーネントに取得したデータを渡す際のpropsに関してはしっかり書いておくことで、フェーズが進んでも改修の必要が最低限になるように意識しました。
これに加えて、どのデータも同じpropsのデータ型になるように成形するような工程を用意しておくことで、開発フェーズが進み、当初予定していたジャンル別ランキングページから他のページへの展開が決まった際、必要とするAPIが異なっていましたが、propsのデータ型を変更し、それに伴ってUIレベルでのタイプガードを追加することにより、様々なページへの対応が可能となりました。

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129548/6cb9c826-dd73-f4d5-b796-4f6ed26334a8.png alt="" width=1080 />

### [react-amphtml](https://github.com/dfrankland/react-amphtml)

JSXでは、小文字で始まるHTML要素は文字列と認識されるため、JSX内にAMPコンポーネントのタグをそのまま記述するだけで、HTMLの要素として記述することができます。
一方で、AMPコンポーネントは、JSXにデフォルトで組み込まれたHTML要素ではないため、JSXの型定義として存在しておらず、[intrinsic elements](https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements)として型定義を追加する必要があります。
また、styled-componentsもAMPには対応しておらず、AMPコンポーネントをラップした関数をstyled関数の引数にとる必要があります。
これを解決するために、 [react-amphtml](https://github.com/dfrankland/react-amphtml) という [AMP validator](https://validator.ampproject.org/) を基にAMPコンポーネントをReactコンポーネントとしてラップした薄いライブラリを使用しました。
このライブラリはstyled-componentsにも対応しており、用意されたコンポーネントをstyled関数の引数に取るだけで、CSSの記述が可能です。
しかし、attributeの型はほぼなかったため、よく使うAMPコンポーネントも限られていたこともあり、AMPコンポーネントのドキュメントを読み最低限使うattributeをその都度、[intrinsic elements](https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements)に追加していきました。

```js

import styled from 'styled-components'
import * as Amp from "react-amphtml";

const Thumbnail = styled(Amp.AmpImg)`
  border-radius: 4px;
`
```

### ビルド時にはnode.jsのランタイムのみ
ビルドシステムを出来るだけ単純にしたかったため、webpack等を使わずに、node.jsのランタイムでビルドするように設計しました。
この際の流れとしては、簡潔に以下のようになります。

1. 既存のAmebaのAPIサーバーからデータを取得し、Reactにpropsとして渡す。
2. Reactをテンプレートとして、ReactDOMServer.renderToStaticMarkupした文字列をもとに、HTMLを作成する。
3. 作成したHTMLをS3にアップロードする。

<img alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129548/8944bb7b-e537-289c-6dc4-40186df668b3.png" width="700" />

## 最後に
AMP Storiesの開発で使用した技術は以上となります。
やはり、アプリでReactを使わなくても、テンプレート(JSX)として書くのは、普段と同じ知識で書けますし、JavaScriptの世界線で書けるので気持ちいいですね！
また、特別な技術を使わずにこれまで使われてきた技術を基にして普段とは少し異なる構成のプロダクトを作ることで、それまでの自身の経験やチームの他の方からの知見をもらうことができ、プロダクト（成果物）の質をよくするために時間を割くことができました。

最近でも、僕自身は直接関わっていませんが、AMP Storiesを利用したアメーバブログの取り組みがリリースされており、どんどん品質がよくなり魅力的になってきていますので、こちらもチェックしてみてください！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">💐特別コンテンツも💐<br><br> <a href="https://twitter.com/hashtag/BLOGoftheyear2019?src=hash&amp;ref_src=twsrc%5Etfw">#BLOGoftheyear2019</a> 特設サイトでは<br>受賞者の人気記事TOP10をストーリー形式でご紹介🌟<br><br>トップの丸アイコンをタップすると見られます🎥<br><br>ぜひチェックしてみてください😊<a href="https://t.co/wPzsVuy7K1">https://t.co/wPzsVuy7K1</a><a href="https://twitter.com/hashtag/%E3%83%96%E3%83%AD%E3%82%B0%E3%81%AE%E6%97%A5?src=hash&amp;ref_src=twsrc%5Etfw">#ブログの日</a></p>&mdash; アメブロトピックス (@ameba_official) <a href="https://twitter.com/ameba_official/status/1225226915356635136?ref_src=twsrc%5Etfw">February 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


## 関連リンク
- [Ameba launched their article landing pages as AMP Stories](https://amp.dev/success-stories/ameba/)
- [Designing Visual Stories at Ameba](https://developers.cyberagent.co.jp/blog/archives/24105/)
- [お弁当づくりジャンル | Ameba公式ジャンル](https://blogger.ameba.jp/ampstories/genres/bento/blog-ranking.html)
- [BLOG of the year 2019(ブログオブザイヤー2019) | Ameba（アメーバ）](https://content.ameba.jp/blogoftheyear/2019/)