昔ながらのWebサイトのためのParcelレシピ

この記事は公開されてから2年以上経過しています。 情報が古い可能性がありますのでご注意ください。

Parcel はよりシンプルな小規模Webサイトの制作に向いている、というのは先日の記事で紹介した通りです。それはひとえにParcel自体のシンプルさ故にでありますが、そのメリットは融通の効かなさというデメリットと表裏一体であり、自在にコントロールする事の難しさでもあります。 本稿はそんなParcelをうまいこと調理するレシピを徒然なるままに書き連ねていく回です。

「昔ながらのWebサイト」とは

タイトルで言う「昔ながらのWebサイト」とは、例えばオーソドックスなコーポレートサイトや非ECなショップサイトなどの、HTMLとCSSとJSから構成されたシンプルで静的なWebサイトを指します。

そういったWebサイトを運用するのはクライアントである事も多く、我々制作者の手を離れる事も少なくありません。そのため、納品するデータはクライアントにとってわかりやすく、編集が容易な状態でなければなりません。あるいはそれは、WordPressのようなCMSかもしれません。

この要件をParcelを用いてどのようにしてバンドルするか、考えていきたいと思います。

ノーコントロールで出力して仕様・挙動を確かめる

まずはParcelの挙動を知るため、デフォルトでどのように出力されるのかを確認してみましょう。

src/
├── about/
│   └── index.html
├── assets/
│   ├── css/
│   │   └── style.scss
│   ├── images/
│   │   ├── 01.jpg
│   │   ├── 02.jpg
│   │   ├── 03.jpg
│   │   └── 04.jpg
│   └── js/
│       └── main.js
└── index.html

上のようなファイル構成で、なんのオプションも渡さずに出力をしてみます。 index.html では、 main.js style.scss 01-04.jpg が読み込まれています。

$ parcel build 'src/**/*.html'

デフォルトの出力先ディレクトリは dist なので、ビルドの完了と同時に dist ディレクトリに出力結果が保存されます。さあ、中身はどうなっているでしょうか。じゃじゃん。

dist/
├── 01.be0ed084.jpg
├── 02.8270bec8.jpg
├── 03.3864a649.jpg
├── 04.0df788c6.jpg
├── about/
│   └── index.html
├── index.html
├── main.17b58856.js
├── main.17b58856.js.map
├── style.2cdee1ef.css
└── style.2cdee1ef.css.map

なんか期待してたのと違う。

ご覧のように、ディレクトリ階層構造は概ね平坦にされ、読み込まれたファイルにはハッシュが挿入され冗長になっています。実際このデータを渡された側は、どうしたものかと困惑してしまいますね。

出力をコントロールする

上の結果を踏まえて、ビルドの出力結果のディレクトリ構造ならびにファイル名を上手にコントロールしてみたいと思います。

  • 元のファイル名のまま出力したい
  • 元の階層構造を維持したい

この課題を解決するために、Parcelのエントリーポイントについて学びましょう。

エントリーポイントの特徴

エントリーポイントの特徴

ここで言うエントリーポイントとは、すなわち parcel コマンドに渡した引数のパスであり、上のコマンドで言うところの 'src/**/*.html' の部分を指します。指定されたファイルは起点となり、読み込まれているリソースを走査して自動的にバンドルしていきます。そして重要なのは、 エントリーポイントはパスとファイル名を維持したまま書き出される という事です。

  • エントリーポイントとして指定されたファイルは、元のファイル名と階層位置を維持して出力される
  • 非エントリーポイントとして読み込まれたファイルは自動的にバンドルされ、ファイル名にハッシュが挿入され、出力ディレクトリのルートに出力される

つまり、元のパスとファイル名を維持したいファイルはエントリーポイントとして指定してしまえば良いのです。

エントリーポイントの指定方法

エントリーポイントの引数は、内部で glob に渡されるので glob の記法で柔軟に制御する事ができます。主な記法はREADMEを参考にしていただくとして、特に良く使うのが次の記法です。

  • ワイルドカード : *.html or **/*.html
  • 括弧によるパターン展開 : file.(html|css|js)
  • 大括弧によるパターン展開 : src/{'foo','bar/baz'}
    → この例では src/foosrc/bar/baz に展開される

これらの記法を駆使して課題を解決してみましょう。今一度ファイル構成を確認しておきます。

src/
├── about/
│   └── index.html
├── assets/
│   ├── css/
│   │   └── style.scss
│   ├── images/
│   │   ├── 01.jpg
│   │   ├── 02.jpg
│   │   ├── 03.jpg
│   │   └── 04.jpg
│   └── js/
│       └── main.js
└── index.html

エントリーポイントに指定したいファイルをタイプごとに分類すると :

  • src/**/*.html
    src 以下(サブディレクトリ内含む)のHTMLファイルを全て指定
  • src/assets/css/*.scss
    src/assets/css 直下のscssファイルのみ指定
  • src/assets/images/**/*.(jpg|png)
    src/assets/images 以下(サブディレクトリ内含む)のJPG,PNGファイルを全て指定
  • src/assets/js/*.js
    src/assets/js 直下のjsファイルのみ指定

こういった具合になります。これらを共通項 src/ で括って大括弧でパターン分けすれば、引数の出来上がりですね。カンマの前後にスペースなどが入っていると動作しないのでご注意ください。

$ parcel build src/{'**/*.html','assets/css/*.scss','assets/images/**/*.(jpg|png)','assets/js/*.js'}

これでビルドを実行するとどうなるか。こうなります。じゃん。

dist/
├── about/
│   └── index.html
├── assets/
│   ├── css/
│   │   ├── style.css
│   │   └── style.css.map
│   ├── images/
│   │   ├── 01.jpg
│   │   ├── 02.jpg
│   │   ├── 03.jpg
│   │   └── 04.jpg
│   └── js/
│       ├── main.js
│       └── main.js.map
└── index.html

エクセレント!憂いなく納品できそうです 🎉

エントリーポイントを制する者がParcelを制す

と言うと少し過言に聞こえるかもしれませんが、実際のところ、Parcelをコントロールする方法はエントリーポイントの指定とあまり多くないオプション渡しくらいしかありません。 プログラマティックに制御することも出来るようですが、それをするならばParcelを選択しないでしょう。

エントリーポイントを理解したならば、それはすなわち「Parcelチョットワカル」と言って良いと思います。

serve | watch | build を使い分ける

ここまで parcel build というコマンドを使用してきましたが、 Parcel には build の他に、 servewatch という2つのコマンドが用意されています。簡単に紹介しましょう。

  • serve
    最も頻繁に使われるコマンドです。コマンド省略時のデフォルトでもあるので、ただ parcel と実行した場合は parcel serve と等価になります。 serve コマンドはソースファイルを監視し、変更を検知した場合に自動的に再ビルドを行いながら、開発用の簡易Webサーバーを起動します。制作中はこれを起動しながらコーディングを進めていくことになるでしょう。

  • watch
    ファイルを監視して変更時に自動再ビルドをします。 serve と異なり、開発用サーバーは起動しません。
    開発用サーバーが別で立ち上がっていて、ファイルの監視とリビルドだけ行いたい場合に使います。

  • build
    ビルドをします。監視もサーバー起動もしません。ただビルドをします。 主に納品用のファイルを出力する為に使います。

出力ディレクトリは分けよう

全てのコマンドにおいて、出力ディレクトリの初期値は共通して dist とされていますが、 serve|watchbuild では基本的に出力結果が異なる為、いずれかを実行するたびに dist の中身が書き換わって差分が生まれてしまいます。生まれた差分が検証の邪魔をする事もあるでしょう。

したがって、 serve|watchbuild では出力ディレクトリは分けるのが賢明だと考えます。

$ parcel serve src/**/*.html
$ parcel watch src/**/*.html
$ parcel build src/**/*.html -d build

出力ディレクトリは -d または --out-dir オプションで設定可能なので、 build する場合だけ別のディレクトリに書き換えてやると良いでしょう。

parcel serve のエントリーポイントはシンプルに

出力をコントロールする では出力結果を制御するために細かくエントリーポイントを指定しましたが、これは build 用であって、出力結果の体裁が問題にならない serve では極めて単純な設定 'src/**/*.html' で大抵事足ります。 servedist の中にどのようにファイルが出力されようが、それはどうでもよいことです。つまり、頑張るところではありません。

$ parcel serve 'src/**/*.html'

また、詳細な発生条件はわからないのですが、 serve コマンドで複雑なエントリーポイントを渡すと、開発サーバーが正常にディレクトリインデックスを返さない不具合が起きる事があるので、それを回避するためというのも理由のひとつです。

不具合の症状としては、デフォルトではhttp://localhost:1234 で開発サーバーが起動するのですが、ブラウザでアクセスしても 404 が返ってきてしまいます。なお、 /index.html でアクセスするとちゃんと表示されます。

どうしても複雑に指定したい時は

とはいえ、 serveglob 記法でゴリゴリ書いたエントリーポイントを渡したい場面というのはあると思います。例えば、動的に読み込まれる画像リソースを扱いたかったり。そんな時は serve ではなく watch を使用し、別途 http-server 等でWebサーバーを立ち上げるのも一つの手段です。

