elm0.19 + webpack4 + babel7 のサンプルをdockerで試したメモ

## 概要

Elm0.18を使っていたが、作成されるソースが大きいと思っていたところ、elm0.19を勧めていただいたので勉強することにした。
手始めに、[elm-webpack-starter : Elm 0.19 with Webpack 4, Hot Reloading & Babel 7][*3]のリポジトリをdockerで試せる環境を作成した。

### 2019/1/9 追記

webpack-serveが非推奨になり、webpack-dev-serverが再び推奨になったため、
webpack.config.jsを修正した記事を以下に追加した。

[elm0.19 + webpack4 + webpack-dev-server のサンプルをdockerで試したメモ](https://qiita.com/hibohiboo/items/d5ab69a62c052e3253c8)

## 開発環境

* Windows 10
* Vagrant 2.1.5
* Virtualbox 5.2.18
* Ubuntu 18.04 LTS (Bionic Beaver)
* Docker version 18.06.0-ce, build 0ffa825
* docker-compose version 1.22.0, build f46880fe

## ディレクトリ構成

```
-
  + bin
  - docker
    - elm
      - Dockerfile
    - docker-compose.yml
  + elm-webpack-starter
```

※ elm-webpack-starterはリポジトリをクローンしたもの。

## ファイル

```dockerfile:docker/elm/Dockerfile
FROM node:10.10.0

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

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

# dev-dependencies
## elmインストール
RUN yarn add --dev elm
RUN yarn add --dev elm-verify-examples

## sass
RUN yarn add --dev node-sass

## webpackインストール
RUN yarn add --dev webpack
RUN yarn add --dev webpack-cli
RUN yarn add --dev webpack-merge
RUN yarn add --dev webpack-serve
### 「History APIを利用して作成されるSPAのためのindexページをプロキシする」ミドルウェア
RUN yarn add --dev koa-connect-history-api-fallback
### plugins
RUN yarn add --dev html-webpack-plugin
RUN yarn add --dev mini-css-extract-plugin
RUN yarn add --dev clean-webpack-plugin
RUN yarn add --dev copy-webpack-plugin

## babelインストール
RUN yarn add --dev @babel/core
RUN yarn add --dev @babel/preset-env
RUN yarn add --dev babel-loader

### loaders
RUN yarn add --dev file-loader
RUN yarn add --dev url-loader
RUN yarn add --dev resolve-url-loader
RUN yarn add --dev css-loader
RUN yarn add --dev style-loader
RUN yarn add --dev sass-loader
RUN yarn add --dev elm-hot-webpack-loader
RUN yarn add --dev elm-webpack-loader

# dependencies
RUN yarn add purecss

# package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"prod\": \"webpack -p\"/g" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"build\": \"webpack\",/g" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"dev\": \"webpack-serve --port 3000\",/g" /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"start\": \"npm run dev\",/g" /app/package.json
RUN sed -i -e "/test/d"  /app/package.json
RUN sed -i -e "s/\(\"scripts\": {\)/\1\n    \"test\": \"elm-test\",/g" /app/package.json
```

```yaml:docker/docker-compose.yml
version: '3'
services:
  elm:
    build: ./elm
    volumes:
      - ../elm-webpack-starter/src:/app/src:z
      - ../elm-webpack-starter/tests:/app/tests
      - ../elm-webpack-starter/elm.json:/app/elm.json
      - ../elm-webpack-starter/webpack.config.js:/app/webpack.config.js
      - ../elm-webpack-starter/.babelrc:/app/.babelrc
    ports:
      - 3000:3000
      - 3002:3002
    environment:
      - NODE_ENV=develop
    command: [yarn, dev, --host, 0.0.0.0, --config, ./webpack.config.js ]
```

elm-webpack-starterの`webpack.config.js`をvagrant + docker環境向けに少し書き足す。

```js:elm-webpack-starter/webpack.config.js
const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const history = require('koa-connect-history-api-fallback');
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

var MODE = process.env.npm_lifecycle_event === "prod" ? "production" : "development";
var filename = MODE == "production" ? "[name]-[hash].js" : "index.js";

var common = {
    mode: MODE,
    entry: "./src/index.js",
    output: {
        path: path.join(__dirname, "dist"),
        publicPath: '/',
        filename: filename
    },
    plugins: [
        new HTMLWebpackPlugin({
            template: "src/index.html",
            inject: "body"
        })
    ],
    resolve: {
        modules: [path.join(__dirname, "src"), "node_modules"],
        extensions: [".js", ".elm", ".scss", ".png"]
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader"
                }
            },
            {
                test: /\.scss$/,
                exclude: [/elm-stuff/, /node_modules/],
                loaders: ["style-loader", "css-loader", "sass-loader"]
            },
            {
                test: /\.css$/,
                exclude: [/elm-stuff/, /node_modules/],
                loaders: ["style-loader", "css-loader"]
            },
            {
                test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                exclude: [/elm-stuff/, /node_modules/],
                loader: "url-loader",
                options: {
                    limit: 10000,
                    mimetype: "application/font-woff"
                }
            },
            {
                test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
                exclude: [/elm-stuff/, /node_modules/],
                loader: "file-loader"
            },
            {
                test: /\.(jpe?g|png|gif|svg)$/i,
                loader: "file-loader"
            }
        ]
    }
};

if (MODE === "development") {
    console.log("Building for dev...");
    module.exports = merge(common, {
        plugins: [
            new webpack.NamedModulesPlugin(),
            new webpack.NoEmitOnErrorsPlugin()
        ],
        module: {
            rules: [
                {
                    test: /\.elm$/,
                    exclude: [/elm-stuff/, /node_modules/],
                    use: [
                        { loader: 'elm-hot-webpack-loader' },
                        {
                            loader: "elm-webpack-loader",
                            options: {
                                // add Elm's debug overlay to output
                                debug: true,
                                forceWatch: true
                            }
                        }
                    ]
                }
            ]
        },
        serve: {
            inline: true,
            stats: "errors-only",
            content: [path.join(__dirname, "src/assets")],
            add: (app, middleware, options) => {
                app.use(history());
            },
            devMiddleware: {
              watch:true, 
              watchOptions:{
                aggregateTimeout: 300,
                poll:1000
              }
            },
            hotClient:{
              host: {
                client: '192.168.50.10',
                server: '0.0.0.0', 
              },
              port:{
                server:3002,
                client: 3002
                }
            },
        },
      watch:true,
      watchOptions: {
        ignored: /node_modules/,
        aggregateTimeout: 300,
        poll: 5000
      },
    });
}
```

以下の部分では、IPアドレス・ポートをdocker用に設定している。

```js
hotClient:{
  host: {
    client: '192.168.50.10', // 仮想環境のIPアドレス
    server: '0.0.0.0',       // Dockerのコンテナ上で動かすのでワイルドカードIPアドレスを指定
  },
  // hot-reloadで使われるポートを固定
  port:{
    server:3002,
    client: 3002
    }
},
```

以下の部分では、ファイルの更新検知用のポーリングの設定をしている。
windowsでvirtual box ubuntuとdockerを組み合わせている場合、watch機能でファイル更新が検知されないため。
macを使っていたり、そもそもlinuxの場合は不要かもしれない。
`webpack`と`webpack-serve`では記述する場所が異なっているので注意。[*1][*1][*2][*2]

```js
watch:true, 
watchOptions:{
  aggregateTimeout: 300,
  poll:1000
}
```

```bash:bin/up.sh
#!/bin/bash

bin_dir=$(cd $(dirname $0) && pwd)
composeFile=${1:-"docker-compose.yml"}
cd $bin_dir/../docker && docker-compose -f $composeFile up
```

## 実行

仮想環境にログインし、以下を実行する。

```
clone https://github.com/simonh1000/elm-webpack-starter.git --depth 1
./bin/up.sh
```

http://192.168.50.10:3000 (仮想環境のIP:今回設定したポート)にアクセスし、hot loadができていることを確認する。

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

## 参考

[github.com/simonh1000/elm-webpack-starter][*3]

[*1]:https://github.com/webpack-contrib/webpack-serve#optionsdevmiddleware
[*2]:https://github.com/webpack/webpack-dev-middleware#watchoptions
[*3]:https://github.com/simonh1000/elm-webpack-starter