MacでHaskellを使ってJavaScriptをビルドする環境(GHCJS)を最短で整える（長い）

# はじめに

先日、Elmを試したけどあまり合わなかった。
HTMLをプログラムで生成せずに昔ながらのやり方でファイルに書きたい。それでいて型指定も欲しい。
なのでGHCJSを試した。

GHCJSはElmのような新しい言語があるのではなく、Haskellのコンパイラ出力をJavaScriptに差し替えるというなかなか凄いやり方。Haskellの機能がほとんどそのまま使えて、更にDOMの機能も仕様の定義から自動生成されている。
自動生成なので [ByteLengthQueuingStrategy](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) のようなマイナー（？）なクラスにも対応するHaskellの関数があったりする。

つまり機能不足で困ることはほとんど無いと予想できる。
その代わりに提供されているのは素のJavaScriptのHaskell版というようなものなので、追加で [miso](https://github.com/dmjio/miso) のようなフレームワークを使った方が開発速度は速いと思う。

# Haskellの環境を整える

```bash
brew install haskell-stack
```
StackはHaskell版brewみたいなもの。すぐにインストールできる。

取りあえず最低限のプログラムを実行してみる。

```bash
stack new haskell-sample --resolver=nightly-2018-05-10 
```

`--resolver`無しの場合は最新版LTSのコンパイラがインストールされる。取りあえずここはあとで使うのでバージョン指定で。

```bash
cd haskell-sample
stack build

.stack-work/install/x86_64-osx/nightly-2018-05-10/8.4.2/bin/haskell-sample-exe
```
someFuncと出れば成功。
これでHaskellの環境は終了。


# Haskell版のコンパイル

続いてJavaScriptを書く環境を作る。

と言ってもサンプルプロジェクトをビルドするだけ。
https://github.com/nishimura/ghcjs-form-sample1.git
ここに置いた。

```bash
git clone https://github.com/nishimura/ghcjs-form-sample1.git
cd ghcjs-form-sample1
stack build
```

ghcjs-dom のコンパイルで固まったように見えるが落ち着いて待つ。メモリも大量に持って行かれるが、最初に書いたようにDOMで使う関数や型の大半をHaskell用に頑張って変換しているところなので。

ちなみに MacBook Pro で43分かかった。

コンパイルされたHaskell版プログラムを実行する。

```bash
.stack-work/install/x86_64-osx/nightly-2018-05-10/8.4.2/bin/ghcjs-form-sample1-exe
```
これでWebサーバーが立ち上がったので、 http://localhost:8000/ にアクセスして確認する。
これはHaskellで動いている状態。


# JavaScript版のコンパイル

次はJavaScriptで動かす。

最初に`~/.stack/config.yaml`にバージョン差異をスルーする設定を書く。

```bash
echo 'allow-newer: true' >> ~/.stack/config.yaml
```

それからGHCJSのコンパイルに使うツールをインストールする。

```bash
stack install alex
stack install happy

export PATH="$HOME/.local/bin:$PATH"
```

PATHはbashrc等にも追加する。

node.jsも無ければインストール。

```bash
brew install node
```



そしてビルド。

```bash
stack build --stack-yaml=js.yaml
```
GHCJS自体のコンパイルに38分かかった。それから続いてghcjs-bootでGHCJSで使うライブラリのコンパイルが始まり、追加で44分かかった。


この時点で `.stack-work/install/x86_64-osx/nightly-2018-05-10/ghcjs-8.4.0.1_ghc-8.4.2.20180505/bin/ghcjs-form-sample1-exe.jsexe/` にJavaScriptが生成されている。


```bash
open .stack-work/install/x86_64-osx/nightly-2018-05-10/ghcjs-8.4.0.1_ghc-8.4.2.20180505/bin/ghcjs-form-sample1-exe.jsexe/index.html
```

ブラウザで確認すると、コンソールに

```
id="area" not found
```

と出ていればOK。
htmlが自動生成されたものになっているので、まだ動かない。

今回利用するHTMLファイルは`html/index.html`にある。


# JavaScriptのコンパイル（圧縮）

Closure Compilerの起動に必要なjavaのjreをここから手に入る。
https://www.java.com/ja/download/
完全にブラウザ用としてインストールされるっぽいけども気にせずこれを使う。

```
ln -s /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java ~/bin/java
```

javaをPATHの通っているところに置いたあと、JavaScriptのコンパイルしてブラウザでアクセス。

```bash
./compile.sh

open html/index.html
```

これで終了。

# Haskellのプログラムの説明

`Main.hs`は`AppMain.hs`を起動するだけ。

本体は

```haskell
application :: HTMLDocument -> HTMLElement -> JSM ()
application doc area = do
    Just body <- getBody doc
    releaseClick <- on area G.click $ do
        (x, y) <- mouseClientXY
        newParagraph <- createElement doc "p"
        text <- createTextNode doc $ "Click " ++ show (x, y)
        _ <- appendChild newParagraph text
        _ <- appendChild body newParagraph
        return ()

    -- Make an exit button
    exitMVar <- liftIO newEmptyMVar
    exit <- createElement' doc TagSpan
    text <- createTextNode doc "Click here to exit"
    _ <- appendChild exit text
    _ <- appendChild body exit
    releaseExit <- on exit G.click $ liftIO $ putMVar exitMVar ()

    -- Force all all the lazy evaluation to be executed
    syncPoint

    -- In GHC compiled version the WebSocket connection will end when this
    -- thread ends.  So we will wait until the user clicks exit.
    _ <- liftIO $ takeMVar exitMVar
    releaseClick
    releaseExit
    setInnerHTML body "<h1>Ka kite ano (See you later)</h1>"
    return ()
```

こんな感じ。

https://github.com/ghcjs/jsaddle
ここのサンプルプログラムを写したんだけど、結構書き換えないと動かなかった。インターフェースをカジュアルに変えるのはHaskellあるあるっぽい。


`Helper.hs` は `Element` => `HTMLElement` 変換用。
型が厳密すぎて、[getElementById](https://developer.mozilla.org/ja/docs/Web/API/Document/getElementById) で取ってきた [Element](https://developer.mozilla.org/ja/docs/Web/API/Element) は[HTMLElement](https://developer.mozilla.org/ja/docs/Web/API/HTMLElement) ではないので [GlobalEventHandlers](https://developer.mozilla.org/ja/docs/Web/API/GlobalEventHandlers) のonclickイベントが付けられない。つらい。


# まとめ

コンパイル待ち時間を抜いたら15分ぐらい。
コンパイル待ち時間を入れたら2時間ぐらいでセットアップできた。
（実は長時間待った後の最後にエラーが出てやり直したりして5時間ぐらいかかっている）