ElmをNext.jsの静的ページで動作させたメモ

## 概要

next.jsで作成した静的ページ上でElmが動作することを確認した。
備忘録として、elmを動作させようとしたときに追加したソースを残す。

[この時点のソース](https://github.com/hibohiboo/create-now/tree/0a3b604500924ce2339aceed2a26d59bb373317d/)
## 環境

* Windows 10 Home
* Vagrant 2.2.9
* virtualbox 6.1.14
* Ubuntu 20.04 LTS 
* Docker version 19.03.12, build 48a66213fe
* docker-compose version 1.26.0, build d4451659

## ソース

### elmのビルド環境の追加

```diff:package.json
  // 省略
  "scripts": {
    "dev": "next dev",
  // 省略
  "devDependencies": {
+    "elm": "^0.19.1-3",
+    "elm-format": "^0.8.3",
+    "elm-hot-webpack-loader": "^1.1.7",
+    "elm-minify": "^2.0.4",
+    "elm-test": "^0.19.1-revision2",
+    "elm-verify-examples": "^5.0.0",
+    "elm-webpack-loader": "^7.0.1",
  // 省略
  },
  "dependencies": {
    "next": "^9.5.3",
  // 省略
```

```diff:docker-compose.yml
version: "3.8"
services:
  zeit_now:
    # Dockerfileビルド
    build: ./vercel
    # ディレクトリを共有する。
    volumes:
      - ../src:/app/src
      - ../public:/app/public
      - ../fonts:/app/fonts
      - ../posts:/app/posts
      - ../data:/app/data
      - ../api:/app/api
      - ../.vercel:/app/.vercel
      - ../vercel.json:/app/vercel.json
      - ../next.config.js:/app/next.config.js
      - ../.vercelignore:/app/.vercelignore
      - ../tsconfig.json:/app/tsconfig.json
      - ../.eslintrc.js:/app/.eslintrc.js
      - ../.gitignore:/app/.gitignore
      - ../.babelrc.js:/app/.babelrc.js
      - ../.prettierrc.js:/app/.prettierrc.js
      - ../.env:/app/.env
      - ../.env.build:/app/.env.build
+      - ../elm.json:/app/elm.json
+      - cacheGardenElmStuffStarter:/app/elm-stuff
+      - cacheGardenDotElmStarter:/root/.elm
    ports:
      - 3000:3000
    environment:
      - CHOKIDAR_USEPOLLING=true # デフォルトの設定の場合、vagrantだとファイルの変更を検知できない。pollingが必要。
    working_dir: /app
    command: [npm, run, dev]
+ volumes:
+   # elmのpackageを毎回ダウンロードしなくてもよいように、キャッシュを行う。2か所のキャッシュが必要。
+   cacheGardenElmStuffStarter:
+   cacheGardenDotElmStarter:
```

```diff:next.config.js
/* eslint-disable @typescript-eslint/no-var-requires */
const { resolve } = require('path')
const withOffline = require('next-offline')
require('dotenv').config()
+ const MODE =
+   process.env.npm_lifecycle_event === 'dev' ? 'development' : 'production'

+ const withDebug = !process.env['npm_config_nodebug'] && MODE == 'development'
const nextConfig = {
  webpack: (config) => {
    // src ディレクトリをエイリアスのルートに設定
    config.resolve.alias['~'] = resolve(__dirname, 'src')
+     config.resolve.extensions.push('.elm')
+     if (MODE === 'development') {
+       config.module.rules.push({
+         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: withDebug,
+             },
+           },
+         ],
+       })
+     } else {
+       config.module.rules.push({
+         test: /\.elm$/,
+         exclude: [/elm-stuff/, /node_modules/],
+         use: {
+           loader: 'elm-webpack-loader',
+           options: {
+             optimize: true,
+           },
+         },
+      })
+     }

    return config
  },
  // manifest設定
  target: 'serverless',
  // 省略
```

```json:elm.json
{
    "type": "application",
    "source-directories": [
        "src/elm"
    ],
    "elm-version": "0.19.1",
    "dependencies": {
        "direct": {
            "elm/browser": "1.0.2",
            "elm/core": "1.0.5",
            "elm/html": "1.0.0",
            "elm/http": "2.0.0",
            "elm/json": "1.1.3"
        },
        "indirect": {
            "elm/bytes": "1.0.8",
            "elm/file": "1.0.5",
            "elm/time": "1.0.0",
            "elm/url": "1.0.0",
            "elm/virtual-dom": "1.0.2"
        }
    },
    "test-dependencies": {
        "direct": {},
        "indirect": {}
    }
}

```

### elm本体ファイルの追加


```elm:src/elm/Main.elm
module Main exposing (Model, Msg(..), init, main, update, view)

import Browser
import Html exposing (..)
import Html.Attributes exposing (class, href, placeholder, type_)



-- ---------------------------
-- MODEL
-- ---------------------------


type alias Model =
    {}


init : () -> ( Model, Cmd Msg )
init _ =
    ( {}, Cmd.none )



-- ---------------------------
-- UPDATE
-- ---------------------------


type Msg
    = NoOp


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



-- ---------------------------
-- MAIN
-- ---------------------------


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }


view : Model -> Html Msg
view model =
    div [] [ text "Hello World" ]
```

### elmを呼び出す静的ページ

```tsx
import { useEffect } from 'react'
import { GetStaticProps } from 'next'
import Head from 'next/head'

export default function Home(ctx: {}) {
    useEffect(() => {
      // eslint-disable-next-line @typescript-eslint/no-var-requires
      const { Elm } = require('~/elm/Main')
      const app = Elm.Main.init({ node: document.getElementById('elm-node') })
    }, [])
  return (
    <div>
      <Head>
        <title>タイトル</title>
      </Head>
      <div id="elm-node"></div>
    </div>
  )
}

export const getStaticProps: GetStaticProps = async () => {
  return {
    props: {},
  }
}
```




## 参考

[Elmでビュー(チャット画面)を作る](https://qiita.com/ababup1192/items/ed3be0b0d7b2d1b42a44)
[github.com simonh1000 elm-webpack-starter](https://github.com/simonh1000/elm-webpack-starter)
[webpackのメモ](https://qiita.com/hrloca/items/61ced4d16d6c257ada02)