はじめに

私はブラウザ上で動く簡単なアプリを作る際にelmを用いることが多い。 elmはブラウザの状態とユーザーの入力を言語の機能とそのアーキテクチャで簡単に管理できるため、簡単なアプリであればほとんど迷うことがなく作りたいものが作れる点で気にいっている。 ただ最近では、elmの便利な状態管理の手法は JS のフレームワークにも導入され、React のコンポーネントを用いた UI 開発や JS のその他の資源と組み合わせることで効率的にフロントエンドを開発することが主流になってきている。 このような流れのためか、ここ数年はelmを用いた開発を行う人が極端に少なくなってきていると感じている。 ただフロントエンドの専門家ではない私からすると、フレームワークの選定に迷う必要がない点や、tsにするかjsにするか迷わなくて良い点や、Haskellライクな言語の楽さとelm-formatの利便性などの観点から考えると、簡単な Web アプリであれば依然としてelmを用いて開発を行うことは悪くない選択肢だと思っている。 この記事ではelmBootstrapelmTailwind CSSを組み合わせたサンプルアプリの構築方法を解説する。

環境構築(Bootstrap 編)

プロジェクトを作成

$ mkdir elm-bootstrap-sampleapp
$ cd elm-bootstrap-sampleapp
$ npm init -y

bootstrap をインストール(参考

$ npm i --save-dev parcel
$ npm i --save-dev bootstrap @popperjs/core

elm をインストールして環境を構築

$ npm i --save-dev elm @parcel/transformer-elm
$ npx elm init

必要なファイルを作成

$ touch src/index.html src/index.js src/index.scss src/Main.elm

src/index.htmlを書く。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link href="index.scss" rel="stylesheet" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="index.js"></script>
  </body>
</html>

src/index.jsを書く。 bootstrapelmjavascriptが共存している。

import * as bootstrap from "bootstrap";
import { Elm } from "./Main.elm";

Elm.Main.init({ node: document.getElementById("root") });

src/index.scssを書く。

@import "bootstrap/scss/bootstrap";

src/Main.elmを書く。

module Main exposing (main)

import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)


main =
    Browser.sandbox { init = init, update = update, view = view }


type alias Model =
    { score : Int
    }


init : Model
init =
    { score = 0
    }


type Msg
    = Select String


update : Msg -> Model -> Model
update msg model =
    case msg of
        Select _ ->
            { model | score = model.score + 1 }


view : Model -> Html Msg
view model =
    div []
        [ button
            [ class "btn btn-primary", onClick (Select "a") ]
            [ text (String.fromInt model.score) ]
        ]

package.jsonを書く。

{
  "name": "elm-bootstrap-sampleapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "parcel src/index.html",
    "build": "parcel build src/index.html --no-source-maps",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@parcel/transformer-elm": "^2.7.0",
    "@parcel/transformer-sass": "^2.7.0",
    "@popperjs/core": "^2.11.6",
    "bootstrap": "^5.2.2",
    "elm": "^0.19.1-5",
    "parcel": "^2.7.0"
  }
}

npm run startで開発出来る。 npm run buildで圧縮してビルドが出来る。

環境構築(Tailwind CSS 編)

プロジェクトを作成

$ mkdir elm-tailwindcss-sampleapp
$ cd elm-tailwindcss-sampleapp
$ npm init -y

elmparceltailwindcssをインストール

$ npm i -D elm parcel tailwindcss @parcel/transformer-elm

elmtailwindcssの環境を構築

$ npx elm init
$ npx tailwindcss init

必要なファイルを作成する。

$ touch .postcssrc src/index.html src/index.css src/index.js src/Main.elm

.postcssrcを作成してtailwindcssをコンパイル出来るようにする。

{
  "plugins": {
    "tailwindcss": {}
  }
}

tailwindcssが見るファイルにhtmljselmを設定する。

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{html,js,elm}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

package.jsonを書く。

{
  "name": "elm-tailwindcss-sampleapp",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "parcel src/index.html",
    "build": "parcel build src/index.html --no-source-maps",
    "clean": "rm dist/*",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@parcel/transformer-elm": "^2.7.0",
    "elm": "^0.19.1-5",
    "parcel": "^2.7.0",
    "tailwindcss": "^3.1.8"
  }
}

src/index.htmlを書く。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="index.css" rel="stylesheet">
</head>
<body>
  <div id="root"></div>
  <script type="module" src="index.js"></script>
</body>
</html>

src/index.cssを書く。

@tailwind base;
@tailwind components;
@tailwind utilities;

src/index.jsを書く。

import { Elm } from "./Main.elm";

Elm.Main.init({ node: document.getElementById("root") });

src/Main.elmを書く。

module Main exposing (main)

import Browser
import Html exposing (Html, button, div, text)
import Html.Attributes exposing (class)
import Html.Events exposing (onClick)


main =
    Browser.sandbox { init = init, update = update, view = view }


type alias Model =
    { score : Int
    }


init : Model
init =
    { score = 0
    }


type Msg
    = Select String


update : Msg -> Model -> Model
update msg model =
    case msg of
        Select _ ->
            { model | score = model.score + 1 }


view : Model -> Html Msg
view model =
    div []
        [ button
            [ class "rounded-full bg-gray-300 px-4 py-2", onClick (Select "a") ]
            [ text (String.fromInt model.score) ]
        ]

npm run startで開発出来る。 npm run buildで圧縮してビルドが出来る。

感想

  • Bootstrapjsに依存しているのでelmと組み合わせて使って良いものなのかよくわからない。とりあえずはうまく動いている。一方で pureCSS のTailwind CSSは css が書ける人用のフレームワークのため、css が得意でない私では使いこなせない代物だと思う。私は楽したいのでBootstrapが好みかなぁ。
  • Reacttsと状態管理フレームワークと組み合わせればelm以上に開発効率が良いのだろうなぁと思う。elmHaskellライクな言語が好きなのでなかなかそちらに行けないが。