自作の Hugo テーマで起こっていた問題について対応した軽いメモ書き

認識

自作 Hugo テーマ(Harbor)にある issue が起票されていた。内容は、「個別の記事を開いたとき、コンソールに『フォントデータが見つからない』というエラーが出る。 index ページだと出ない」というもの。

原因

Harbor では、 CSS の @font-face 規則を用いて、 /static/fonts/ 以下にあるフォントデータからフォントを読み込んでいる。そして CSS ファイルは JS ファイルと合わせて webpack によって一つの JS ファイルにバンドルし、全てのページでバンドルした JS ファイルを読み込むという構成になっていた。

/* noto-sans-jp-regular - japanese_latin */
@font-face {
  font-family: 'Noto Sans JP';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Noto Sans Japanese Regular'), local('NotoSansJapanese-Regular'),
       url('../fonts/noto-sans-jp-v25-japanese_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/noto-sans-jp-v25-japanese_latin-regular.woff') format('woff'); /* Modern Browsers */
}

/* roboto-regular - latin */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local('Roboto'), local('Roboto-Regular'),
       url('../fonts/roboto-v20-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/roboto-v20-latin-regular.woff') format('woff'); /* Modern Browsers */
}

上記の通り、 @font-face 規則でドメイン直下の fonts ディレクトリに格納されたフォントデータのソースを相対パスで参照していたことにより、 index ページ(例: https://example.com)からは該当のパスが参照できてフォントデータが取得できる一方、他のページ(例: https://example.com/post/something)からは参照できない状態になっていた。

だいぶ初期から出てたエラーのはずなのに。今まで気にしなかったのか。

対応

webpack v5 で導入された Asset Modulesという機能を使う。

これは jpeg や png などの画像やフォント、 JSON データを JS にバンドルさせる、もしくは JS 内に適切な外部参照の URI を注入するというもの。 v5 が出るまでは url-loader とか file-loader が用いられていたが、 v5 では webpack ネイティブで対応されるようになった。

使い方は公式ガイドの通り、 module.rules に対応させたいファイルタイプの拡張子と asset のタイプを指定するだけ。指定できる asset のタイプは asset/resource (バンドルしたいファイルは個別に出力して、JS内では外部参照という形にする)や asset/inline (JS内にエンコードしたファイルデータをまるっと注入する)など複数あるが、 asset としていすることで webpack が外部参照か JS 内にエンコードデータを入れ込むかを判断してくれるので便利。

module.exports = {
 // ...
 module: {
   rules: [
     {
       test: /\.(woff|woff2)$/,
       type: 'asset',
     },
   ]
 }
};

公式ガイドの通りやってもうまくいかんなーとか思ってたら css-loader の options で url: false を指定していた。

webpack v5 は去年10月にリリースされていてもう半年たつのに全然内容知らなかった。フロントエンドはフレームワークの発展スピードもさることながら、ツールチェインまわりの進化も追わないといけなくて大変そう。

参考