elm reactorとwebpackを使って快適なElm開発環境を構築する

この記事は[Elm Advent Calendar 2019](https://qiita.com/advent-calendar/2019/elm) の14日目の記事です。
Elm入門時に快適な環境構築するための手順書です。

使用したソースは[GitHub](https://github.com/louvre2489/elm-start)にアップしています。Scalaのコードが混ざっていますが、あまり気にしないでください（理由は一番最後に）。Elmのコードの部分は[ここ](https://github.com/louvre2489/elm-start/tree/master/src/main/resources/elm-start-front)で確認できます。

#今回のゴール
快適な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](http://jinjor-labo.hatenablog.com/entry/2016/05/30/165816)という手法があるようですが、イチからCSSを手組みする時ならまだしも、既存のCSSファイルやCSSフレームワークを使用する場合は嬉しさよりも面倒さの方が勝つ気がします。試してはいません。

### CSSフレームワークを使うには？
各種CSSフレームワークを使用する場合は[Elm Bootstrap](http://elm-bootstrap.info/)や[elm-bulma](https://package.elm-lang.org/packages/surprisetalk/elm-bulma/)のようなパッケージもありますが、きっと純粋にCSSフレームワークの用意してくれたクラスだけを使ってレイアウトするってこともないはず。これらを使用した際にElm Reactorで正しくスタイルされて描画されるのかもやっぱり試していません:confused:

### Elm Reactorを使う時は妥協する！？
まぁ、CSSが読み込めなくってもロジックの妥当性は作り込むことはできるはずなので、ゴリゴリ実装している間はスタイルを無視する前提でElm Reactorを使いながら開発するという手もあるのですが、まぁやっぱり画面が寂しいのは気になるわけです。。。

# 解決方法
[webpack](https://webpack.js.org/)を使います。
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](https://github.com/louvre2489/elm-start/tree/master/src/main/resources/elm-start-front)の`package.json`を見てください。CSSには[Bulma](https://bulma.io/)を使用します。

# やってみる
## まずはwebpackなしで画面を作成してみる

<details><summary>Elmのコード</summary><div>

``` elm:src/elm/Example/StyleExample.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) ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
```

</div></details>

さっそくElm Reactorで確認してみましょう。

![範囲を選択_012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/c31af565-f64e-1002-843c-ee3c1f2725cd.png)

↓↓↓

![範囲を選択_015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/c3d0f4a2-38c2-a528-da54-107afe89ff37.png)


**だっせえええええええええええええ！！！！！**[^1]

今回は、姓名を入力するとフルネームをラベルで表示するようにしています。

![範囲を選択_014.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/7ad7cae8-34db-9c19-74ab-7881d50aa39c.png)

Elmのロジックは正しく動作するので業務ロジックの妥当性は確認できますが、画面が寂しすぎてやる気が一気にゼロになりますね。


## webpackを使用する
ショボショボな画面を見てしまって既にやる気はゼロになっているわけですが、ここからやる気を復活させるための設定をしていきましょう。
まずはwebpack。~~webpackとかよくわからないので~~ごちゃごちゃ書くとわけわからなくなるので、`webpack.config.js`は最小限の設定だけにしています。必要に応じて良い感じに加工してください。（誤りがあったら指摘をお願いします）

ポイントは以下の部分です。

``` js: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]',
    // 出力先のパス（※１）
    path: path.join(__dirname, 'public/js')
  },

  // 〜 途中省略 〜
};
```

また、webpackにファイルの変更を監視させることで、コードの編集時に自動的に再トランスパイルが発生するようにしています。これで、『コードをエディタでコードを編集したら、いったんターミナルでmakeして・・・』みたいな手間が発生しないようにします。

``` js:webpack/config.js

module.exports = merge(common, {

    // 〜 途中省略 〜

    watch:true,
    watchOptions: {
      ignored: /node_modules/,
      aggregateTimeout: 300,
      // ５秒毎にポーリング
      poll: 5000
    },
  });
```



## webpackでトランスパイルされたjsを使用する

### HTMLを用意する

まずはElmで書いたコードを埋め込むHTMLを用意します。

``` html:public/html/StyleExample.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`の（※１）で出力先に指定した場所であり、`StyleExample.js`はwebpackに出力させます。それでは、webpackに食わせるためのjsファイルを作成します。

```js:src/StyleExample.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で動かしてやるだけです。

``` bash
 > 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`を開いてみます。

![範囲を選択_016.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/bd0aa749-8f1a-7594-9bdd-0f46edf17601.png)

↓↓↓

![範囲を選択_017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/7eaf1d25-b59b-1a9f-d3c7-d0fb905658d2.png)

Elm Reactorで動かしているのにスタイルも反映されてますね！
実際に動かしてみます。

![範囲を選択_018.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/2d416c1a-4df2-f7a1-8004-08b5a2184484.png)

**完璧**:laughing:

### スタイルを追加してみる
これにスタイルを追加してみます。

``` css:public/css/custom/custom.css
html {
  height: 100%;
}

body {
  height: 100%;
  background-color: #E6E6FA;
}

.custom {
  font-size: 15em;
}
```

htmlの`head`にカスタムのcssファイルを追加します。

``` StyleExample.html
<link rel="stylesheet" href="/public/css/custom/custom.css">
```

`p`の`class`にもスタイルの指定を追加してやります。

``` elm:src/elm/Example/StyleExample.elm
[ p
  [ class "label has-text-info custom" ]
  [ text (makeFullName model) ]
]
```

Elmの修正を行いましたが、webpackが自動的に変更を監視して自動的にトランスパイルしてくれているので、ソースを修正したら即画面で確認できます。
それでは、まずは直接`/src/elm/Example/StyleExample.elm`を直接開いてみます。

![範囲を選択_022.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/7353586e-afa5-50a3-cf6c-4c89d1a86f56.png)

↓↓↓

![ワークスペース 1_025.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/07397ef1-5143-69de-f00d-f4080cd33075.png)

**ほげえええええええええええｗｗｗ！！！！！**[^1]
スタイルが反映されてなくて、やる気もなくなりますね。

お次は`/public/html/StyleExample.html`を開いてみる。

![範囲を選択_023.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/f89f9773-7dbe-5aed-2918-d3f4021e813f.png)

↓↓↓

![ワークスペース 1_024.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/ab2f4be9-84ff-b674-0ff0-36f736bca1fd.png)

実際にWebアプリとしても動かしてみて確認してみます。(UTLが微妙に違うのがわかると思います。GitHubにScalaのコードがアップされているのはこの確認のためです)

![Screenshot from 2019-12-14 02-10-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64360/e0b8c4fc-0c66-ff7e-b195-a546fd5fa3b5.png)


Webアプリとして動かした時とElm Reactorで表示した時で同じようにスタイルされています！見た目もキレイで、失ったやる気も俄然復活してきたのではないでしょうか:relaxed:
これでストレスの無い快適なElm開発環境を構築することができました！

# 免責事項
罠に陥ることもあるようです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アップデート来てた！<br>たぶんこれが落ちてた原因<br><br>怖すぎるのでwebpack依存やめます・・・。<a href="https://t.co/6mPBSuC1rx">https://t.co/6mPBSuC1rx</a></p>&mdash; ABAB↑↓BA (@ababupdownba) <a href="https://twitter.com/ababupdownba/status/1192611288288878592?ref_src=twsrc%5Etfw">November 8, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

---------------------------------------

[^1]: [トリビュート](https://qiita.com/kinchiki/items/57e9391128d07819c321)