HTML
JavaScript
JAMstack
11ty
19
どのような問題がありますか?

この記事は最終更新日から1年以上が経過しています。

投稿日

更新日

11tyで作る最低限の静的サイト

Jamstack Advent Calendar 2020の3日目の記事です。

最小限の静的サイト

single_line_network.jpg
https://hatebu.noplan.cc

1リクエスト(最小限)

半年ぐらい前に11tyのドキュメントを読んでから、そのうち試したいなあと思っていたので、以前に作ったサイトを11tyで書き直しました。11tyはシンプルに必要な機能が揃っていて、文字通りの静的サイトジェネレーターの仕事をしてくれる所がよいです。

ドキュメントがわかりやすいので、そっちを読んでもらった方が確実ですが、ドキュメントを読むのが面倒くさい人もいると思うので、やったことをまとめてます。Markdownで書けるブログのテンプレートを作る、とかの方が需要がありそうですが、今回は1ページしかないサイトなのでMarkdownは使ってないです。

そもそも、1ページのために静的サイトジェネレーターってどうなの。

11ty - Eleventy

11ty.png
https://www.11ty.dev/docs/

Node.js製の静的サイトジェネレーター。多くのテンプレートエンジンに対応しているのが特徴。Jekyllを知っている人には、Jekyllみたいなやつと言えば伝わる。

Next.jsやNuxt.jsのようなフレームワークの場合、静的サイトとしてエクスポートしても、初回のレンダリング以降はSPAのように振る舞います。本来これが体験的な利点になるわけですが、今回はページ全体のリクエストを1回にしたいので、JSをリクエストされては困ります。

その点、11tyはテンプレートから静的サイトを生成する機能に特化しているので、そのまま使うもよし、フロントエンドのJSを組み合わせるもよしで、選択の自由がこちらにあります。これなら1リクエストにする要件を満たせる。シンプルなので dist がスッキリして気持ちが良い。

ひとつ注意が必要なのが、webpackやSassあたりは11tyのスコープから外れるので、npm scriptsgulpで環境を用意する必要があります。今回は環境の構築が面倒だったので、11tyだけで完結させました。

インストール

mkdir project-name
cd project-name
npm init -y
npm install --save-dev @11ty/eleventy
package.json
{
  "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もサポートしています。

_data/hatebu.js
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

TEMPLATE LANGUAGES

先ほど設定したデータは、ファイル名の hatebu で参照できます。

index.njk
<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 ディレクトリ内に作成して、

_includes/css/index.css
li + li {
  margin-top: 1.4rem;
}

テンプレートの <style> 内に読み込みます。

index.njk
<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.

https://www.11ty.dev/docs/config/#transforms

出力を変更したい場合は、設定ファイルでTransform
html-minifierのパッケージを追加して、

npm install --save-dev html-minifier

.eleventy.jsという設定ファイルにTransformの記述を追加すると、

.eleventy.js
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 ディレクトリ内にレイアウトのベースになるファイルを作成して、

_includes/layouts/default.njk
---
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という最初の部分でレイアウトを指定します。

index.njk
---
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 の設定で変更できますが、あまりデフォルトから変えすぎると分かりにくくなるので、 inputoutput だけ変更しました。

.eleventy.js
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で。
最小限なのでデプロイがすぐ終わる。
vercel_deploy.jpg
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 }}

actions.png
2秒で終わるアクション。

計測

lighthouse.jpg
画像やWebフォントを使わずに、野暮ったくないサイトにするのが地味に大変だった。

サイト
https://hatebu.noplan.cc
ソースコード
https://github.com/nishinoshake/hatebu-tech

ドキュメントにいる、風船で浮いてるオポッサムが可愛い。
AN HOMAGE TO THE JAMES WILLIAMSON POSSUM BALLOON

参考

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
nishinoshake
AWSの料金をざっくり計算できるサイトを作ってます。

コメント

この記事にコメントはありません。
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
2022年に流行る技術予想
~
19
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー