JavaScript
静的サイトジェネレーター
reactjs
縦書き
gatsby

静的サイトジェネレーター Gatsby を使ってみた事と、縦書きテンプレートを作った話

Gatsby を使ってみた事と、縦書きテンプレートを作った話

image.png

作ったのはこんな感じです

( 既に執筆中にもドキュメントが大きく変更されているが、一部更新できていない。間違いなど何かあったらコメント欄にお願いします )

Gatsby

Reactjs 製の静的サイトを簡単に作成できるツール

https://www.gatsbyjs.org/

Get Start

まずは、通常の Gatsby プロジェクトを作成する

環境構築

  • Windows 10 Pro
  • nodejs - 9.5.0
  • npm - 5.6.0

プロジェクト作成

PS> npm install -g gatsby-cli
PS> gatsby new weblog
PS> cd weblog
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├── gatsby-ssr.js
├── LICENSE
├── package.json
├── public
│   ├── index.html
│   ├── render-page.js.map
│   └── static
├── README.md
├── src
│   ├── components
│   │   └── Header
│   │       └── index.js
│   ├── layouts
│   │   ├── index.css
│   │   └── index.js
│   └── pages
│       ├── 404.js
│       ├── index.js
│       └── page-2.js
└── yarn.lock

Launch Develop Server

PS> yarn run develop

image.png

image.png

Build

PS> yarn run build
public
├── 404
│   └── index.html
├── 404.html
├── app-fdbfd90de30d9bb2f8ed.js
├── app-fdbfd90de30d9bb2f8ed.js.map
├── build-js-styles.css
├── build-js-styles.css.map
├── chunk-manifest.json
├── commons-61689ac9f897bb0fcb2f.js
├── commons-61689ac9f897bb0fcb2f.js.map
├── component---src-layouts-index-js-00f5c2e25cfe978dd214.js
├── component---src-layouts-index-js-00f5c2e25cfe978dd214.js.map
├── component---src-pages-404-js-4503918ea3a16cfcdb75.js
├── component---src-pages-404-js-4503918ea3a16cfcdb75.js.map
├── component---src-pages-index-js-517c35727b7bc8c2b42f.js
├── component---src-pages-index-js-517c35727b7bc8c2b42f.js.map
├── component---src-pages-page-2-js-65f0147ecb0dc4da2a2b.js
├── component---src-pages-page-2-js-65f0147ecb0dc4da2a2b.js.map
├── index.html
├── page-2
│   └── index.html
├── path---404-a0e39f21c11f6a62c5ab.js
├── path---404-a0e39f21c11f6a62c5ab.js.map
├── path---404-html-a0e39f21c11f6a62c5ab.js
├── path---404-html-a0e39f21c11f6a62c5ab.js.map
├── path----557518bd178906f8d58a.js
├── path----557518bd178906f8d58a.js.map
├── path---index-a0e39f21c11f6a62c5ab.js
├── path---index-a0e39f21c11f6a62c5ab.js.map
├── path---page-2-a0e39f21c11f6a62c5ab.js
├── path---page-2-a0e39f21c11f6a62c5ab.js.map
├── static
├── stats.json
└── styles.css

手軽さは体感できた。

Deploy

Deploying Gatsby

開発時の確認用

公式サイトでは、開発用の静的サイトホスティングサービスとして surgeお薦めされている。

Netlify よりもシンプルで、Now.sh の静的サイトホスト版といった使い心地。

