Elm を導入 - Elm + Electron + Webpack 覚書(2)

1. Elm のインストール

elm はコマンドとしても使うので、グローバルにインストールする。また、自動でフォーマットを整形してくれる elm-format も一緒にインストールしておく。

yarn global add elm elm-format

elm-format はコマンドを直接起動するのではなく、エディタのプラグインとして実行する。 Visual Studio Code では、歯車マーク→設定→右上の「設定(JSON)を開く」で開く、settings.json に以下の記述を追加する。

    // Elm
    "[elm]": {
        "editor.formatOnSave": true
    },

これにより、elm ファイルを保存すると自動的に elm-format が起動し、自動整形される。

2. elm 環境の構築

いきなり electron に行く前に、まず Elm 単体でのテストをしてみる。ただし、今後のことを考えて、webpack は導入したい。調べたら、elm-webpack-loader というものを見つけた。さらにQiita にこれを使った導入記事 webpackでElm + Sassな開発環境を作る〜ついでにHMR〜 - Qiita を見つけた。とりあえずこれと同じようにやってみて構成を確認してみる。

まず、テストするフォルダを作成し、そこに入る。

mkdir -p elm-webpack-test
cd elm-webpack-test

まず、elm init で Elm を初期化する。

hkob@hMBP ~/e/elm-webpack_test> elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.1/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]:
Okay, I created it. Now read that link!

各作業でどのファイルが書き変わるのかを確認できるように、git リポジトリにしてこまめにコミットしてみる。

git init
gibo dump Elm Node macOS VisualStudioCode Vim > .gitignore

とりあえずここまでをコミットしておく。

まず、このフォルダでyarn initとして yarn の初期化をする。

hkob@hMBP ~/e/elm-webpack_test (master)> yarn init
yarn init v1.22.4
warning ../../package.json: No license field
question name (elm-webpack_test):
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author (Hiroyuki KOBAYASHI <メールアドレス>):
question license (MIT):
question private:
success Saved package.json
✨  Done in 22.31s.

3. 各種ライブラリの構築

まず、webpack 関連のライブラリを devDevelopments にインストールする。

yarn add webpack webpack-cli webpack-dev-server -D

Qiita の参考サイトではエントリーポイントの Javascript に ES6 を使いたいとのことで、babel を入れていた。せっかくなので同じように入れてみる。

yarn add @babel/core @babel/preset-env babel-loader -D

babel の設定ファイルである.babelrcを作成する。

touch .babelrc

中身は Qiita のサイトを参考に記述する。

{
    "presets": [
        "@babel/preset-env"
    ]
}

次に SASS 関係のライブラリを導入する。

yarn add node-sass sass-loader css-loader style-loader -D

最後に elm を webpack で読み込むローダー(elm-webpack-loader)を導入する。また、HMR(ホットリロード)用には別にローダー(elm-hot-webpack-loader)があるようなので、こちらも導入する。

yarn add elm-webpack-loader elm-hot-webpack-loader -D

ここまでインストールしたら、package.json に scripts を追加しておく。最終的な package.json はこんな感じになった。この結果、yarn build でデプロイができ、yarn dev では webpack-dev-server が起動する。webpack-dev-server はファイル更新を監視し、開発ように webpack を自動実行してくれるサーバである。これを使って、HMR を実現する。

{
  "name": "elm-webpack_test",
  "version": "1.0.0",
  "main": "index.js",
  "author": "Hiroyuki KOBAYASHI <hkob@metro-cit.ac.jp>",
  "license": "MIT",
  "scripts": {
    "build": "webpack --mode production",
    "dev": "webpack-dev-server"
  },
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "@babel/preset-env": "^7.9.6",
    "babel-loader": "^8.1.0",
    "css-loader": "^3.5.3",
    "elm-hot-webpack-loader": "^1.1.6",
    "elm-webpack-loader": "^6.0.1",
    "node-sass": "^4.14.1",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.2.1",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.0"
  }
}

4. webpack.config.js の記述

webpack.config.js については、Qiita に詳細な説明が書いてあるので省略する。最終的な 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'
                }
            ]
        }
    }
}

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

index.html は以下の通りになる。main.js を script で body に描画しているだけである。

<!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>

エントリーポイントの src/index.js は以下のようになる。Main.elm を読み込み、html の elm id の部分で Elm.main.int を実行する。また、CSS として sass/main.sass も読み込んでいる。Qiita の記事では scss だったが、試しに sass にしてみた(少しぐらいはアレンジしたい)。

import { Elm } from './Main.elm'
import './sass/main.sass'

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

src/Main.elm は Hello elm を描画するだけのものである。さすがに elm のシンタックスハイライトはないらしい。

module Main exposing (main)

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


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

最後に src/sass/main.sass を記述する。フォルダがないので、先に作っておく。

mkdir -p src/sass

src/sass/main.sass はこんな感じになる。scss と違って、yaml や haml っぽいので、Ruby 使いには、こっちの方が馴染みがある。

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

    h1
        font-size: 48px
        color: #42ba09

6. 開発環境のテスト

package.jsonに書いたように、yarn devで開発環境が立ち上がる。

hkob@hMBP ~/e/elm-webpack_test (master)> yarn dev
yarn run v1.22.4
warning ../../package.json: No license field
$ webpack-dev-server
ℹ 「wds」: Project is running at http://0.0.0.0:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/hkob/elm/elm-webpack_test
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.

ブラウザで http://localhost:3000/ にアクセスすると、以下の画面が表示される。

f:id:hkob:20200524182205p:plain
作成されたアプリによる描画画面

ホットリロードの設定ができているので、Main.elm を書き換えたり、main.sass を書き換えりすると、ブラウザの読み込みをすることなく画面が書き変わる。

f:id:hkob:20200524182727p:plain
Main.elm と main.sass の書き換え(自動リロード後)

今回の Elm + Webpack のアプリを Electron に変えてみようと思う。そのために、前回の Electron + Webpack の結果を参考とする。とりあえずここまでを git にコミットしておく。