Elm での開発用テンプレート elm-starfighter を作った
自作の Elm 開発用テンプレート「 elm-starfighter 」が形になってきたので、その説明を書きます。
elm-starfighter とは?
関数型言語 Elm を使った Web アプリケーション開発用のテンプレートです。webpack や Parcel といったモジュールバンドラーを使用せず、npm scripts で完結しています。Elm での開発に最低限必要なものをシンプルに使えるのが、elm-starfighter の特徴です。
なぜ作ろうと思ったのか?
JavaScript での開発と同様に、Elm においても webpack や Parcel を使った開発方法が普及しています。しかし、Elm はモジュールバンドラーを必要とはしていないかもしれません。それならばモジュールバンドラーに頼らない、よりシンプルな開発ができるのではないかと考えました。
既に知られている create-elm-app や elm-webpack-starter に対して「毎回使うには大きすぎる」と感じたことも動機の 1 つです。Elm 入門者が簡単に使える開発ツールがあれば、Elm の持つ魅力をもっと引き出せるのではないかと思います。
開発の方針
Elm 入門者に優しく
公式ガイドや『基礎からわかる Elm』を読んだあとで、すぐに使ってもらえるよう意識して作りました。使い方に迷うようなところがあれば改善していきたい。
Elm の持つ魅力を引き出す
JavaScript と Elm では何が違うのかを意識しながら作りました。標準の elm make コマンドはデバッグ機能を使うことができますし、そこに開発用サーバー機能を付加した elm-live も非常に強力です。それらの良さを残しつつ、苦手な部分を補うようにしました。
モジュールバンドラーと戦わない
適材適所を意識すること。例えば、Port を積極的に使うならモジュールバンドラーの方が有利のはず。 (私は Port をほとんど使わないので、推測で言っています)
謝辞
@ababup1192 さんには、開発初期の段階で多くの相談に乗っていただきました。ありがとうございました。
簡単に使えるので書くことがない
いちばん簡単な試し方は以下のコマンドを実行する方法です。
$ git clone https://github.com/y047aka/elm-starfighter.git
$ cd elm-startfighter
$ npm install
$ npm start
また、リポジトリのトップから緑色のボタン「Use this template」を選択すると、elm-starfighter を使った新しいリポジトリを作る事もできます。

npm start
npm start を実行すると、
- docsにファイルが生成され
- 開発用サーバーが起動し
- ブラウザに最初のページが表示されます
npm run build
もう 1 つのコマンドは npm run build で起動します。
- publicにファイルが生成されます
生成前のファイルはすべて src にあるので、いつも通りに開発を進められます。
src
 - index.html
 - main.js
 - Main.elm
 - style.sass または style.sass
 - assets(画像など)
