Jamstack Advent Calendar 2020の3日目の記事です。
最小限の静的サイト
1リクエスト(最小限)
半年ぐらい前に11tyのドキュメントを読んでから、そのうち試したいなあと思っていたので、以前に作ったサイトを11tyで書き直しました。11tyはシンプルに必要な機能が揃っていて、文字通りの静的サイトジェネレーターの仕事をしてくれる所がよいです。
ドキュメントがわかりやすいので、そっちを読んでもらった方が確実ですが、ドキュメントを読むのが面倒くさい人もいると思うので、やったことをまとめてます。Markdownで書けるブログのテンプレートを作る、とかの方が需要がありそうですが、今回は1ページしかないサイトなのでMarkdownは使ってないです。
そもそも、1ページのために静的サイトジェネレーターってどうなの。
11ty - Eleventy
Node.js製の静的サイトジェネレーター。多くのテンプレートエンジンに対応しているのが特徴。Jekyllを知っている人には、Jekyllみたいなやつと言えば伝わる。
Next.jsやNuxt.jsのようなフレームワークの場合、静的サイトとしてエクスポートしても、初回のレンダリング以降はSPAのように振る舞います。本来これが体験的な利点になるわけですが、今回はページ全体のリクエストを1回にしたいので、JSをリクエストされては困ります。
その点、11tyはテンプレートから静的サイトを生成する機能に特化しているので、そのまま使うもよし、フロントエンドのJSを組み合わせるもよしで、選択の自由がこちらにあります。これなら1リクエストにする要件を満たせる。シンプルなので dist
がスッキリして気持ちが良い。
ひとつ注意が必要なのが、webpackやSassあたりは11tyのスコープから外れるので、npm scriptsやgulpで環境を用意する必要があります。今回は環境の構築が面倒だったので、11tyだけで完結させました。
インストール
mkdir project-name
cd project-name
npm init -y
npm install --save-dev @11ty/eleventy
{
"name": "project-name",
"version": "1.0.0",
"scripts": {
"dev": "eleventy --serve",
"build": "eleventy"
},
"devDependencies": {
"@11ty/eleventy": "^0.11.1"
}
}
echo '<p>EVERGLOW</p>' > index.njk
npm run dev
これでBrowsersyncが立ち上がるはず。
http://localhost:8080
_dataはグローバルなオブジェクトになる
node-fetchを使ってますがお好きなものを。
npm install --save-dev node-fetch
テンプレート内で参照したいデータは _dataディレクトリ内に追加していきます。下記のコードは非同期なのでPromiseを返す関数を定義していますが、プレーンなJSの値やJSONもサポートしています。
const fetch = require('node-fetch')
module.exports = async function() {
const res = await fetch('https://nishinoshake-hatebu-tech.s3-ap-northeast-1.amazonaws.com/weekly/20201111.json')
const items = await res.json()
return { items }
}
_dataディレクトリ内に作成したファイルは、階層どおりにグローバルなオブジェクトに変換されてテンプレートから参照できるようになります。こんな感じのイメージです。
_data/
├── hoge/
│ ├── fuga.js
│ └── foo.js
├── hatebu.js
└── meta.js
↓ ディレクトリの階層でグローバルなオブジェクトになる
{
hoge: {
fuga: {},
foo: {}
},
hatebu: {},
meta: {}
}
Nunjucksでマークアップ
ドキュメントで使われてたから、という主体性のない理由でテンプレートエンジンはNunjucksにしました。Mozillaがテンプレートエンジンを開発しているのを知らなかった。完全に好みの問題ですが、pugのような短い記法は慣れると楽なんですが、しばらく使わないとすぐに忘れてしまうのが辛いです。Nunjucksは、HTMLを少し便利にしました、ぐらいの印象なので学習コストが少なくてよいです。(数十行しか書いてないけど)
他にも様々なテンプレートエンジンをサポートしています。
HTML/Markdown/JavaScript/Liquid/Nunjucks/Handlebars/Mustache/EJS/Haml/Pug/JavaScript Template Literals
先ほど設定したデータは、ファイル名の hatebu
で参照できます。
<ul>
{% for item in hatebu.items %}
<li>
<a href="{{ item.url }}" target="_blank" rel="noopener">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
CSSを埋め込む
CSSファイルを _includes/css
ディレクトリ内に作成して、
li + li {
margin-top: 1.4rem;
}
テンプレートの <style>
内に読み込みます。
<style>
{% include "css/index.css" %}
</style>
今回はスタイルシートをリクエストしたくないので直接埋め込んでますが、複数のページで共通のCSSがある場合は、CSSを分割してブラウザのキャッシュを効かせた方が無駄がないと思います。
HTMLのminify
はてブのエントリーを100件詰め込んでるのでHTMLがでかい。出力の可読性は必要ないので、大差ないけどminifyしておく。gzip後のサイズで比べると、本当に大差ない。
- | ファイルサイズ | gzip |
---|---|---|
素 | 56KB | 14.4KB |
minify | 50KB | 13.6KB |
Transforms can modify a template’s output. For example, use a transform to format/prettify an HTML file with proper whitespace.
出力を変更したい場合は、設定ファイルでTransform。
html-minifierのパッケージを追加して、
npm install --save-dev html-minifier
.eleventy.jsという設定ファイルにTransformの記述を追加すると、
const htmlmin = require('html-minifier')
module.exports = function(eleventyConfig) {
eleventyConfig.addTransform('htmlmin', function(content, outputPath) {
if (outputPath.endsWith('.html')) {
const minified = htmlmin.minify(content, {
collapseWhitespace: true,
minifyCSS: true,
removeComments: true
})
return minified
}
return content
})
}
こんな感じの出力になります。
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><title>先週のはてブテクノロジー</title><style>li+li{margin-top:1.4rem}}</style></head><body><div><header><p>11.07</p><h1>先週のはてぶ<br>テクノロジー</h1></header><ul><li><a href="https://togetter.com/li/1618183" target="_blank" rel="noopener">001</a> <a href="https://togetter.com/li/1618183" target="_blank" rel="noopener">部下から「議事録ってなんで作成する必要あるんですか?」と聞かれたので議事録の必要性について図解してみた - Togetter</a> <a href="https://b.hatena.ne.jp/entry/s/togetter.com/li/1618183" target="_blank" rel="noopener">1348users</a></li></ul><footer></footer></div></body></html>
レイアウトの設定
ペライチなので、わざわざレイアウトを設定する必要はないんですが、説明のために追加しました。レイアウトを設定すると、ヘッダーやフッターなどの共通部分をページごとに切り替えられるので、ページを量産する際には便利です。
_includes/layouts
ディレクトリ内にレイアウトのベースになるファイルを作成して、
---
title: Default title
description: Default description
---
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<meta name="description" content="{{ description }}">
<style>
{% include "css/index.css" %}
</style>
</head>
<body>
<header>header</header>
{{ content | safe }}
<footer>footer</footer>
</body>
</html>
index.njk
内のFront Matterという最初の部分でレイアウトを指定します。
---
layout: layouts/default.njk
title: 先週のはてブテクノロジー
description: はてブのテクノロジータグで話題になったエントリーを一週間分まとめて
---
<ul>
{% for item in hatebu.items %}
<li>
<a href="{{ item.url }}" target="_blank" rel="noopener">{{ item.title }}</a>
</li>
{% endfor %}
</ul>
Front MatterのフォーマットはYAML以外にも、JSONやJSがサポートされています。レイアウトで出てくるNunjucksのsafeフィルターは「安全だからエスケープしなくていいよ」という意味です。
ディレクトリの整理
ディレクトリがごちゃついてきたので少し整理を。
.eleventy.jsという設定ファイルでデフォルトのディレクトリを変更できます。
今までのコードで、CSSとレイアウトはそれぞれ _includes/css
と _includes/layouts
ディレクトリ内に作成してますが、わかりやすくするためにディレクトリを切っただけなので、_includes
内にあればどこでも動作します。
この _includes
ディレクトリは dir.includes の設定で変更できますが、あまりデフォルトから変えすぎると分かりにくくなるので、 input
と output
だけ変更しました。
module.exports = function (eleventyConfig) {
// Add a filter using the Config API
// eleventyConfig.addTransform('htmlmin', function (content, outputPath) {})
// You can return your Config object (optional).
return {
dir: {
input: 'src',
output: 'dist'
}
}
}
Before
├── _data/
├── _includes/
│ ├── css/
│ └── layouts/
├── _site/
├── .eleventy.js
└── index.njk
After
├── dist/
├── src/
│ ├── _data/
│ ├── _includes/
│ │ ├── css/
│ │ └── layouts/
│ └── index.njk
└── .eleventy.js
Vercel + GitHub Actions
ホスティングはVercelで。
最小限なのでデプロイがすぐ終わる。
APIが日替わりなので、GitHub Actionsでcronを設定して、VercelのDeploy Hookを叩いてます。Vercelを起こすためだけに毎日起こされるGitHub Actionsはどんな気持ちだろう。
name: Cron for Vercel
on:
schedule:
- cron: '0 16 * * *' # 1am JST
jobs:
wake:
runs-on: ubuntu-latest
steps:
- name: Wake Vercel up
run: curl -X POST $VERCEL_DEPLOY_URL
env:
VERCEL_DEPLOY_URL: ${{ secrets.VERCEL_DEPLOY_URL }}
計測
画像やWebフォントを使わずに、野暮ったくないサイトにするのが地味に大変だった。
サイト
https://hatebu.noplan.cc
ソースコード
https://github.com/nishinoshake/hatebu-tech
ドキュメントにいる、風船で浮いてるオポッサムが可愛い。
AN HOMAGE TO THE JAMES WILLIAMSON POSSUM BALLOON
コメント