静的サイト開発のための最強のボイラープレートを作った

プロジェクトの雛形を雑に作ると開発でかなりストレスを抱えることになる。仮に小規模な静的サイトであっても。

とはいえ開発環境を作ることをがんばりすぎてもコストに見合わないこともある。コストを軽減するために各々ボイラープレートを作ってたりもするけど、その作りもバラバラでだいたい不満が出る。

この問題を解決するために、自分が本当に正しいと思える構成でボイラープレートを作った。作る過程で考えたことについて書く。

受託で静的ウェブサイト作ってるみたいな人向け。


最初に、ほとんどのプロジェクトはビルド前提だが、何をどのようにビルドするかはかなり慎重に考えるべきだ。どんなディレクトリ構成にして、どのファイルをどこにコピーするのか。HTMLテンプレートはどうやって設計するのか。複雑にするのか単純にするのか。

全体の方針

できるだけ標準的な構成に寄せて、初見でも全体を予測させやすくすることを目指した。ビルドプロセスは可能な限りシンプルにした。
想定外の仕様を求められること(文字コードShift_JISにする、改行コードをCRLFにする、ディレクトリ構成を指定されている)はあるあるなので、ユーザーが当たり前に拡張しやすくするためだ。

Create React Appみたいに外部パッケージ化すると柔軟性が失われるので、あえて全部露出させている。

CSS

まず、CSSはビルドツールに頼り切るしかない。Sassで書いてPostCSSで最適化するというフローからはしばらく抜け出せそうにない。論理的な単位でのソースファイルの分割、共通化すべき値の変数化、ベンダープリフィックスの自動付与などは必須だろう。

ファイルの構成は、個別ファイルを読み込むだけのmain.scssから、Normalize.cssとベースCSSを読み込んだ上で、その他のコンポーネントGlobで読み込んでいる。

ちなみに、SassでやっていたことをPostCSSに置き換えることに関してはあまり積極的ではない。
cssnextのアプローチは微妙。機能の雰囲気だけ真似て仕様とぜんぜん違う形で使わされたり、何年経っても実装されなさそうな糖衣構文を使えるようになってもあまり嬉しくない。
Sassは機能が多すぎるので、最低限の機能だけ利用するためにPostCSSを使うというのもある。ただそれだと、使う機能の基準を定める必要があるし、プロジェクトごとにそのバラつきが出て面倒になる。
そうすると、意識的にSassの機能を制限しながら使うというところに落ち着く。たぶん近い未来にCSSをビルドレスで使うということもなくて、10年後も普通にSassを書いてる気がする。

JavaScript

最近のJavaScriptはやたら複雑そうだが、普通のウェブサイトのためのスクリプトとして見ると要点は2つだ。モジュールシステムと、JavaScriptの(モジュールシステム以外の)最新機能だ。複雑なJavaScriptでの実装が求められるとこれらは欠かせない。

webpackとBabelを使って、できるだけ単純な設定でビルドできるようにしている。

これらは、数年後には一通りのモダンブラウザに実装されて、ビルドレスで利用できる見込みがある。ただ、IEをサポートするためにはこれらの複雑なビルドツールに依存し続けるしかない。

HTMLテンプレート

HTMLをテンプレートから生成するのはそこそこのコストになることがある。記述量やページの性質によってはそのままHTMLを書いた方が良いことも少なくない。コードの短縮や修正の効率化のため、今回は仕組みに含めた。

テンプレートエンジンはPugを選んだ。インデントでタグの入れ子を表現するという構文のため、修正の手間をかなり削減できる。個人的にタグというもの自体を書きたくないというのもある。
テンプレートの継承ができたり、テンプレート内にそのままJavaScriptを書けたりという便利な機能もあるが、気をつけないと全く読めない最悪なファイルにしてしまうことがある。これは不安要素としてある。
HTMLの構文とかけ離れているため、学習コストがかかるというデメリットもあるが、それ以上に効率化はできる。

テンプレート内で利用する変数はJSONで管理できるようにした。管理方法は2通り用意した。
まず、単一のテンプレートファイルのみだけで有効なデータ。テンプレートファイルと同名のファイル(company.pugだとcompany.jsonが対応する)を作成すると利用できる。テンプレート内のpage変数から参照できる。
次に、全てのテンプレートで有効なデータだ。_data/products.jsonというファイルを作成すると、テンプレート内のfile.products変数から参照できる。

└── src
    └── html
        ├── _data
        │   └── products.json
        ├── company.json
        └── company.pug

ページのパスも変数として提供する。これがあると、メタ情報の設定やナビゲーションのリンクがアクティブかなどを判定することができる。
それぞれのページからpage.path変数を参照すると、プロジェクトルートから見たファイルのパスが取得できる。product/drink.pugだと/product/drink.htmlになり、product/index.pug/product/になる。

この辺は、HugoJekyllあたりを参考にした。

HTMLテンプレートと開発サーバー

ひとつのテンプレートを変更するたびに全てのテンプレートをビルドしているとかなりの秒数がかかってしまう。ファイル数が少ないと問題にならないが、増えてくると深刻な問題になってくる。これを解決するために、開発サーバーにリクエストしたタイミングでURLに対応するファイルをビルドする仕組みにした。開発時にはライブリロードが有効なまま、ファイル数が増えても高速にビルドできるようになっている。

サブディレクトリの解決

サブディレクトリにウェブサイトを納品する機会は多い。サブディレクトリ直下のページしかない場合は問題になりにくいが、そこからさらにディレクトリが深化する場合はパスの解決が複雑になる。

これは設定ファイルに記述するだけで解決できるようにした。開発サーバーや、ビルド時に生成されるディレクトリ構成もそれに沿ったものになる。
HTMLのテンプレートにも、サブディレクトリのパスを解決できる便利関数を提供している。

ディレクトリ構成

├── dist/
│   └── subdir/
│       ├── assets/
│       │   ├── css/
│       │   │   └── main.css
│       │   ├── img/
│       │   │   └── logo.png
│       │   └── js/
│       │       └── main.js
│       └── index.html
├── public/
│   └── assets/
│       └── img/
│           └── logo.png
├── src/
│   ├── css/
│   │   └── main.scss
│   ├── html/
│   │   ├── _data/
│   │   │   └── products.json
│   │   ├── _includes/
│   │   │   ├── global-header.pug
│   │   │   ├── head.pug
│   │   │   └── scripts.pug
│   │   ├── index.json
│   │   └── index.pug
│   └── js/
│       └── main.js
└── vendor-public/

src/ディレクトリにビルドの対象になる全てのファイルが格納される。ディレクトリの見通しのよさのために種類別に分離している。src/html/ディレクトリの中身はその階層を保ったままプロジェクトルートに出力される。

public/ディレクトリには単にコピーされるだけのファイルが格納される。画像ファイルやファビコン、あるいはテンプレートから生成しないHTMLファイルなど。

vendor-public/ディレクトリには開発時には必要だが納品しないファイルを格納する。共通CSSや共通ヘッダーなど。開発サーバーではルートディレクトリから参照できる。

dist/ディレクトリには本番向けビルドで生成されたファイルが格納される。vendor-public/ディレクトリの中身は含まれない。

この構成であれば、ディレクトリごとの役割がはっきりしてかなり見通しが良くなる。

他の機能

差分納品やSSIなど、よくある要件の解決策もガイドとして含めた。プロジェクトコードを管理する際の面倒くささはかなり解決できたと思う。


仕事をはじめてからずっとめんどいって思ってた問題は結構解決できた。これからは開発環境じゃなくて、ウェブサイトの作り方自体を改善していきたい。

yuheiy/real-world-website-boilerplate