ReScriptでとりあえずParcelでバンドル

# 準備
```SH:ReScriptのインストール
npm create rescript-app@latest
```
<details><summary>src/SousekiForm.res</summary>

```ReScript:src/SousekiForm.res
module Document = {
  @send external getElementById: (Dom.document, string) => Dom.element = "getElementById"
  @send external getElementsByName: (Dom.document, string) => Dom.nodeList = "getElementsByName"
  @get external getBody: Dom.document => Dom.element = "body"
}
module Element = {
  @send external addEventListener: (Dom.element, string, () => unit, bool) => unit = "addEventListener"
  @get external getDataset: Dom.element => Dom.domStringMap = "dataset"
  @get external getValue: Dom.element => string = "value"
  @get external getChecked: Dom.element => bool = "checked"
  @set external setInnerHTML: (Dom.element, string) => unit = "innerHTML"
}
module NodeList = {
  @get external getLength: Dom.nodeList => int = "length"
  @send external item: (Dom.nodeList, int) => Dom.element = "item"
}
module DomStringMap = {
  @get external getCorrectChoice: Dom.domStringMap => string = "correctChoice"
}

// 目的:設問部のinputのNodeをReScriptのlistに格納する
// getListedNodeList : Dom.nodeList -> Dom.element list
let getListedNodeList = nodeList => {
  let length = nodeList->NodeList.getLength

  // 目的: nodeList->item(0)からnodeList->item(i)までのDom.elementをReScriptのlistに格納する
  // loop : int -> Dom.element list
  let rec loop = i =>
    if i < 0 {list{}}
    else {list{nodeList->NodeList.item(i), ...loop(i - 1)}}

  loop(length - 1)
}

// 目的: 指定された選択肢のvalueを返す
// getAnswer : (Dom.element -> bool) -> Dom.element list -> string
let rec getAnswer = (lst, f) => switch lst {
| list{} => ""
| list{first, ...rest} => if f(first) {first->Element.getValue} else {getAnswer(rest, f)}
}

// 目的: ボタンがクリックされたときの処理
// onBtnClick : unit -> unit
let onBtnClick = () => {
  let listedNodeList = document->Document.getElementsByName("Q1")->getListedNodeList
  let correctAnswer = listedNodeList->getAnswer(e => e->Element.getDataset->DomStringMap.getCorrectChoice == "true")
  let selectedAnswer = listedNodeList->getAnswer(Element.getChecked)
  let judgement = correctAnswer == selectedAnswer ? "正解です！" : "不正解です。"
  let message = "正解は" ++ correctAnswer ++ "。あなたの回答は" ++ selectedAnswer ++ "でした。"
  document->Document.getBody->Element.setInnerHTML(judgement ++ "<br>" ++ message)
}

document->Document.getElementById("btn")->Element.addEventListener("click", onBtnClick, false)
```
</details>
<details><summary>src/souseki_form.html</summary>

```html:souseki_form.html
<!doctype HTML>
<html lang="ja">
<head>
  <title>漱石先生クイズ</title>
</head>
<body>
  <form>
    <dl>
      <dt>Q1. 漱石先生のデビュー作は？</dt>
      <dd><p>漱石先生のデビュー作は次のうちどれでしょう？</p>
        <p><input type="radio" name="Q1" value="坊っちゃん" checked>坊っちゃん<br>
          <input type="radio" name="Q1" value="吾輩は猫である" data-correct-choice="true">吾輩は猫である<br>
          <input type="radio" name="Q1" value="明暗">明暗</p>
      </dd>
    </dl>
    <button id="btn" type="button">答え合わせ</button>
  </form>
  <script type="module" src="SousekiForm.res.js"></script>
</body>
</html>
```
</details>

```:ディレクトリ構成
parcel-test/
├── rescript.json
├── package.json
└── src/
    ├── SousekiForm.res
    └── souseki_form.html
```
```SH:ビルド
npm run res:build
```

[Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)などのローカルのサーバで試すと、以下のWebページが表示されます。
<p class="codepen" data-height="300" data-slug-hash="MWdQVmq" data-pen-title="漱石先生クイズ #3" data-user="sgigagaeru" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sgigagaeru/pen/MWdQVmq">
  漱石先生クイズ #3</a> by sgigagaeru (<a href="https://codepen.io/sgigagaeru">@sgigagaeru</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 改修して動作確認
`SousekiForm.res`の`getAnswer`を再帰を使って書いてます。
```ReScript:修正前：SousekiForm.res
let rec getAnswer = (lst, f) => switch lst {
| list{} => ""
| list{first, ...rest} => if f(first) {first->Element.getValue} else {getAnswer(rest, f)}
}
```
`find`を使う方が良いかと思ったので修正します。

```ReScript:修正後:SousekiForm.res
let getAnswer = (lst, f) => switch List.find(lst, f) {
| None => ""
| Some(e) => e->Element.getValue
}
```
ローカルサーバにアクセスしてページの動作を確認すると、動作しません。
```console
Uncaught TypeError: Failed to resolve module specifier "@rescript/core/src/Core__List.res.js". Relative references must start with either "/", "./", or "../".
```
モジュールが見つけられないようです。

# `Parcel`でバンドル
バンドルして一つのファイルにしてしまえば良いのではと思ったので、`parcel`でバインドします。
以下のコマンドをプロジェクトのトップディレクトリ（`rescript.json`、`package.json`等が置かれているディレクトリ）で実行します。
```SH:Parcelのインストール
npm install --save-dev parcel
```
```SH:SousekiForm.resをエントリーポイントにしてバンドル
npx parcel build src/SousekiForm.res.js
```
`dist`ディレクトリが作成され、`SousekiForm.res.js`、`SousekiForm.res.js.map`が置かれました。
```:ディレクトリ構成
parcel-test/
├── rescript.json
├── package.json
├── src/
│   ├── SousekiForm.res
│   └── souseki_form.html
└── dist/
    ├── SousekiForm.res.js
    └── SousekiForm.res.js.map
```
# 確認
`dist`配下の`SousekiForm.res.js`を読み込むように`souseki_form.html`の`script`タグを修正します。
```html:souseki_form.html
<script type="module" src="../dist/SousekiForm.res.js"></script>
```
ブラウザで確認すると動きます。
バンドラが作成したJavaScriptファイルはモジュール形式ではないようなので、読み込みから`type="module"`を外してみます。
```html:souseki_form.html
<script src="../dist/SousekiForm.res.js"></script>
```
これでも動きました。[^1]
[^1]:`type="module"`があってもなくても動くのは不思議です。