概要

ReScript
とりあえず、ReScriptのみを扱うシンプルな新規プロジェクトを作成して、bs-jestでテストを実行できるようにしてみます。
この記事では、ReScriptの公式手順を使わずに、NPMの素のプロジェクトを作成して、必要最低限のライブラリを指定してインストールしていきます。

  • グローバルを汚さずににReScriptプロジェクトを作る方法
  • ReScriptでReactを動かすまでの設定方法
  • bs-jstを使ったテスト方法

の大枠が解ると思います。

ReScriptは、ReScriptのビルド設定など固有の設定をbsconfig.jsonの中に記述します。
その内容によって、ビルドした結果が大きく変わってきます。
Reactだけではなく、SvelteからReScriptを利用したりする可能性も考え、ReScriptをmjsファイルとしてビルドして、さらにユニットテストの実行もES6のまま実行するようにします。

なお、Nodeはv14を前提としています。

プロジェクト作成

まずは、とりあえずReScript組み込みのJSXを使って簡単にReactアプリケーションを作ってみます。
なお、全てのライブラリは今回作成されるsamples配下にインストールされるので、グローバル環境を汚すことはありません。

[koji:rescript]$ mkdir -p samples/src
[koji:rescript]$ mkdir -p samples/test
[koji:rescript]$ mkdir -p samples/public/script
[koji:rescript]$ mkdir -p samples/public/style
[koji:rescript]$ 
[koji:rescript]$ cd samples
[koji:samples]$ npm init

.envrc

自分はNode.jsの切り替えをenvrcで管理しています。
必要が有れば以下のような内容でプロジェクトディレクトリ内に.envrcというファイル名で保存しておいて下さい。

export PATH=$HOME/opt/node/node-v14.15.4-linux-x64/bin:$PATH

ライブラリのインストール

npm install --save-dev bs-platform parcel@next @glennsl/bs-jest jest
npm install --save @rescript/react react react-dom bs-fetch

余談ですが、bs-platformというライブラリは、近々rescriptに変更される予定のようです。分かりやすくなって良いですね。

必要なファイルの準備

ReScript/Reactに必要最低限なファイルを準備します。

bsconfig.json

プロジェクトルートにbsconfig.jsonを作成し、以下のような内容にする。

{
    "name": "samples",
    "version": "0.0.1",
    "sources": 
    [
      {
      "dir" : "test",
        "subdirs" : true,
        "type": "dev"
      },
      {
        "dir" : "src",
        "subdirs" : true
      }
    ]
    ,
    "package-specs": {
      "module": "es6",
      "in-source": false
    },
    "suffix": ".mjs",
    "bs-dependencies": [
      "@rescript/react",
      "bs-fetch"
    ],
    "ppx-flags": [],
    "reason": { "react-jsx": 3 },
    "bs-dev-dependencies": [
      "@glennsl/bs-jest"
    ],
    "warnings": {
      "error" : "+101"
    }
  }

package.json

既に存在していますので、中身を修正します。
scriptsの中身は最低限以下のようにします。

  "scripts": {
    "clean": "npx bsb -clean-world",
    "build": "npx bsb -clean-world -make-world  && npx parcel build public/index.html",
    "start": "npx parcel public/index.html",
    "re:build": "npx bsb -make-world",
    "re:watch": "npx bsb -clean-world -make-world -w",
    "re:test": "NODE_OPTIONS=--experimental-vm-modules npx jest"
  },

React

本命ですね。
とはいえ難しいことはありません。ReScriptはReactをとても良い感じに統合してくれています。

まず、src/Index.resを作成します。
このファイルがReScriptアプリケーションの本体になります。

switch (ReactDOM.querySelector("#app")) {
  | Some(app) => ReactDOM.render(<Application />, app)
  | None => ()
}

次に、ReScriptでReactコンポーネントを作成します。
src/Application.resと言うファイル名で、以下の内容を記述します。

@react.component
let make = () => {
  <div> <h1> {`こんにちわReScript!`->React.string} </h1> </div>
}

public/index.html