"scripts": {
  "dev": "parcel watch 'src/**/*.(html|png|jpg)' & http-server dist -p 1234"
},

WordPress のテーマ制作にParcelを活用する

これもまた頻出の課題。WordPressの独自テーマで使うJS/CSSファイルをビルドしたい。例えばテーマ名を my-awesome-theme としてこのように構築するとします。

public/
└── wordpress/
    └── wp-content/
        └── themes/
            └── my-awesome-theme/
                ├── functions.php
                ├── index.php
                ├── style.css
                └── assets/
                    ├── js/
                    │   └── main.js
                    ├── css/
                    │   └── style.css
                    └── images/
                        ├── 01.png
                        ├── 02.png
                        └── 03.png

はじめに確認しておきたいのは、Parcel でビルドするのは assets の中身だけです。 Parcel はエントリーポイントとしてPHPファイルを扱うことができませんし、テーマルートの style.css はテーマの基本情報を記述するためのファイルなので、完全に静的であるべきです。したがって、それ以外の、Parcelを通したい物をまとめて assets につっこんでしまおうという考えです。

この assets の中身をビルドするためのソースは、いままで通り src に設置するとしましょう。

src/
└── assets/
    ├── js/
    │   └── main.js
    ├── css/
    │   └── style.scss
    └── images/
        ├── 01.png
        ├── 02.png
        └── 03.png

あとはこれまでと同様にエントリーポイントを指定して出力先をテーマルートにしてあげればOK。WordPressをどのように起動するかについてはここでは触れませんが(DockerなりXamppなり仮想マシンなりお好みで)Parcelの開発サーバーはここでは完全に無駄なので、 serve ではなく watch を使います。

$ parcel watch src/assets/{'css/*.scss','images/**/*.(jpg|png)','js/*.js'} -d public/wordpress/wp-content/themes/my-awesome-theme/assets

これでファイルを監視しつつ自動ビルドを行ってくれます。ここで注意事項が2つ。

  1. この場合、CSSのホットリロードは効きません。CSSのホットリロードが働くのは、エントリーポイントにHTMLが含まれる場合のみなので、諦めてマニュアルリロードしましょう。(JSのホットリロードはちゃんと機能します)
  2. watch コマンドは、出力ファイルをミニファイする事ができません。最終的にJS/CSSファイルをミニファイしたい場合は、同じ引数で build コマンドを実行しましょう。

納品用のHTMLファイルを整形する

HTMLの更新がクライアントのタスクになる場合、「ミニファイされたHTMLでは管理がつらすぎるので、整形されたHTMLを渡して欲しい」というケースもあると思います。 Parcelの機能では賄えないので記事の内容からやや外れてしまいますが、せっかくなので。 そんな時は、 build した後で整形するためのスクリプトを実行してあげましょう。

まず整形のための js-beautify と、ファイルを走査するための glob をインストールします。

$ npm i js-beautify glob -D

次に整形スクリプトを用意しましょう。書き方には色々あると思いますが、今回は process.argv で引数を受け取って処理する形をとりました。

// tasks/beautify.js

const path = require('path')
const fs = require('fs')
const glob = require('glob')
const beautify = require('js-beautify')

if (process.argv.length > 2) {
  glob.sync(path.join('./', process.argv[2]))
    .forEach(filePath => {
      fs.readFile(filePath, { encoding: 'utf-8' }, (error, data) => {
        if (!error) {
          fs.writeFileSync(filePath, beautify.html(data, { indent_size: 2 }), 'utf-8')
        }
      })
    })
}

使い方は、コマンドの引数としてパスを渡すだけです。glob記法を使う場合には、クォーテーションで囲ってあげましょう。

$ node tasks/beautify.js 'build/**/*.html'

こんな具合で build で出力されたHTMLに対して実行してやることで、改行・インデントした読みやすいHTMLに整形してくれます。 build する度に実行しなおすのは手間なので、 package.json の npm script に parcel build と一緒に登録しておくと良いでしょう。

"scripts": {
  "build": "parcel build 'src/**/*.html' -d build && npm run beautify:html",
  "beautify:html": "node tasks/beautify.js 'build/**/*.html'"
},

まとめ

非常にまとまりなく、ただ連連とレシピを紹介する回となってしまいましたが、是非とも知っていただきたいのは、Parcelの設定の9割方はエントリーポイントが握っているということです。 繰り返しになりますが、エントリーポイントを制すればParcelを制したも同然でしょう。

快適なビルドライフをお送りいただけますように! 🙏🏻