dream2nixでElm開発環境を構築する方法
本記事では、Elmを使ってアプリケーションを開発したいと考えているNix/NixOSユーザーに向けて、開発環境を構築する方法について説明します。紹介する手順では、dream2nixという、さまざまなプログラミング言語のパッケージ管理(npm、pip、cargoなど)を統一的なインターフェースによって扱うためのフレームワークによって、Elmで開発したアプリケーションをビルドできる開発環境を構築します。
また、実行ファイルをビルドするだけではなく、Elmで開発したアプリケーションをデバッグする際には、elm-watchの開発サーバーをつかい、Elm Debuggerを活用できます。
記事の後半では、Google CloudのCloud RunなどのDockerイメージが必要となる状況に対応するため、DockerイメージをNixパッケージとしてビルドする方法も紹介します。
本記事で扱うツール一覧
この記事では、以下のツールを扱うことでElmの開発環境を構築します。
ツール名称 | 本記事で想定するバージョン | 説明 |
---|---|---|
flake-parts | - | Nix Flakesを再利用可能なモジュールに分割するためのフレームワークです。 |
dream2nix | - | さまざまなプログラミング言語のパッケージ管理(pip, npm, cargo など)を統一的なインターフェースによって扱うためのフレームワークです。 |
elm2nix | 0.3.0 | Elm パッケージの指定を Nix 式に変換するツールです。 |
elm-watch | 2.0.0-beta.2 | elm makeに近い体験を提供しつつ、ホットリロード機能をつかえるツールです。バージョン2のベータ版では、試験的な開発サーバーが付属しています。 |
esbuild | 0.20.2 | JavaScript/TypeScript のバンドラーです。 |
DevshellでElmプロジェクト作成
まずは、Elmの開発環境に必要なNixパッケージをDevshellとして設定しましょう。
- シェルでプロジェクトとなるベースディレクトリ
example-elm
を作成します。 - ベースディレクトリ
example-elm
をカレントディレクトリに設定します。 -
git init
を実行してGitリポジトリを初期化します。 - NodeJSとElmに関するgitignoreファイルを取得します。
nix run nixpkgs#curl -- -sL https://www.toptal.com/developers/gitignore/api/node,elm >> .gitignore
- Nix Flakesファイルとして
flake.nix
を作成します。
{
description = "Elm Example";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
flake-parts.url = "github:hercules-ci/flake-parts";
};
outputs = inputs @ {self, ...}:
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
systems = ["x86_64-linux"];
perSystem = {
config,
self',
pkgs,
system,
...
}: {
devShells.default = with pkgs;
mkShell {
nativeBuildInputs =
[
nodejs_21
nodePackages.npm
]
++ (with elmPackages; [
elm
]);
};
};
};
}
-
git add .
を実行します。 -
nix develop
を実行して、Devshellに入ります。 -
elm init
を実行して、Elmプロジェクトを作成します。 - Elmのソースコードとしてファイルを
src/Main.elm
に配置して、その内容を以下のように記述します。
module Main exposing (..)
import Browser
import Html exposing (div, h1, text)
main : Program () () ()
main =
Browser.element
{ init = always ( (), Cmd.none )
, update = always (\() -> ( (), Cmd.none ))
, view = always (h1 [] [ text "Elm Example" ])
, subscriptions = always Sub.none
}
これでElmプロジェクトを作成できました。
elm-watchで開発サーバーを設定
Elmで構築したアプリケーションをデバッグしやすいように、elm-watchで開発サーバーを設定しましょう。
- NodeJSのパッケージファイルとして
package.json
を作成し、ファイルの内容を以下のように記述します。
{
"name": "example-elm",
"version": "0.1.0",
"scripts": {
"start": "npx elm-watch hot"
}
}
- シェルで
npm install elm-watch@2.0.0-beta.2
を実行してelm-watchをインストールします。 - HTMLファイルとして
public/index.html
を配置し、ファイルの内容を以下のように記述します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="build/main.js" defer></script>
<title>Elm Example</title>
</head>
<body>
<div id="app"></div>
<script type="module">
const app = Elm.Main.init({
node: document.getElementById('app')
});
</script>
</body>
</html>
- elm-watchの設定ファイルとして、JSONファイル
elm-watch.json
を作成し、ファイルの内容を以下のように記述します。
{
"targets": {
"Example Elm": {
"inputs": [
"src/Main.elm"
],
"output": "public/build/main.js"
}
},
"serve": "public/"
}
- 続いて、シェルに下記のコマンドを実行すると、elm-watchの開発サーバーが起動します。開発サーバーのポートは自動的に割り当てられます。標準出力に含まれるURLへアクセスして、動作を確認してください。
npm run start
elm2nixでElmのパッケージ指定をNix式に変換
実行ファイルをビルドするためには、Elmのパッケージ指定をNix式で表現しなければいけません。elm2nixというツールを使ってelm.jsonファイルからNix式へ変換しましょう。
- シェルで下記のコマンドを実行します。
nix run nixpkgs#elm2nix convert > elm-srcs.nix
nix run nixpkgs#elm2nix snapshot
これによって、 elm-srcs.nix ファイルにelm.jsonのパッケージ指定がNix式として記述されます。開発中の際に、elmのパッケージを新しくインストールやアンインストールした場合は、再度この手順を実施してください。
dream2nixでNodeJSプロジェクトを追加
dream2nixを使ってNodeJSプロジェクトを追加します。
- ファイル
flake.nix
のinputs
項目にdream2nix
を追加します。
dream2nix = {
url = "github:nix-community/dream2nix";
inputs.nixpkgs.follows = "nixpkgs";
};
- ファイル
package.json
を編集し、以下のスクリプトを追加します。start
コマンドあとに追加する場合は、行末に,
をつけてください。
"build": "elm make src/Main.elm --optimize --output=public/build/main.js && npm run esbuild -- --minify",
"esbuild": "npx esbuild app.ts --bundle --outdir=public/build --public-path=/build/"
- シェルで
npm install esbuild
を実行して、esbuildをインストールします。 - esbuildのエントリポイントファイルとして
app.ts
を作成して、ファイルの内容を以下のように記述します。
function run(): void {
window.Elm?.ApplicationMain?.init();
}
run();
export {};
- ファイル
flake.nix
のperSystem
項目に下記を追加します。
packages.default = inputs.dream2nix.lib.evalModules {
packageSets.nixpkgs = pkgs;
modules = [
./default.nix
{
paths.projectRoot = ./.;
paths.package = ./.;
}
];
};
- Nixパッケージのビルドのため Nixファイル
default.nix
を作成し、ファイルの内容を以下のように記述します。
{
lib,
config,
dream2nix,
...
}: rec {
imports = [
dream2nix.modules.dream2nix.nodejs-package-lock-v3
dream2nix.modules.dream2nix.nodejs-granular-v3
];
name = "example-elm";
version = "0.1.0";
deps = {nixpkgs, ...}: {
inherit
(nixpkgs)
stdenv
nodejs_21
esbuild
caddy
;
inherit (nixpkgs.nodePackages) npm;
inherit
(nixpkgs.elmPackages)
elm
fetchElmDeps
;
};
mkDerivation = {
src = ./.;
buildInputs = with config.deps; [
elm
caddy
];
postConfigure = config.deps.fetchElmDeps {
elmPackages = import ./elm-srcs.nix;
elmVersion = "0.19.1";
registryDat = ./registry.dat;
};
installPhase = ''
cp -R $out/lib/node_modules/${name}/public $out/public
rm -rf $out/lib
mkdir $out/bin
makeWrapper $(type -p caddy) $out/bin/${name} \
--add-flags "file-server --listen :\"\''${PORT:-8000}\" --root $out/public"
'';
};
nodejs-granular-v3 = {
installMethod = "symlink";
};
nodejs-package-lock-v3 = {
packageLockFile = "${config.mkDerivation.src}/package-lock.json";
};
}
default.nix
の内容について説明します。
-
imports
項目では、NodeJSのパッケージを扱うために、dream2nixのnodejs-package-lock-v3
とnodejs-granular-v3
モジュールをインポートしています。 -
deps
項目では、このプロジェクトで使用する依存パッケージを記述しています。 -
mkDerivation
項目では、プロジェクトのビルド手順を記述しています。buildInputs
項目で必要なパッケージを指定し、postConfigure
項目でElmの依存関係を取得しています。installPhase
項目では、esbuildによってバンドルされた成果物をcaddyで配信できるようにパッケージ化しています。 -
nodejs-granular-v3
項目とnodejs-package-lock-v3
項目では、それぞれNix Flakesの出力へのインストール方法とパッケージロックファイルの場所を指定しています。
これで、dream2nixを使ってNodeJSプロジェクトが追加できました。シェルで git add .
したのち、 nix run
を実行して、ウェブブラウザー上で http://localhost:8000
にアクセスして動作を確認してください。
DockerイメージのNixパッケージを追加
Google CloudのCloud Runにデプロイする必要がある場合は、Dockerイメージが必要となるので、実行ファイルをDockerイメージで扱えるように、新しいNixパッケージ oci
を追加しましょう。ファイルflake.nix
の perSystem
項目に以下の内容を記述します。
packages.oci = with pkgs;
dockerTools.buildLayeredImage rec {
name = "example-elm";
tag = "latest";
config = {
Env = [
# https://gist.github.com/CMCDragonkai/1ae4f4b5edeb021ca7bb1d271caca999
"SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt"
];
Cmd = [
"${self.packages.${system}.default}/bin/${name}"
];
};
};
シェルで git add .
したのち、nix build .#oci
を実行すると、Dockerイメージをビルドできます。ローカルもしくはサーバーのシェルで docker load -i result && docker run -p 8000:8000 example-elm
を実行して、ウェブブラウザー上で http://localhost:8000
にアクセスして動作を確認してください。
まとめ
Elmの開発環境を構築する手順は以上となります。本記事では扱いませんでしたが、NixOS Testing libraryという仕組みで結合テストを扱ったり、バックエンドをモノレポとして含めることで、フロントエンドとバックエンド間でのNixパッケージに関する依存関係もNixで扱うことができます。
また、サンプルプロジェクトをGitHubで公開しています。このリポジトリには、terranixによるInfrastructure as Codeや結合テストといった応用の例も含んでおりますので、よろしければご活用ください。
本記事の内容やサンプルプロジェクトは、さまざまなNixの活用例のほんのひとつですが、少しでも、みなさまの快適な開発環境を構築するために役立てば幸いです。お目通しくださり、ありがとうございました。
Discussion