ディレクトリ名について
docs は Github Pages を、public は Netlify をすぐに使えるよう意図して設定しています。
おわり
この記事の本編は、ここで終了です。 カスタマイズして使う場合には以下の付録を参考にしてください。
付録 1: package.json を読む
ここに elm-starfighter のすべてがあります。webpack や Parcel の姿はなく、代わりに npm scripts が並んでいます。elm make コマンドや elm-live を使っていることが分かりますね。これから scripts を詳しく見ていきましょう。
{
  "scripts": {
    "clean": "rimraf docs public",
    "watch:html": "cpx -w src/index.html docs",
    "watch:assets": "cpx -w \"src/assets/**/*\" docs/assets",
    "watch:js": "cpx -w src/main.js docs",
    "watch:elm": "elm-live src/Main.elm --open --start-page=index.html --dir=docs -- --output=docs/elm.js",
    "watch:sass": "sass --watch src:docs",
    "watch": "sass src:docs && npm-run-all -p watch:*",
    "compile:html": "cpx src/index.html public",
    "compile:assets": "cpx \"src/assets/**/*\" public/assets",
    "compile:js": "cpx src/main.js public",
    "compile:elm": "elm make src/Main.elm --optimize --output=public/elm.optimized.js",
    "compile:sass": "sass --style=compressed --no-source-map src:public",
    "compile": "npm-run-all -p compile:*",
    "minify:elm": "google-closure-compiler --js=public/elm.optimized.js --js_output_file=public/elm.js && rimraf public/elm.optimized.js",
    "build": "npm-run-all -s clean compile minify:elm",
    "start": "npm-run-all -s clean watch",
    "test": "elm-test"
  },
  "devDependencies": {
    "cpx": "^1.5.0",
    "elm": "0.19.0-bugfix6",
    "elm-live": "3.4.0",
    "elm-test": "^0.19.0-rev6",
    "google-closure-compiler": "^20190819.0.0",
    "npm-run-all": "^4.1.5",
    "rimraf": "^3.0.0",
    "sass": "^1.22.10"
  }
}
npm scripts
以下の 4 種類のコマンドを start と build から実行しています。
- clean
- watch
- compile
- minify:elm
start
clean と watch を順番に実行します。
"start": "npm-run-all -s clean watch"
生成したファイルは docs に出力されます。出力先のディレクトリ名を変更する場合は、以下のコマンド内の docs を新しい名前に書き直します。(実際には package.json をエディタで一括変換すれば問題ありません)
clean
docs と public ディレクトリを 2 つとも削除します。このコマンドのみ、build と共有しています。
"clean": "rimraf docs public"
watch
「watch:」で始まるコマンドを、すべて同時に実行します。それぞれが src のファイルを監視し、変更があれば docs に出力します。
"watch:html": "cpx -w src/index.html docs",
"watch:assets": "cpx -w \"src/assets/**/*\" docs/assets",
"watch:js": "cpx -w src/main.js docs",
"watch:elm": "elm-live src/Main.elm --open --start-page=index.html --dir=docs -- --output=docs/elm.js",
"watch:sass": "sass --watch src:docs",
"watch": "sass src:docs && npm-run-all -p watch:*"
開発用のサーバーは watch:elm の elm-live が起動しています。
build
clean compile minify:elm を順番に実行しています。
"build": "npm-run-all -s clean compile minify:elm",
生成したファイルは public に出力されます。出力先のディレクトリ名を変更する場合は、以下のコマンド内の public を新しい名前に書き直します。
compile
「compile:」で始まるコマンドを、すべて同時に実行します。
"compile:html": "cpx src/index.html public",
"compile:assets": "cpx \"src/assets/**/*\" public/assets",
"compile:js": "cpx src/main.js public",
"compile:elm": "elm make src/Main.elm --optimize --output=public/elm.optimized.js",
"compile:sass": "sass --style=compressed --no-source-map src:public"
"compile": "npm-run-all -p compile:*"
注意: minify まで実行しないと elm.js を出力できない実装になっています。cpx でどうにかしたい。(2019 年 7 月 6 日)
minify:elm
elm.optimized.js を圧縮し、elm.js として出力します。
"minify:elm": "google-closure-compiler --js=public/elm.optimized.js --js_output_file=public/elm.js && rimraf public/elm.optimized.js"
付録 2: devDependencies
使用した npm のパッケージについて簡単にコメントします。
cpx
ファイル・フォルダのコピーを、Mac でも Windows でも出来るように。
--watch のオプションがあり、devDependencies の記述量を減らすことに繋がった。
(内部的には chokidar かな?)
cpx で上手くいかない場合は、ncp で妥協することになる。
elm
Elm のコンパイラ。
elm-live
browser-sync を試したものの、コンパイルエラーを無視してサーバーが起動してしまうため、 elm-live が最適という結論になった。
elm-test
テスト(script 未実装)
google-closure-compiler
Elm をコンパイルした後の js ファイルを圧縮する。 elm-minify が deprecated となり、こちらが推奨されていたので使用した。
node-sass
SASS を扱うために使用。いつか sass(Dart Sass)に変えるかもしれない。 sass(Dart Sass)に変更しました。(2019 年 7 月 9 日)
npm-run-all
コマンドの直列実行と並列実行を読みやすく記述できる。
rimraf
フォルダの削除を、Mac でも Windows でも出来るように。