Nix でのビルド時に Puppeteer を使う場合には $FONTCONFIG_FILE を設定しよう
この記事は以下のページに移転しました.
TL; DR
FONTCONFIG_FILE = "${pkgs.fontconfig.out}/etc/fonts/fonts.conf";
ビルドプロセスで Puppeteer を使いたい
前回の記事で,静的にビルドした Storybook に対し,Puppeteer を通じてヘッドレスモードの Chromium を立ち上げ,stories.json というファイルを生成する手順を紹介した.
Chromium を動かすのは面倒な場合がある
Puppeteer を用いれば Chromium の操作の自動化を手軽に行えるが,CI 上などで Chromium をインストールするためには,大量の動的ライブラリの存在が要求され,少々煩わしい.
以下は,かつて .circleci/config.yml に書いていた内容の抜粋である.
$ sudo apt-get install -y \ libatk-bridge2.0-0 \ libatk1.0-0 \ libcups2 \ libgtk-3-0 \ libnss3 \ libx11-xcb1 \ libxcomposite1 \ libxcursor1 \ libxdamage1 \ libxi6 \ libxrandr2 \ libxss1 \ libxtst6
Docker イメージをビルドする際にも metadata.json を生成する必要があるため,Dockerfile にもほぼ同じ (sudo を除いただけの) コマンドを記述しており,二重管理になっていた.Nix を用いれば依存関係の解決が簡単に行なえるし,かつ dockerTools を利用すればビルドプロセスをそのまま Docker イメージの作成にも流用できるため,Nix で Chromium をインストールしてビルドに用いることにした.
The first Nix file
まず以下のような Nix ファイルを用意した.
{ pkgs ? import <nixpkgs> {} }:
let
packageJSON = pkgs.lib.importJSON ./package.json;
version = packageJSON.version;
in
pkgs.stdenv.mkDerivation {
name = "herpism-storybook";
inherit version;
src = pkgs.nix-gitignore.gitignoreSource [] ./.;
buildInputs = [
pkgs.chromium
pkgs.noto-fonts
pkgs.yarn
];
PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium";
buildPhase = ''
HOME=$TMP yarn install --frozen-lockfile
yarn build-storybook -o ./dist
yarn sb extract ./dist
'';
installPhase = ''
mv ./dist $out
'';
}
しかし,これでは $ yarn sb extract ./dist を実行した際に失敗してしまう.
効かない $PUPPETEER_EXECUTABLE_PATH
本題から逸れるが,実は Storybook が環境変数 $PUPPETEER_EXECUTABLE_PATH を読んでくれないという問題がある.このため,Puppeteer 自身が勝手に Chromium をインストールしようとしては失敗するという自体に直面した.
これは @storybook/api が内部的に puppeteer ではなく puppeteer-core を用いており,puppeteer-core は puppeteer とは異なり $PUPPETEER_EXECUTABLE_PATH を考慮しないことに起因する.また,残念なことに,Storybook の CLI にも Chromium のパスを指定するオプションなどは特に用意されていないため,現状では自前で用意した Chromium を使用することができなくなってしまっている.
この問題に対処するため,以下のコマンドを build phase に追加した.
@@ -22,6 +22,8 @@ pkgs.stdenv.mkDerivation { buildPhase = '' HOME=$TMP yarn install --frozen-lockfile yarn build-storybook -o ./dist + yarn add -D puppeteer + sed -i ./node_modules/@storybook/cli/dist/extract.js -e s/puppeteer-core/puppeteer/ yarn sb extract ./dist '';
また,この件に関しては GitHub に issue を立てておいた. github.com
立ち上がらない Chromium
話を本筋に戻す.上記の問題を解決し,自前で用意した Chromium を用いるように Puppeteer に伝えても,ECONNRESET が発生してしまい,失敗してしまう.どうやら Chromium がうまく立ち上がっていないようである.
普段 CI 上で Nix によるビルドを行いたい場合には,Docker イメージとして nixos/nix を用いるのだが,これは Alpine Linux のイメージを継承しているため,apk コマンドが利用できる.CircleCI に SSH で接続し,デバッグを試みていたところ,おもしろいことに $ apk add chromium で Chromium をインストールしておきさえすれば,Puppeteer が apk でインストールした Chromium を使わずとも実行に成功することがわかった.apk を用いて Chromium をインストールする際には100を超える依存が同時にインストールされるので,そのいずれかによって生じる副作用によって何らかの変化が起こっていると考えた.インストールされたパケッジのリストを抽出し,問題の切り分けを行ったところ,fontconfig が犯人であると判明した.fontconfig のインストール時に /etc/fonts/fonts.conf が作成されることが原因だったようである.
$FONTCONFIG_FILE を指定する
Nix で fontconfig をインストールした際には,/etc/ 以下にファイルを作成するなどといった行儀の悪いことは行われない.fonts.conf へのパスは環境変数 $FONTCONFIG_FILE によって設定でき,Chromium もこれを考慮してくれるようだったので,Nix ファイルに以下のような変更を加えた.
@@ -13,10 +13,12 @@ pkgs.stdenv.mkDerivation { buildInputs = [ pkgs.chromium + pkgs.fontconfig pkgs.noto-fonts pkgs.yarn ]; + FONTCONFIG_FILE = "${pkgs.fontconfig.out}/etc/fonts/fonts.conf"; PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium"; buildPhase = ''
こうして無事に Nix を用いて metadata.json を伴った静的な Storybook をビルドすることができた.
コード全文
{ pkgs ? import <nixpkgs> {} }:
let
packageJSON = pkgs.lib.importJSON ./package.json;
version = packageJSON.version;
in
pkgs.stdenv.mkDerivation {
name = "herpism-storybook";
inherit version;
src = pkgs.nix-gitignore.gitignoreSource [] ./.;
buildInputs = [
pkgs.chromium
pkgs.fontconfig
pkgs.noto-fonts
pkgs.yarn
];
FONTCONFIG_FILE = "${pkgs.fontconfig.out}/etc/fonts/fonts.conf";
PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium";
buildPhase = ''
HOME=$TMP yarn install --frozen-lockfile
yarn build-storybook -o ./dist
yarn add -D puppeteer
sed -i ./node_modules/@storybook/cli/dist/extract.js -e s/puppeteer-core/puppeteer/
yarn sb extract ./dist
'';
installPhase = ''
mv ./dist $out
'';
}