ElmのチュートリアルアプリをDocker + Webpack4 で試してみたメモ

[elmチュートリアル][*1]の[アプリケーションの開始][*4]を試してみた時に、つまづいたりしたのでメモ。

## 動作環境

* windows10
* vagrant2.0.2
* virtualbox5.1.26
* ubuntu-16.04
* Docker version 17.09.0-ce, build 19e2cf6
* docker-compose version 1.18.0, build 8dd22a9

## 動かした環境のpackage.json

チュートリアルと異なるのはwebpackのバージョンが4になっていること。
これで結構ハマった。

```json:package.json
{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start":  "nf start", 
    "client": "webpack-dev-server", 
    "build":  "webpack", 
    "api":    "node api.js", 
    "test":   "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ace-css": "^1.1.0",
    "css-loader": "^0.28.10",
    "elm": "^0.18.0",
    "elm-webpack-loader": "^4.4.0",
    "file-loader": "^1.1.10",
    "font-awesome": "^4.7.0",
    "sanitize.css": "^5.0.0",
    "style-loader": "^0.20.2",
    "url-loader": "^0.6.2",
    "webpack": "^4.0.1"
  },
  "devDependencies": {
    "elm-test": "^0.18.12",
    "foreman": "^2.0.0",
    "json-server": "^0.12.1",
    "webpack-cli": "^2.0.9",
    "webpack-dev-server": "^3.0.0"
  }
}
```


## ソースコード

### 環境

チュートリアルのままでは、elm-webpack-loaderでエラーが起こりwebpack4で動かなかった。
エラーの原因であるwebpack3でdeprecateで4で消えたthis.optionsを、this.queryに置換している。[*][*8]
これ、プルリクとか出したほうがいいのだろうか。やったことないけれど。

```docker:docker/elm-dev/Dockerfile
FROM node:9.6.0

# コンテナ上の作業ディレクトリ作成
WORKDIR /app

# 後で確認出来るようにpackage.jsonを作成
RUN npm init -y

# elmインストール
RUN npm i -S elm
RUN npm i -D elm-test
RUN npx elm package install elm-lang/html -y

# webpackv4
RUN npm i -S webpack
# v4から追加
RUN npm i -D webpack-cli
# 開発サーバ
RUN npm i -D webpack-dev-server
# loader
RUN npm i -S elm-webpack-loader
RUN npm i -S file-loader
RUN npm i -S css-loader
RUN npm i -S style-loader
RUN npm i -S url-loader

# バックエンド
RUN npm i -D json-server

# サーバ複数起動用
RUN npm i -D foreman

# フロントエンド
RUN npm i -S sanitize.css
RUN npm i -S ace-css
RUN npm i -S font-awesome

# "source-directories": [ "src" ], に変更
RUN sed -i -e "s/\".\"/\"src\"/" /app/elm-package.json

# package.json設定
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"api\": \"node api.js\", /" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"build\": \"webpack\", /" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"client\": \"webpack-dev-server\", /" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"start\": \"nf start\", /" /app/package.json

# https://medium.com/webpack/webpack-4-migration-guide-for-plugins-loaders-20a79b927202
# deprecateなthis.optionsをthis.queryに置き換え。
RUN sed -i -e "s/this\.options/this\.query/g" /app/node_modules/elm-webpack-loader/index.js
```

```yaml:docker/docker-compose.yml
version: '3'
services:
  elm-dev:
    build: ./elm-dev
    volumes:
      - ../tutorial/webpack.config.js:/app/webpack.config.js
      - ../tutorial/api.js:/app/api.js
      - ../tutorial/db.json:/app/db.json
      - ../tutorial/src:/app/src
      - ../tutorial/Procfile:/app/Procfile
      - ../tutorial/dist:/app/dist
    ports:
      - 3000:3000
      - 4000:4000
    command: [yarn, start]
```

dev-serverの設定をdockerとvagrantを使用したときの書き方に。

```javascript:tutorial/webpack.config.js
var path = require("path");

const env = process.env.NODE_ENV;

module.exports = {
  mode: env || `development`,
  entry: {
    app: [
      './src/index.js'
    ]
  },

  output: {
    path: path.resolve(__dirname + '/dist'),
    filename: '[name].js',
  },

  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: [
          'style-loader',
          'css-loader',
        ]
      },
      {
        test:    /\.html$/,
        exclude: /node_modules/,
        loader:  'file-loader?name=[name].[ext]',
      },
      {
        test:    /\.elm$/,
        exclude: [/elm-stuff/, /node_modules/],
        loader:  'elm-webpack-loader?verbose=true&warn=true',
      },
      {
        test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url-loader?limit=10000&mimetype=application/font-woff',
      },
      {
        test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'file-loader',
      },
    ],

    noParse: /\.elm$/,
  },

  // 開発サーバの設定
  devServer: {
    // インラインモード
    inline: true,
    stats: { colors: true },
    // 3000番ポートで起動
    port: 3000,
    // dockerのコンテナ上でサーバを動かすときは以下の設定で全ての接続を受け入れる
    host:"0.0.0.0",
  },
  // Windowsのvagrantの仕様でポーリングしないとファイルの変更を感知できない
  watchOptions: {
    aggregateTimeout: 300,
    // ５秒毎にポーリング
    poll: 5000
  },
};
```

