blog.anqou.net
rss
author
tags

Nix で soupault に HTML を生成させる

前の記事の続編です。blog.anqou.net のビルドを Nix に載せました。

#モチベーション

blog.anqou.net は、各記事の一番下に表示されているように、soupault という OCaml 製のツールを使って生成されています。soupault 自体は HTML タグをパーズして Lua を使って好きに書き換えてから出力するという機能を持った静的サイトジェネレータ(SSG)になっていて、入力する HTML ファイルは外部コマンドを起動して生成させることができます。この機能を使い、例えば記事の内容を書いてある Markdown は cmark-gfm を起動して HTML に変換し、あるいはコードブロックは Shiki(を使う Node.js の自作プログラム)を起動して HTML に変換し、それぞれ適切に変形して最終的な HTML ファイルに組み込んでいます。より詳細な話は過去に書いた記事を参考にしてください。

外部コマンドを適宜起動して処理を行うという soupault の仕組み上、blog.anqou.net のビルドを行う環境には、cmark-gfm などのビルドに必要なコマンドを予めインストールしておく必要があります。これを愚直にやるのは面倒なので、いままでは Docker を使っていました。つまり Dockerfile で soupault を含めた必要なツールを全てインストールするようにしておき、blog.anqou.net をビルドする際にはこの Docker イメージを通してビルドを行っていました。この方法は普通に動くのですが、Dockerfile を正しく書くのは結構面倒なのと、コンテナの中に入力ファイルを見せる必要があったりして、細かいところで色々と面倒でした。また、ビルド自体には使わないものの、記事を書く際に必要なソフトウェア(具体的には http-serverinotifywait など)を別で入れる必要もありました。

そこで、最近 NixOS に乗換えたこともあり、このブログビルド環境を Nix に載せ替えてみました。Nix であればコンテナ周りの面倒とは無縁ですし、nixpkgs にすでにあるソフトウェアを自分でビルドする必要もありません。また、Nix Flake の devShell の仕組みを使えば、記事を書くときにだけ使う(ビルド自体には使わない)ソフトウェアを nixpkgs から導入し、envdir を経由してブログ用のディレクトリの下でだけ有効化することもできます。

#実装

Nix Flake を使って環境を整えます。blog.anqou.net のビルドに必要な soupault・cmark-gfm は共に nixpkgs でパッケージングされているので、これをそのまま使います。nix flake init を打つとスケルトンの flake.nix を作ってくれるので、これをベースに以下のような flake.nix を書きます:

{
  description = "A very basic flake";

  inputs = {
    # nixpkgs には nixos-24.11 のブランチを採用。
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-24.11";

    # Shiki を使った自作 highlight コマンドも Nix Flake 管理にして
    # ここで参照する。inputs.nixpkgs.follows を指定することで、
    # 同じ nixpkgs のレビジョンを見るようにする。
    highlight = {
      url = "github:ushitora-anqou/highlight";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = {
    self,
    nixpkgs,
    highlight,
  }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages."${system}";
    lib = nixpkgs.lib;
    nodejs = pkgs.nodejs_23;
    pnpm = pkgs.pnpm_10;
  in {
    # Nix ファイル用のフォーマッタの設定。nix fmt で動く。
    formatter."${system}" = pkgs.alejandra;

    packages."${system}" = rec {
      # メインの部分。blog.anqou.net をビルドするのに必要な依存パッケージと
      # ビルドの手順を詰め込んだ derivation を作る。default に書いたものが
      # nix build コマンドで実行される。
      default = pkgs.stdenv.mkDerivation rec {
        name = "blog.anqou.net";
        phases = "buildPhase"; # buildPhase だけ動けば十分。
        nativeBuildInputs =
          # nixpkgs に存在する soupault と cmark-gfm を入れておく。
          (with pkgs; [coreutils soupault cmark-gfm])
          # 加えて 自作の highlight コマンドを入れる。
          ++ [highlight.packages."${system}".default];
        PATH = lib.makeBinPath nativeBuildInputs;
        src = ./.;
        # src に指定したディレクトリが $src にあるので、そこへ cd して
        # soupault を起動する。出力は $out に作る必要がある。
        builder = pkgs.writeShellScript "builder.sh" ''
          cd $src
          soupault --build-dir $out
        '';
      };
    };

    # 開発時(つまり記事執筆時)に必要なパッケージを devShells に書く。
    # nix develop すると起動できる他、envdir を設定しておくと、
    # flake.nix と同じディレクトリに cd するだけで勝手に使えるようになる。
    devShells."${system}" = rec {
      default = pkgs.mkShell {
        packages =
          [nodejs pnpm]
          ++ (with pkgs; [
            gnumake
            http-server
            inotify-tools
          ]);
      };
    };
  };
}

以上のような flake.nix を書いたうえで nix build を打つと soupault が起動して HTML の生成を行ってくれます。結果は(Nix ではおなじみの)result ディレクトリに入ります。あとはこの result ディレクトリの中身を http-server でローカルにホストすれば、好きなブラウザから記事を見ることができます。また GitHub Action も nix build を使うように設定しなおしておいたので、git push すると GHA 上で nix build が走り、その結果が Cloudflare Pages を通して blog.anqou.net でホストされるようになりました。

よかったよかった……と書きかけて思い出したのですが、実は blog.anqou.net は素の cmark-gfm ではなく、日本語用にカスタムしたものを使っているのでした(この記事を書き始めるまで完全に忘れていました)。詳細は以前書いたブログ記事を参照してください。

ということで、このパッチを当てた cmark-gfm を使うように改造します。といってもやることは簡単で、nixpkgs の cmark-gfm を override して src をカスタムの cmark-gfm の GitHub レポジトリに向けるだけです。こんな形で簡単にパッチを当てられるのが Nix の使い勝手のいいところ(の一つ)です:

# 諸々省略

  outputs = {
    self,
    nixpkgs,
    highlight,
  }: let
    # ...

    # overrideAttrs を使って cmark-gfm の src を
    # カスタム cmark-gfm の GitHub レポジトリに向ける。
    cmark-gfm = pkgs.cmark-gfm.overrideAttrs (finalAttrs: previousAttrs: {
      src = pkgs.fetchFromGitHub {
        owner = "ushitora-anqou";
        repo = "cmark-gfm";
        rev = "8ec199d8a3665a40c13f732ce4c24c28a500193e";
        sha256 = "sha256-2EdfoXDvkLoagkVQsm0+Hb73tfxQOwSDrmvha2sQ0Yo=";
      };
    });
  in {
    # ...

    packages."${system}" = rec {
      default = pkgs.stdenv.mkDerivation rec {
        # ...

        # nixpkgs にあるものではなく、
        # カスタムの cmark-gfm を使うようにする。
        nativeBuildInputs =
          (with pkgs; [coreutils soupault])
          ++ [cmark-gfm highlight.packages."${system}".default];

        # ...
      };
    };

    # ...
  }

ということで、無事 blog.anqou.net は Nix で完全にビルドできるようになりました。よかったよかった。