この記事はElm Advent Calendar 2019 の14日目の記事です。
Elm入門時に快適な環境構築するための手順書です。
使用したソースはGitHubにアップしています。Scalaのコードが混ざっていますが、あまり気にしないでください(理由は一番最後に)。Elmのコードの部分はここで確認できます。
#今回のゴール
快適なElm開発環境を構築する。
Elm Reactorを使って開発する
Elmには、Elm Reactorという超強力なツールがあります。これがあるだけで快適なElm開発環境を手に入れられます。
Elm Reactorとは?
Elm ReactorはElm専用の組み込みサーバーで、コンパイラ機能とサーバー機能が一緒になった超便利なツールです。こいつはElmをインストールするだけで標準で付いてくる。いつもお世話になっています。
以下のコマンドを実行するだけで画面が立ち上がってきます。
> elm reactor
Go to <http://localhost:8000> to see your project dashboard.
Elm Reactorのちょっと微妙なこと
Elm Reactorでは、外部ファイル化されたCSSファイルを使うことが少し難しいです。Elmのソース内で外部のCSSファイルを読み込んだりはできません(多分)。
CSS in Elm?
CSS in Elmという手法があるようですが、イチからCSSを手組みする時ならまだしも、既存のCSSファイルやCSSフレームワークを使用する場合は嬉しさよりも面倒さの方が勝つ気がします。試してはいません。
CSSフレームワークを使うには?
各種CSSフレームワークを使用する場合はElm Bootstrapやelm-bulmaのようなパッケージもありますが、きっと純粋にCSSフレームワークの用意してくれたクラスだけを使ってレイアウトするってこともないはず。これらを使用した際にElm Reactorで正しくスタイルされて描画されるのかもやっぱり試していません
Elm Reactorを使う時は妥協する!?
まぁ、CSSが読み込めなくってもロジックの妥当性は作り込むことはできるはずなので、ゴリゴリ実装している間はスタイルを無視する前提でElm Reactorを使いながら開発するという手もあるのですが、まぁやっぱり画面が寂しいのは気になるわけです。。。
解決方法
webpackを使います。
Elm ReactorはElm専用のツールというわけではなく、HTML+jsなページも普通に動かすことができます。
つまり、、、
1. Elmでコードを書いて、
2. それをwebpackにトランスパイルさせて、
3. トランスパイルされたjsを使用するHTMLをElm Reactorで開く。
これによって、上記3.の際に合わせてCSSファイルを読み込むことができるので、Elm Reactorを使いながらキレイに整ったレイアウトのページでの動作を確認することができるようになります。
今回使用したツール等のバージョン
Elm 0.19.0
webpack 4.33.0
その他はGitHubのpackage.json
を見てください。CSSにはBulmaを使用します。
やってみる
まずはwebpackなしで画面を作成してみる
Elmのコード
module Example.StyleExample exposing (Model, Msg(..), init, main, update, view)
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
main : Program () Model Msg
main =
Browser.sandbox
{ init = init
, view = view
, update = update
}
type alias Model =
{ firstName : String
, familyName : String
, fullName : String
}
init : Model
init =
{ firstName = ""
, familyName = ""
, fullName = ""
}
type Msg
= InputFirstName String
| InputFamilyName String
update : Msg -> Model -> Model
update msg model =
case msg of
InputFirstName firstName ->
{ model | firstName = firstName }
InputFamilyName familyName ->
{ model | familyName = familyName }
makeFullName : Model -> String
makeFullName model =
model.familyName ++ " " ++ model.firstName
view : Model -> Html Msg
view model =
div
[]
[ section
[ class "section" ]
[ div
[ class "container" ]
[ div
[ class "column is-one-third" ]
[ div
[ class "field" ]
[ div
[ class "control" ]
[ input
[ type_ "text", class "input is-success is-small", placeholder "familyName", value model.familyName, onInput InputFamilyName ]
[]
]
]
, div
[ class "field" ]
[ div
[ class "control" ]
[ input
[ type_ "text", class "input is-rounded is-large", placeholder "firstName", value model.firstName, onInput InputFirstName ]
[]
]
]
, div
[ class "field" ]
[ div
[ class "control" ]
[ p
[ class "label has-text-info" ]
[ text (makeFullName model) ]
]
]
]
]
]
]
さっそくElm Reactorで確認してみましょう。
↓↓↓
だっせえええええええええええええ!!!!!1
今回は、姓名を入力するとフルネームをラベルで表示するようにしています。
Elmのロジックは正しく動作するので業務ロジックの妥当性は確認できますが、画面が寂しすぎてやる気が一気にゼロになりますね。
webpackを使用する
ショボショボな画面を見てしまって既にやる気はゼロになっているわけですが、ここからやる気を復活させるための設定をしていきましょう。
まずはwebpack。webpackとかよくわからないのでごちゃごちゃ書くとわけわからなくなるので、webpack.config.js
は最小限の設定だけにしています。必要に応じて良い感じに加工してください。(誤りがあったら指摘をお願いします)
ポイントは以下の部分です。
// Elmのコードパス
const elmBasePath = path.resolve(__dirname, 'src/elm');
// Elmを呼び出すjs(後述)のコードパス
const jsBasePath = path.resolve(__dirname, 'src/');
// Elmのコードのサブディレクトリ
// 対象のサブディレクトリが増えたら追記してやらないといけない
const elmCompileFolders = ['Example'];
// 対象ファイルをセットするオブジェクト
const entries = {};
// Elmのソースファイルをセット
const elmTargets = glob.sync(`${elmBasePath}/+(${elmCompileFolders.join('|')})/*.elm`);
elmTargets.forEach(value => {
const re = new RegExp(`${elmBasePath}/`);
const key = value.replace(re, '');
entries[key] = value;
});
// jsのソースファイルをセット
// 本当はelmとディレクトリ構成を合わせて/src/js/Example/*.jsにしたいんだけど、
// なぜかwebpackがエラーになるのでjsは雑にsrc直下に置いています・・・
const jsTargets = glob.sync(`${jsBasePath}/*.js`);
jsTargets.forEach(value => {
const re = new RegExp(`${jsBasePath}/`);
const key = value.replace(re, '');
entries[key] = value;
});
// 〜 途中省略 〜
var common = {
// 上記で指定したファイルを対象とする
entry: entries,
// 出力の設定
output: {
// 出力するファイル名
filename: '[name]',
// 出力先のパス(※1)
path: path.join(__dirname, 'public/js')
},
// 〜 途中省略 〜
};
また、webpackにファイルの変更を監視させることで、コードの編集時に自動的に再トランスパイルが発生するようにしています。これで、『コードをエディタでコードを編集したら、いったんターミナルでmakeして・・・』みたいな手間が発生しないようにします。
module.exports = merge(common, {
// 〜 途中省略 〜
watch:true,
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 300,
// 5秒毎にポーリング
poll: 5000
},
});
webpackでトランスパイルされたjsを使用する
HTMLを用意する
まずはElmで書いたコードを埋め込むHTMLを用意します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Elm Advent Calendar 2019</title>
<link rel="stylesheet" href="/public/css/bulma-0.8.0/css/bulma.min.css">
</head>
<body>
<div id="elm"></div>
<script type="text/javascript" src="/public/js/StyleExample.js"></script>
</body>
</html>
id="elm"
を指定したdiv
にElmのview
で作成した内容を出力します。
Elmを呼び出すためのjsを用意する
HTMLではpublic/js/StyleExample.js
を呼び出していますが、このファイルは直接作成しません。public/js
はwebpack.config.js
の(※1)で出力先に指定した場所であり、StyleExample.js
はwebpackに出力させます。それでは、webpackに食わせるためのjsファイルを作成します。
'use strict';
const {Elm} = require('./elm/Example/StyleExample.elm');
const mountNode = document.getElementById('elm');
const app = Elm.Example.StyleExample.init({node: mountNode});
これでHTML <--> js <--> Elmが繋がりました。
動かしてみる
Elm Reactorで開発する
ここまで準備ができたらあとはElm Reactorで動かしてやるだけです。
> elm reactor & webpack (git)-[master]
[1] 22916
Go to <http://localhost:8000> to see your project dashboard.
Building for dev...
webpack is watching the files…
Hash: 94efc514a17db9519d85
Version: webpack 4.41.2
Time: 1629ms
Built at: 2019-12-04 01:50:26
Asset Size Chunks Chunk Names
Example/StyleExample.elm 276 KiB Example/StyleExample.elm [emitted] Example/StyleExample.elm
StyleExample.js 277 KiB StyleExample.js [emitted] StyleExample.js
Entrypoint Example/StyleExample.elm = Example/StyleExample.elm
Entrypoint StyleExample.js = StyleExample.js
[./src/StyleExample.js] 192 bytes {StyleExample.js} [built]
[./src/elm/Example/StyleExample.elm] 261 KiB {Example/StyleExample.elm} {StyleExample.js} [built]
コードの変更は監視させているので、elm reactor & webpack
は一度実行したらほったらかしです。
ブラウザで確認
今度は前回とは違って、Elmのファイルを指定して開かずに先ほど用意した/public/html/StyleExample.html
を開いてみます。
↓↓↓
Elm Reactorで動かしているのにスタイルも反映されてますね!
実際に動かしてみます。
完璧
スタイルを追加してみる
これにスタイルを追加してみます。
html {
height: 100%;
}
body {
height: 100%;
background-color: #E6E6FA;
}
.custom {
font-size: 15em;
}
htmlのhead
にカスタムのcssファイルを追加します。
<link rel="stylesheet" href="/public/css/custom/custom.css">
p
のclass
にもスタイルの指定を追加してやります。
[ p
[ class "label has-text-info custom" ]
[ text (makeFullName model) ]
]
Elmの修正を行いましたが、webpackが自動的に変更を監視して自動的にトランスパイルしてくれているので、ソースを修正したら即画面で確認できます。
それでは、まずは直接/src/elm/Example/StyleExample.elm
を直接開いてみます。
↓↓↓
ほげえええええええええええwww!!!!!1
スタイルが反映されてなくて、やる気もなくなりますね。
お次は/public/html/StyleExample.html
を開いてみる。
↓↓↓
実際にWebアプリとしても動かしてみて確認してみます。(UTLが微妙に違うのがわかると思います。GitHubにScalaのコードがアップされているのはこの確認のためです)
Webアプリとして動かした時とElm Reactorで表示した時で同じようにスタイルされています!見た目もキレイで、失ったやる気も俄然復活してきたのではないでしょうか
これでストレスの無い快適なElm開発環境を構築することができました!
免責事項
罠に陥ることもあるようです。
アップデート来てた!
— ABAB↑↓BA (@ababupdownba) November 8, 2019
たぶんこれが落ちてた原因
怖すぎるのでwebpack依存やめます・・・。https://t.co/6mPBSuC1rx