foreman用の設定。
yarnはdockerのnode9.6だと最初から入っている。

```.:tutorial/Procfile
api: yarn api
client: yarn client
```

### バックエンド

ここはチュートリアルどおり。

```javascript:tutorial/api.js
var jsonServer = require('json-server')

// Returns an Express server
var server = jsonServer.create()

// Set default middlewares (logger, static, cors and no-cache)
server.use(jsonServer.defaults())

var router = jsonServer.router('db.json')
server.use(router)

console.log('Listening at 4000')
server.listen(4000)
```

```json:tutorial/db.json
{
  "players": [
    { "id": "1", "name": "Sally", "level": 2 },
    { "id": "2", "name": "Lance", "level": 1 },
    { "id": "3", "name": "Aki", "level": 3 },
    { "id": "4", "name": "Maria", "level": 4 },
    { "id": "5", "name": "Julian", "level": 1 },
    { "id": "6", "name": "Jaime", "level": 1 }
  ]
}
```

### フロントエンド

こちらもチュートリアルどおり。

```html:tutorial/src/index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Elm SPA example</title>
  </head>
  <body>
    <div id="main"></div>
    <script src="/app.js"></script>
  </body>
</html>
```

```javascript:tutorial/src/index.js
'use strict';

require('ace-css/css/ace.css');
require('font-awesome/css/font-awesome.css');

// index.htmlがdistにコピーされるようにRequireする
require('./index.html');

var Elm = require('./Main.elm');
var mountNode = document.getElementById('main');

// .embed()はオプションの第二引数を取り、プログラム開始に必要なデータを与えられる。たとえばuserIDや何らかのトークンなど
var app = Elm.Main.embed(mountNode);
```

```elm:tutorial/src/Main.elm
module Main exposing (..)

import Html exposing (Html, div, text, program)

-- モデル


type alias Model =
    String


init : ( Model, Cmd Msg )
init =
    ( "Hello", Cmd.none )



-- メッセージ


type Msg
    = NoOp



-- ビュー


view : Model -> Html Msg
view model =
    div []
        [ text model ]



-- 更新


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            ( model, Cmd.none )



-- サブスクリプション(購読)


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none



-- MAIN


main : Program Never Model Msg
main =
    program
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }
```

## 確認

`docker-compose up`で起動。
vagrantで作った仮想環境のIPが192.168.50.10だとして以下のように確認。

サーバのテスト。db.jsonのid:1のデータが表示されればOK
http://192.168.50.10:4000/players/1

フロントエンドのテスト。Helloと出力されていればOK
http://192.168.50.10:3000

webpack-dev-server3.0ではホットロードがデフォルトらしく、Main.elmのHelloを書き換えるとブラウザが自動的に更新される。

[この時点のソース](https://github.com/hibohiboo/develop/blob/cf0746eb2db7e66e2b0a9f91404a602a406c1f46/tutorial/lesson/elm/)

[チュートリアル終了時のソース](https://github.com/hibohiboo/develop/blob/edfda82c22dd47829e9878230a93ba57657b2160/tutorial/lesson/elm/)


## 参考

[elmチュートリアル][*1]
[elm思考法][*2]
[tutorial source][*3]
[アプリケーションの開始][*4]
[最新版で学ぶWebpack4のモジュールハンドラ][*5]
[gulpでwebpack4][*6]
[次のリリースであるwebpack 4の主な変更点まとめ][*7]
[webpack 4: migration guide for plugins/loaders][*8]
[elm-webpack-loader][*9]
[example][*10]
[Elmでオブジェクト指向プログラマのための関数型入門][*11]
[webpack4が出たのでメモ][*12]

[*1]:https://www.elm-tutorial.org/jp/
[*2]:https://qiita.com/ababup1192/items/629585f8b2ae953a9b47#elm%E9%96%8B%E7%99%BA%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%80%9D%E8%80%83%E9%96%8B%E7%99%BA%E3%83%95%E3%83%AD%E3%83%BC
[*3]:https://github.com/sporto/elm-tutorial-app
[*4]:https://www.elm-tutorial.org/jp/04-starting/cover.html
[*5]:https://ics.media/entry/12140
[*6]:https://qiita.com/tonkotsuboy_com/items/2d4f3862e6d05dc0bea1#_reference-10a6b7f15bcab71313af
[*7]:http://abouthiroppy.hatenablog.jp/entry/2017/12/25/100004
[*8]:https://medium.com/webpack/webpack-4-migration-guide-for-plugins-loaders-20a79b927202
[*9]:https://github.com/elm-community/elm-webpack-loader
[*10]:https://github.com/evancz/elm-architecture-tutorial
[*11]:https://qiita.com/ababup1192/items/c9c2efb7e4bf69ae2ec4
[*12]:https://qiita.com/shota_key/items/4a49c24ef2751a88e0bd