webpackでElm + Sassな開発環境を作る〜ついでにHMR〜

### はじめに

Elmで書かれたアプリケーションをSassで装飾したい。
ついでにホットリロード(以下HMR)な環境にしたい。
あると思います。

### 開発環境

macOS Catalina 10.15.2
docker desktop 2.1.0.5
Elm 0.19.1

なお、以下の作業は全てElmがインストールされたDockerコンテナ上で行っております。

### プロジェクトの初期化
まずは開発環境上でElmがインストールされてるかを確認してみます。

```sh
$ elm --help
Hi, thank you for trying out Elm 0.19.1. I hope you like it!
```

バッチリですね。
プロジェクトディレクトリを作成していきましょう。

```sh
$ mkdir qiita && cd qiita
$ yarn init
$ elm init
$ ls
elm.json  package.json  src
```
ここまででプロジェクトの初期化は完了です！

### 依存ライブラリのインストール

`yarn add`で必要なライブラリをインストールしていきます。

#### webpack関連

```sh
$ yarn add webpack webpack-cli webpack-dev-server -D
```

#### Babel関連

```sh
$ yarn add @babel/core @babel/preset-env babel-loader -D
```

必須ではないのですが、
エントリーポイントのJavaScriptにES6を使いたかったためインストールしています。
ついでに`.babelrc`ファイルも追加しておきましょう。

```sh
$ touch .babelrc
```

``` js:.babelrc
{
  "presets": [
    "@babel/preset-env"
  ]
}
```

#### Sass関連

```sh
$ yarn add node-sass sass-loader css-loader style-loader -D
```

#### Elm関連

```sh
$ yarn add elm-webpack-loader elm-hot-webpack-loader -D
```

webpackで`.elm`ファイルを処理するためにはローダーを用意する必要があります。
今回はHMRを利用したいのでそのためのローダーも追加でインストールしています。