PS> yarn add -D surge
package.json
...
  "scripts": {
+    "deploy": "surge public/"
...
PS> yarn run deploy

# create account at the first deployment

2018-03-01_15h22_44.png

と、ここまでで流れは掴めたので、一旦動作確認は終わり。

コンセプト

コンセプトをざっと見ていく。

◆ Lifecycle

実は当初、これが上手く理解できない為に、Gatsby 全体の理解が進まなかった。
ので、真っ先にここを掴むのが良いと思う。

https://www.gatsbyjs.org/docs/gatsby-lifecycle-apis/

gatsby.png

Gatsby は、大まかに分けて Build, SSR, On Browser と3つのフェーズが存在している。

Build

gatby build を行うと、Gatsby の設定読み込み, 必要なファイルのコピー, ページ生成 ( Component の動的生成 ), webpack による Build, GraphQL によるデータ取得と埋め込み, 各種 Plugin による処理等々が逐次行われる。

Gatsby では、それらの処理に対し、動作のカスタマイズや各処理イベントをフックするハンドラを登録する為の Gatsby Node APIs が提供されている。
プロジェクトルートにある gatsby-node.js に記述する。

https://www.gatsbyjs.org/docs/node-apis/

webpack の設定変更やページのカスタマイズが可能。

SSR

gatby build の最後に、各ページを React Component として動作 ( renderToString ) させ、初期表示用の静的 HTML が生成される。

この部分の動作のカスタマイズや各処理イベントをフックするハンドラを登録する為に、Gatsby SSR APIs が提供されている。
プロジェクトルートにある gatsby-ssr.js に記述する。

https://www.gatsbyjs.org/docs/ssr-apis/

redux 等で初期状態を渡したい場合はここで注入する事になる。

On Browser

一度 SSR されて抜け殻になった HTML は、クライアントのブラウザ上で React Component として息を吹き返す ( Hydration )。

ブラウザ実行時における、Gatsby の各種動作をカスタマイズする Gatsby Browser APIs もある。
gatsby-browser.js に記述する。これだけ、ブラウザ上で実行されるので注意。

https://www.gatsbyjs.org/docs/browser-apis/

Redux 等の状態管理ライブラリと Connect するならここで行う。

Note: 各 API の呼び出し時に渡される boundActionCreators は、Gatsby の内部で利用されている Redux の ActionCreator の連想配列である。Gatsby の内部状態を変更する際に利用される。詳しくはこちら

◆ React Components

Gatsby は、何と言っても Reactjs で静的サイトが作成できるのが最大のポイント。

https://www.gatsbyjs.org/docs/building-with-components/

Components は src 以下に配置されているが、そのフォルダによって役割が違う。

/src/pages/*.js

ページに対応する Component を配置する。
ファイルパスがそのまま URL に変換される ( about.js/about )。
ここは Nextjs と同じ。

Build 時には、SSR され初期表示用の HTML に変換される。

ブラウザ実行時は、ただの React Component として実行される。
State をもたせることも Interactive に動作させことも当然可能。

/src/templates/*.js

Markdown 等で書かれた文章を埋め込むための Template を配置する。
Markdown の埋め込み方法は後述。

/src/layouts/index.js

header や footer 等の外枠になる共通コンポーネント。
react-helmet で header 内をカスタマイズするのもここで行われている。

後述する /src/html.js との使い分けとしては、こちらはブラウザ実行時にも React Component として存在することができるので、動的書き換えが可能ということ( SSR 時に renderToString され、ブラウザ上で Hydration される )。

/src/components/*.js

gatsby new で作成されたプロジェクトには存在するが、ただの Component 置き場。
Gatsby 的に特別な意味があるわけではない。

/src/html.js

<head> 領域を独自にカスタマイズしたい場合は、これを配置する。
metadata 等のカスタマイズが必要となったら、第一の選択肢としてこれを選ぶべき、らしい。

Build 時には既にただの HTML に変換されてしまう為、動的書き換えはできない ( SSR 時に renderToStaticMarkup される )。

◆ GraphQL

Gatsby を始めて見た時、ドキュメント内で GraphQL が使われまくっていて疑問に思った。静的サイトを生成するはずのツールが、何故実行時のデータ取得にまで関わってくるのか、と。

これは完全に勘違いで、ブラウザ実行時にサーバからデータを取得する目的で使われる GraphQL とは違うもの だった。

https://www.gatsbyjs.org/docs/querying-with-graphql/

データとは

Gatsby では、React の外側にあるリソース ( 画像, Markdown ファイル, 設定ファイル内の情報, 外部 API ) 全てを データ と捉えている。

データへの統一的なアクセス

それらデータを問い合わせる為の統一的な方法として、GraphQL が利用されている。
開発サーバを立ち上げ、http://localhost:8000/___graphql にアクセスすると見られる。

image.png

以下なんかを見ると、GraphQL のパワーを感じることができる。
Programmatically creating pages from data - Gatsby.js Tutorial Part Four

Node

データの1つのまとまり ( Model ) を、Gatsby では Node という単位で扱う。
実装的には、Schema の Interface として各リソースデータが実装する。

https://www.gatsbyjs.org/docs/node-interface/

image.png
File という Node の Schema

一部 Plugin は、 Node を追加する形でデータを提供する。

Build 時の挙動

GraphQL は Build 時に実行され、取得されたデータは全て埋め込まれたり解決された状態で静的コンテンツ化されるため、Production 時に別途 GraphQL サーバを用意する必要はない。

Note : 埋め込むと言っても、必ずしもインライン展開されるということではなく、JSON ファイルに書き出して、表示時に Ajax で取得し動的にコンテンツをクライアント生成することもあるようだ。

ただ、ブラウザ動作時にも別の GraphQL サーバを使うとなると、紛らわしくないんだろうか??
公式では推奨されているけども。

Gatsby is a great framework for building apps so it’s possible and encouraged to pair Gatsby’s native build-time GraphQL with GraphQL queries running against a live GraphQL server from the browser.

◆ Plugins

Gatsby の本体はシンプルなので、Plugin で拡張していくのが基本。

https://www.gatsbyjs.org/docs/plugins/
https://www.gatsbyjs.org/docs/plugin-authoring/

Plugin の実体は、依存パッケージの取得 ( package.json ) と Lifecycle APIs へのハンドラの追加 ( gatsby-*.js ) である。

gatsby-source-*

外部取得できるデータを追加する為の Plugin。

  • gatsby-source-hacker-news
  • gatsby-source-google-sheets
  • etc ...

データは Node として提供され、GraphQL から利用可能となる。

https://www.gatsbyjs.org/docs/node-interface/#source-plugins
https://www.gatsbyjs.org/docs/create-source-plugin/

gatsby-transformer-*

主にファイルを読み込んで、そのデータを JavaScript Object に変換する為の Plugin。

  • gatsby-transformer-yaml
  • gatsby-transformer-excel
  • etc ...

これも、Node として提供され、GraphQL から利用可能となる。

https://www.gatsbyjs.org/docs/node-interface/#transformer-plugins

gatsby-remark-*

gatsby-transformer-remark の為の拡張。つまり拡張の拡張。

  • gatsby-remark-embed-snippet
  • gatsby-remark-emoji
  • etc ...

gatsby-plugin-*

それ以外の Plugin。

  • Compiler, Transpiler 系
    • gatsby-plugin-sass
    • gatsby-plugin-typescript
  • React サポート系
    • gatsby-plugin-styled-components
    • gatsby-plugin-typography
  • その他
    • gatsby-plugin-feed
    • gatsby-plugin-google-fonts
    • gatsby-plugin-google-analytics
    • gatsby-plugin-i18n
    • gatsby-plugin-sentry

◆ PRPL ( PWA )

PWA も良しなにやってくれるらしい。ここは余り確認していない。

https://www.gatsbyjs.org/docs/prpl-pattern/
https://www.gatsbyjs.org/blog/2017-09-13-why-is-gatsby-so-fast/

Style

デザイン周りも気になったので、調べた。

Components Style

一通りのデザイン手法は揃えてある

Global Style

Global Style としては、公式サイトでは Typography.js をゴリ押ししてくる。

Markdown の記事化

Gatsby をもう少し使いやすくする為、Markdown で記事を書けるようにする。

PS> yarn add gatsby-source-filesystem gatsby-transformer-remark
gatsby-config.js
module.exports = {
  siteMetadata: {
    title: 'Gatsby Starter Blog',
    author: 'kentork'
  },
  plugins: [
    'gatsby-plugin-react-helmet',
    {
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: []
      }
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
        name: 'pages'
      }
    }
  ]
}

これで、src/pages 以下にフォルダを作成し、その中に index.md というファイルを配置する事で、記事を書くことができるようになる。

.
└── src
    └── pages
        ├── 404.js
        └── hello-world
            ├── index.md
            └── my-image.jpg

記事中で使用する画像も、同じフォルダに配置すれば、index.md からの相対パスで表示させることが可能

index.md
![My Image](./my-image.jpg)

このフォルダ名がそのまま記事の URL となる。この場合だと、http://my-domain/hello-world にマッピングされる。

ツール使い分け

似たようなツールが多い中、どんな場合に使うことが適当なのか。

コンテンツをとにかく迅速に手間なく公開したい、デザインは Template + α で良い、となったら、HugoHexo で十分。

構造化されたドキュメントが欲しい場合は、Docusaurus などの専門ツールが良さそう。

認証やら API 集約やらキャッシュやら、本格的に BFF 的な何かが欲しいなぁと思ったら、Next.js 辺りで始めれば良い。

Gatsby は、それらの狭間にある悩ましい領域を任せられる可能性がある。
何となく始めていたいくつかのプロジェクト、 実は Gatsby で十分かもしれない。

代替ツール

Phenomic

https://phenomic.io/

Gatsby より構成要素が少なく、学習コストは低く抑えられそう。
そして何より、Reasonml の Template があるので注目している。

https://github.com/phenomic/phenomic/tree/master/examples/reason-react-app/

Reasonml についてはこちらを参考に。
Reason 使った感想 on Windows

react-static

https://nozzle-react-static.netlify.com/

使ったこと無いけど、こんな感じらしい。

https://medium.com/@tannerlinsley/%EF%B8%8F-introducing-react-static-a-progressive-static-site-framework-for-react-3470d2a51ebc

テンプレート作成

概念的な部分も理解できてきたので、試しに一つテンプレートを作成してみる。

コンセプト

日本語の縦書きテンプレート を作った。

https://github.com/kentork/gatsby-starter-tategaki

デモサイト

image.png

image.png

各ブラウザでも縦書きに必要な機能は整ってきてはいるが、あと一歩痒い所に手が届かない状況だった。

レンダリングのばらつきやブラウザ間での解釈の違いも未だ存在し、どうしても JavaScript に頼らなければいけない部分も多く、ノウハウもあまり共有されていない。
しっかり設計された企業サイト以外での採用は少なかった。

その点 Gatsby は、Markdown で記事を簡単に書けるユーザビリティを維持しつつ、React をフルに使ってプログラマブルな拡張性を持てるので、コレを使えば Markdown で書ける縦書きブログができないだろうかと考えた。

主な開発は Chrome で行っているが、一応 Firefox と Edge は動作確認している。

参考資料

そもそも縦書きはどの様な技術を使って実現するのかは、以下サイトにまとまっていたのでまず全体にさっと目を通した。

その他、以下記事なども参考になった。

以下、苦労した点と対応を簡単にまとめる。

課題 1. top, bottom, left, right の向き

CSS で縦書き表示をするためには、writing-mode: vertical-rl; を指定する必要がある。

この時、line-height letter-spacing text-align 等のテキストに対する指定は、直感通り方向を 90 度回転した指定に解釈される。

しかし、marginpadding 等の レイアウトに対する指定は、勝手に 90 度回転して解釈してはくれない

CSSの上下 - CSS3で縦書きにする方法と挙動

テキストレイアウトを担うフレームワーク・テンプレートのほとんどはこれに対応できない。
すなわち、使えないのである。

( 一部、Sass や PostCSS の変数を利用して値のみカスタマイズできるものもあるが、そもそも margin-bottom などと一方向のみに指定されている場合は、全てを margin-left に書き換えなければならないことには変わらない )

対策

Gatsby は、typography.js というほぼフルカスタム可能なテーマライブラリを推奨しているので、typography.js のデフォルトテーマをベースに方向指定プロパティを上書きした独自テーマを作成した。

https://kyleamathews.github.io/typography.js/

課題 2. スクロール

縦書きのページを作る場合、ユーザがどの方向に向かって記事を読んでいくのか という点で、2 つの方法が考えられる。

1. 上から下

通常の横書きページ同様、スクロール方向は従来と変えず上 → 下のままにし、縦書きテキストは新聞や雑誌のように 段組み をする事で対応するケース。

テキストレイアウト以外のレイアウトはほとんど変える必要が無くなるため、取り入れやすい方法である。
たてよこ Web アワードにある受賞サイトも、ほとんどがこれである。

少し前に話題になった以下記事でも、段組みされている。

縦書きでレスポンシブなブログを作った

2. 右から左

段組みをせずに縦書きテキストはそのまま右 → 左で流し、スクロールの方を横方向に変換するケース。

固定ヘッダやフッタなら問題ないが、それ以外はレイアウトを一から考え直す必要があるため、とてもハードルが高い。
横固定 + 縦可変が前提のコンポーネントはほとんど諦めなくてはいけない。

縦書きを極めるのならば、やはりこちらであろう。

対策

まずは、縦ホイールの横スクロール化が必要と考えた。
onWheel イベントから移動幅取得し scrollLeft に渡すだけで難しくはないが、それだと滑らかな動作にならない。

結局、react-motion を使って滑らかにスクロールするようにした。
まだ調整中で、少しおかしな動きをすることがある。

課題 3. 画像のレスポンシブ対応プラグインの挙動

Gatsby には、Markdown の画像タグ生成を担当する gatsby-remark-images というプラグインがある。
こいつは、レスポンシブ対応 (max-width:100%, srcset) や、画像の読み込み完了まで低解像度 Base64 画像で領域確保する機能などを持っている。通常なら、とても便利な機能だ。

残念なことに、レスポンシブ対応は親要素横幅に合わせるのが前提 で、領域確保機能は JavaScript で margin-bottom 等がごりごりと書かれている

スクロールが横方向のため、コンテンツが横に長くなる。
それに合わせてレスポンシブに画像幅を決めているものは全く効かなくなる。

対策

gatsby-remark-images-tategaki というプラグインを作った。

Gatsby は、ルートフォルダに置いた plugins フォルダに置いた package.json を含むプロジェクトをプラグインと認識してくれるので、結構手軽に追加できる。

レスポンシブ対応としては、vw に合わせて画像幅を決定するようにしている。
( 最初、親要素縦幅に合わせる実装を試してみたが、スマホのような縦長環境では画像が大きすぎて画面に入り切らなくなるので諦めた )

領域確保機能は、色々難しかったので書き直した。

課題 4. 単語ぶつ切り問題

これは、縦書きだけの問題ではないが、日本語は英語とは違って単語の間がスペースで区切られていない為、ブラウザは何も考えずに単語ぶつ切りで表示する。

毎日のように見ている横書日本語では違和感をあまり感じなかったはずなのに。
縦書きにした途端、気になりだした。
( とはいえ、新聞も雑誌もぶつ切りだが違和感無いので、きっと段組みされていると違和感を感じないんだろう )

対策

gatsby-remark-wordbreak というプラグインを作った。

形態素解析器である kuromoji.js を使って分解し、適当な部分で区切る様にしている。
始めは、mikan.js が軽量でよかったので検討したが、やはり単語分割の質という点で kuromoji.js の方を採用した。

https://github.com/takuyaa/kuromoji.js
mikan.js : 機械学習なしで、日本語の単語の改行を処理するライブラリを書いた

また、単語分割のための <wbr/> という素晴らしい専用タグが用意されているが、残念ながら日本語でまともに動くのは Chrome 位なもんで、その場合は <span> で区切る事になる。

wbr 要素 - MDN
<wbr>を使わずに、改行するならここでして欲しい指定の仕方。

課題としては、kuromoji.js は辞書データ取得・展開があるためか、npm install npm run build のコストがとても高いことが悩ましい。
Gatsby の createPage イベントにかかる時間が、kuromoji.js 入れる前と後で数倍に膨れ上がっている。

課題 5. フォントガタガタ問題

これは原因が定かではないけど、writing-mode: vertical-rl; すると、欧文フォントがガタガタになる。

最初は Segoe UI を指定していたけど、これが特にガタガタ。
アンチエイリアスのかかり方が変わってる? のか、特に w が波打って見える。

image.png
↑ Windows 10 + Chrome で縦表示した Segoe UI

対策

ひとまず欧文フォントを縦になっても見えやすい Roboto に変更した。

課題 6. 游ゴシック問題

これも縦書き関係ない話ではあるが、Win, Mac 共通で使える綺麗で読みやすいフォントとして期待されている 游ゴシック を使いたい。が、こいつが少し癖がある。

その一つが、Windows の Chrome での表示が細すぎる というもの。

2020年まで使えるfont-familyおすすめゴシック体
2018年のfont-family指定

上記参考サイトに則り、@font-face を定義することで回避できと思っていたが、できなかった。

游ゴシックのfont-family指定方法2018年度版!@font-faceは絶対NG!WindowsのChromでかすれる問題徹底検証

どうやら、Chrome 62 以降でこの手法は使えなくなってしまったらしい。

解決策

大人しく font-weight: 500 を指定した。

縦書き感想

ひとまず技術的にどこまでできるのか、について探求した。
デザインや便利機能はできるだけ削ぎ落とし、本質部分のみとなっている。

サンプルが青空文庫ばかりだが、文学作品を読むのにはやはり縦書きが似合うと感じた。
日記などにも合いそう。写真メインは辛そう。コードは論外。
自分自身は技術情報しか書かないので、多分このテンプレートは使わないだろう。

フィードバックあれば Github の方へ下さい。

しかし、縦書きについて調べていて驚いたのは、既に中国も韓国も縦書きを使わなくなってきているという事。台湾はまだ使うらしいが、香港も横書き化が進んでいるとか。
Web 標準機能として、今後縦書き機能は残されていくのだろうか。

パフォーマンスチューニング

デモサイトを元に、Lighthouse を使ってチューニングしていく。

原因 1. Web フォント問題

image.png

これはコーディングしている時点で、ここは引っかかるだろうなぁと思っていたので、まっさきに対応した。

対策

Gatsby のテンプレートの多くは typography.js で Google Fonts を読み込む設定をしているが、これは使わないのが最近の Gatsby 流らしい。

gatsby-plugin-typography omitGoogleFont option and README

その代わりに推進しているのが、Typefaces という npm で font を管理してしまおう という試み。

https://github.com/KyleAMathews/typefaces

npm install typeface-XXX して、import 'typeface-XXX' するだけ。
有名所は一通りあるので、Webpack 使うならこれでも良いのかなぁと思う。

https://www.npmjs.com/package/typeface-roboto
https://www.npmjs.com/package/typeface-open-sans
https://www.npmjs.com/package/typeface-lato
https://www.npmjs.com/package/typeface-source-sans-pro

感想

Gatsby は、全力でプログラマブルに静的な Web サイトを構築できるプラットフォームだった。
縦書きテンプレート作成でも、その実力を十分実感できた。
学習コストは低くはないが、React + GraphQL に慣れている人ならばドキュメントに一通り目を通せば使えるようになると思う。

感謝

当テンプレートでは、青空文庫で公開されているテキストを一部利用しています。

青空文庫