public/index.htmlを作成して、以下の内容にします。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <title>ReScript</title>
</head>
<body>
    <div id="app"></div>
    <script src="../lib/es6/src/Index.mjs"></script>
</body>
</html>

起動

この時点で、もしnpm run re:watchを実行していないのであれば、実行して常にReScriptファイルをトランスパイルさせるか、npm run re:buildを都度実行してReScriptファイルをJavaScriptにトランスパイルさせて下さい。

トランスパイル出来たら、npm startと実行すれば自動的に確認用サーバが起動して、ReScriptでReactアプリケーションが作成できたことが確認できます。
サーバはhttp://localhost:1234で起動します。

ユニットテスト(bs-jst)

jest.config.json

プロジェクトディレクトリ直下にjest.config.jsを作成して、以下のようにして下さい。

module.exports = {
  testMatch: ["**/test/**/*.?(m)js"],
  moduleFileExtensions: ["js", "jsx", "mjs"]
};

テストの作成

次に、test/SampleReScriptTest.resと言うファイルを作成して、実際に以下のようなテストを書いてみます。

open Jest
open Expect
let () = describe(`テストのサンプル`, () => {
  test(`最初のテスト`, () => {
    expect(1) |> toBe(1)
  })

  // 複数のexpectを一つのテストで実行したい場合は以下のようにtestAllを使って、
  // テスト対象になるデータを以下のようにlistで渡す。
  testAll(`複数テスト`, list{1, 2, 3}, v => {
    let value = switch v {
    | 1 => v * 2
    | 2 => v * 2
    | _ => v * 2
    }
    expect(value) |> toBe(v * 2)
  })
})

テストの実行

後は、package.jsonに指定したスクリプトを実行するだけです。

npm run re:testと実行すると以下のようにJestが実行されます。

[koji:samples]$ npm run re:test

> samples@1.0.0 re:test /home/koji/code/rescript/samples
> NODE_OPTIONS=--experimental-vm-modules npx jest

(node:13666) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
 PASS  lib/es6/test/SampleReScriptTest.mjs
  テストのサンプル
    ✓ 最初のテスト (2 ms)
    ✓ 複数テスト - 1
    ✓ 複数テスト - 2
    ✓ 複数テスト - 3 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        0.678 s
Ran all test suites.
[koji:samples]$ 

まとめ

これで、ReScriptのプロジェクトをゼロから作成してReactを動かし、ユニットテスト(bs-jst)を実行する方法がわかりました。
テストを実行するために、NODE_OPTIONS=--experimental-vm-modulesというnodeのまだ正式に採用されていない機能を使う点がネックですが、コレを使えば今後Svelte等の他のフロントエンドフレームワークなどでも簡単にReScriptを利用することが出来るようになります。

ReScriptは元々はFacebook発のReasonmlという別の名称だったものが2020年末にリブランディングされたものです。
Reasonml時代は、「OCamlをベースとしたフロントエンド用関数型プログラミング言語」という面を強調していた面があったと個人的には思っていますが、ReScriptになってからは、意図的にOCamlや関数型プログラミングという単語は使わずに、「フロントエンド開発者を楽にさせる」という面を押し出している印象です。

とはいえ、OCamlベースの最強の型システムがあり、文法もOCamlとTypeScriptのちょうど中間のような感じになっているので、OCamlとTypeScrpitユーザ双方ともに学びやすい言語では、と思います。

今後、SvelteとReScriptを連携させる方法なども別途書いていきますが、2021年内にはReScript自体のチュートリアルを用意したいなーと思っています。

おまけのgitignore

*.exe
*.obj
*.out
*.compile
*.native
*.byte
*.cmo
*.annot
*.cmi
*.cmx
*.cmt
*.cmti
*.cma
*.a
*.cmxa
*.obj
*~
*.annot
*.cmj
*.bak
lib/bs
lib/ocaml
*.mlast
*.mliast
.vscode
.merlin
.bsb.lock
/node_modules/
*.bs.js
/src/**/*.mjs
/test/**/*.mjs
.envrc
.cache/
dist/
yarn-error.log