[elm-webpack-loader](https://github.com/elm-community/elm-webpack-loader)
[elm-hot-webpack-loader](https://github.com/klazuka/elm-hot-webpack-loader)

依存ライブラリのインストールは以上となります！
最後に`package.json`にコマンドを登録しておきましょう。

```json:package.json
{
  "name": "qiita",
〜中略〜
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack-dev-server"
  },
〜後略〜
}
```

### webpack.config.jsの記述

下準備が整ったところで設定ファイルを書いていきましょう！

```sh
$ touch webpack.config.js
```

まずは雛形を書いてみます。

```js:webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = (env, arg) => {
  return {
    plugins: [],
    entry: './src/index.js',
    output: {
      filename: 'main.js',
      path: path.resolve(__dirname),
    },
    devServer: {},
    module: {
      rules: []
    }
  }
};
```

ここから各種設定項目を埋めてまいります。

#### プラグインの追加

```js
plugins: [
  new webpack.HotModuleReplacementPlugin()
],
```

今回はHMRを利用したいのでプラグインを追加しています。

#### 開発サーバの設定

```js
devServer: {
  hot: true,
  stats: 'errors-only',
  port: 3000,
  host: '0.0.0.0',
  historyApiFallback: {
    rewrites: [
      { from: /.*/, to: '/' }
    ]
  }
},
```

`devServer.hot`を`true`にすることでHMRを有効化しています。
また、SPA開発時は`devServer.historyApiFallback`オプションを設定することで、
常にSPAのエントリーポイントとなるファイルを返すことが可能です。
上記の例では常に`index.html`を返すようにしています。

#### ローダーの設定

```js
module: {
  rules: [
    {
      test: /\.elm$/,
      exclude: [/elm-stuff/, /node_modules/],
      use: [
        { loader: 'elm-hot-webpack-loader' },
        {
          loader: 'elm-webpack-loader',
          options: {
            cwd: __dirname,
            optimize: (argv.mode == 'production') ? true : false
            debug: (argv.mode == 'development') ? true : false,
          }
        }
      ]
    },
    {
      test: /\.s[ac]ss$/i,
      use: [
        'style-loader',
        'css-loader',
        'sass-loader',
      ],
    },
    {
      test: /\.js$/,
      exclude: [/elm-stuff/, /node_modules/],
      loader: 'babel-loader'
    }
  ]
}
```

`.elm`ファイルを処理する際、webpackのmodeによって
Elmのビルド時にデバッグモードか最適化モードかが切り替わるよう設定しています。
また、ローダーの適用順ですが、[elm-hot-webpack-loader](https://github.com/klazuka/elm-hot-webpack-loader)のドキュメントに従い、
「elm-webpack-loader」-> 「elm-hot-webpack-loader」となるようにします。

#### まとめ
設定は以上となります！

最後に出来上がったコードを再掲します。

```js:webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = (env, argv) => {

  return {
    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ],
    entry: './src/index.js',
    output: {
      filename: 'main.js',
      path: path.resolve(__dirname),
    },
    devServer: {
      hot: true,
      stats: 'errors-only',
      port: 3000,
      host: '0.0.0.0',
      historyApiFallback: {
        rewrites: [
          { from: /.*/, to: '/' }
        ]
      }
    },
    module: {
      rules: [
        {
          test: /\.elm$/,
          exclude: [/elm-stuff/, /node_modules/],
          use: [
            { loader: 'elm-hot-webpack-loader' },
            {
              loader: 'elm-webpack-loader',
              options: {
                cwd: __dirname,
                optimize: (argv.mode == 'production') ? true : false,
                debug: (argv.mode == 'development') ? true : false,
              }
            }
          ]
        },
        {
          test: /\.s[ac]ss$/i,
          use: [
            'style-loader',
            'css-loader',
            'sass-loader',
          ],
        },
        {
          test: /\.js$/,
          exclude: [/elm-stuff/, /node_modules/],
          loader: 'babel-loader'
        }
      ]
    }
  }
};
```

### エントリーポイントとindex.htmlの作成

まずは`index.html`ファイルを作成しましょう。

```sh
$ touch index.html
```

```html:index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1">
</head>

<body>
  <div id="elm"></div>
  <script src="/main.js"></script>
</body>

</html>
```

続いてエントリーポイントとなるJSファイルです。

```sh
$ touch ./src/index.js
```

```js:src/index.js;
import { Elm } from './Main.elm'
import './sass/main.scss'

Elm.Main.init({
  node: document.getElementById('elm')
})
```

### Main.elmとmain.scssの作成

```sh
$ touch src/Main.elm
```

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

import Html exposing (..)
import Html.Attributes exposing (..)


main =
    div [ class "container" ]
        [ h1 [] [ text "hello elm!!" ] ]

```


最後に`main.scss`ファイルを作成します。

```sh
$ mkdir src/sass
$ touch src/sass/main.scss
```

```scss:src/sass/main.scss

.container {
  display: flex;
  justify-content: center;
  align-items: center;

  h1 {
    font-size: 48px;
    color: #428A09;
  }
}
```

お疲れ様でした！
全ての初期ファイルがこれで完成しました！

### 開発サーバの起動

それでは早速開発サーバを起動してみましょう。

```sh
$ yarn dev
```

[http://localhost:3000/](http://localhost:3000/)

`Main.elm`や`main.scss`を編集すると自動的に反映される様子が分かるかと思います！

### さいごに

ここまでで、プロジェクトディレクトリ下の構成は以下のようになります。

```
.
├── src
│   ├── Main.elm
│   ├── index.js
│   └── sass
│       └── main.scss
├── node_modules
├── elm_stuff
├── .babelrc
├── elm.json
├── index.html
├── package.json
├── webpack.config.js
└── yarn.lock
```

ファイル数が少ないため`src`ディレクトリ下に`.elm`も`.js`も`.scss`もまとめて入れてしまっていますが、
実際のプロジェクトではディレクトリ構成はもう少し工夫する必要はありそうです。

ともあれ、私達はElmとSassが使えてHMRな環境を手に入れることができました！
今年はElmも勉強してきたいです。