Vercelビルド時にPlaywrightをインストールする

当ブログではシーケンス図やフローチャートの描画に Mermaid使用している。 変換方法として、MDXのビルド時にrehypeを使いJavaScriptオブジェクトに変換する処理の中で rehype-mermaid使うことでMermaidに変換している。

rehype-mermaidがMermaidの構文をSVGにレンダリングする際にはヘッドレスブラウザの Playwright内部的に利用する。 このPlaywrightがなかなかの曲者である。

今回はVercelビルド時のNode.jsのバージョンを20にアップグレードする過程で詰まったので備忘録として残しておく。

開発環境

  • Node.js: v20.15.0(Vercelではv20を指定)
  • @playwright/test: v1.45.1

Node18からNode20にアップグレードする

Vercelビルド時はNode.jsのバージョンを16, 18, 20の中から選べる。 デフォルトでは20になっているが、当プロジェクトではPlaywrightのインストールに失敗するため仕方なく18を使用していた。 ただ、基本的に最新のバージョンを使って運用したいので今回エラーの原因を調査することにしたというわけである。

Vercelビルド時のNode.jsのバージョンはVercelの管理画面から変更できるがpackage.jsonengines#node読み取ってオーバーライドする機能1あるためIaC的な考えからpackage.json記載する法がおすすめ。 そうすることで他の人がこのリポジトリをビルドするときにNode.jsのバージョンを合わせられるので一石二鳥だ。

apps/web/package.json
{
"name": "@kkhys/web",
// ...
"engines": {
"node": ">=20.15.0"
},
// ...
}

ちなみにNode.jsの他にパッケージマネージャのバージョンを指定したい場合は実験的な機能だが Corepack使うと良い。 Vercelでもサポートされている2

package.json
{
"name": "root",
// ...
"packageManager": "[email protected]",
// ...
}

これで次回以降のビルドステップではNode20が使われるようになる(マイナーバージョン以下は自動的に決定される)。

ビルド時にPlaywrightをインストールする

Vercelビルド時はTurborepoを使っている場合、デフォルトではturbo run build実行される。 そのコマンドが実行される前にPlaywrightをインストールしたいので、デフォルトの設定をオーバーライドするためにまずは以下のコードを追加する。

apps/web/package.json
{
"name": "@kkhys/web",
// ...
"scripts": {
"playwright-install": "pnpx playwright-core install chromium",
},
// ...
}

package.jsonscriptsフィールドにplaywright-installいうコマンドラインスクリプトを追加する。 このスクリプトを実行するとPlaywrightのコアライブラリを通じてChromiumブラウザバイナリをインストールする。

Playwrightの 公式ドキュメント にもあるとおり--with-deps付ければPlaywrightの実行に必要なシステムの依存関係をインストールできるが、Vercelビルド時に使用するコンテナイメージが対応していないためこのオプションは使えない。 以下のエラーが発生する。

sh: apt-get: command not found
Failed to install browsers
Error: Installation process exited with code: 127
ELIFECYCLE Command failed with exit code 1.
Error: Command "pnpm playwright-install && turbo run build" exited with 1

仕方なくオプションは付けずに進めていく(後で問題が起こるが)。

Vercelビルド時のビルドスクリプトをオーバーライドするにはVercelの管理画面から変更する法とvercel.json定義する法がある。 可能な限りコードで環境を管理したいのでvercel.json使用していく。

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "pnpm playwright-install && turbo run build"
}

vercel.json JSON Schema対応しているため2行目で定義している。 JSON Schemaを使えばコードの補完が効くのでできれば設定しておきたい。

3行目でpackage.json定義したスクリプトをビルドコマンドに追加する。 これをVercelにデプロイすればビルド時にPlaywrightがインストールされる。

ビルドコンテナに追加のパッケージをインストールする

上述した設定を追加した上でVercelにデプロイすると以下のエラーが発生する。

╔══════════════════════════════════════════════════════╗
║ Host system is missing dependencies to run browsers. ║
║ Please install them with the following command: ║
║ ║
║ pnpm exec playwright install-deps ║
║ ║
║ Alternatively, use apt: ║
║ apt-get install libnss3\ ║
║ libnspr4\ ║
║ libgbm1 ║
║ ║
║ <3 Playwright Team ║
╚══════════════════════════════════════════════════════╝

Playwrightインストール時に--with-depsオプションを使えなかったのでそれが原因で実行に必要な依存関係が不足しているっぽい。 VercelはDockerデプロイに対応していないため万事休すかと思われたが、幸いにもインストールコマンドを使うことでビルドイメージにパッケージを追加できる3

ちなみにNode20ではベースイメージに Amazon Linux 2023使われている。 Amazon Linux 2023のパッケージマネージャはdnf(Dandified Yum)なので注意が必要(ただ、一応yumは使える)。

Vercelデプロイ時のエラーでは以下のパッケージをapt-get使ってインストールするように言われたが、APTはDebianベースのパッケージマネージャなのでここではdnf install使うようにする。 システムによってパッケージ名が異なるのでそれぞれ置換する。

  • libnss3 → nss
  • libnspr4 → nspr
  • libgbm1 → mesa-libgbm

念の為、ビルドイメージをローカル環境で実行して確かめてみる。

Terminal window
# Docker コンテナを作成して実行する
docker run --rm -it amazonlinux:2023.2.20231011.0 sh
# nss があるかチェック -> ある
dnf search nss
# nspr があるかチェック -> ある
dnf search nspr
# mesa-libgbm があるかチェック -> ある
dnf search mesa-libgbm
# それぞれインストールする
dnf install -y nss nspr mesa-libgbm
exit

問題なくインストールできたのでビルドコマンドと同じようにvercel.jsonインストールコマンドを追加する。

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "pnpm playwright-install && turbo run build",
"installCommand": "pnpm install && dnf install -y nss nspr mesa-libgbm"
}

これで完了。 Node20にアップグレードしてMermaidをSSGでレンダリングすることに成功した。

さいごに

そもそもMermaidをレンダリングするためにわざわざヘッドレスブラウザをインストールするのはPlaywrightのサイズ的に考えてやり過ぎな気もするが、MermaidはNode.js上で動くDOMライブラリではレンダリングできないため仕方なく使っている4

Mermaidがjsdomに対応してくれれば良いのだがブラウザで使われることを想定されているためそうなることは当分の間なさそう。 ビルド時に毎回Playwrightをインストールするのは処理的に重いので、すでにPlaywrightをインストール済みの別環境でMermaidをレンダリングするAPIを作るなど他の法がないか模索中である。


  1. Vercel: Version overrides in package.json

  2. Vercel: Corepack

  3. Vercel: Installing additional packages

  4. GitHub Issue: server side mermaid with jsdom