# Contributing
Welcome to the ReScript compiler project!
This document will give you guidance on how to get up and running to work on the ReScript compiler and toolchain.
(If you want to contribute to the documentation website, check out [rescript-association/rescript-lang.org](https://github.com/reason-association/rescript-lang.org).)
We tried to keep the installation process as simple as possible. In case you are having issues or get stuck in the process, please let us know in the issue tracker.
Happy hacking!
## Setup
> Most of our contributors are working on Apple machines, so all our instructions are currently macOS / Linux centric. Contributions for Windows development welcome!
- [Node.js](https://nodejs.org/) v22.x or newer
- [Yarn CLI](https://yarnpkg.com/getting-started/install) (can be installed with `corepack`, Homebrew, etc)
- C compiler toolchain (usually installed with `xcode` on Mac)
- Rust toolchain (required to build rewatch; follow the instructions at https://www.rust-lang.org/tools/install and install the version listed as `rust-version` in `rewatch/Cargo.toml`)
- `opam` (OCaml Package Manager) v2.2.0 or newer
- VSCode (+ [OCaml Platform Extension](https://marketplace.visualstudio.com/items?itemName=ocamllabs.ocaml-platform))
## Cloning the Git Repo
The rescript-compiler git repo is very large because, prior to tooling improvements made for ReScript 11 development, build artifacts were checked in to the repo ("snapshotted"). Therefore, cloning the repo in full will consume a lot of bandwidth and disk space (> 2GB).
If you are only interested in the latest master commit, you can perform a shallow clone instead as follows:
```sh
git clone --depth 1 https://github.com/rescript-lang/rescript.git
```
This will only consume less than 50MB.
## Installation
### A. Manual installation
#### Install OCaml compiler + dependencies
The ReScript compiler compiles with any recent OCaml compiler. We are using `dune` as a build system for easy workflows and proper IDE support.
Make sure you have [opam](https://opam.ocaml.org/doc/Install.html) installed on your machine.
```sh
opam init
# Any recent OCaml version works as a development compiler
# Can also create local switch with opam switch create
# If you get "No compiler matching `5.3.0' found" error,
# then you need to run `opam update && opam upgrade` first
opam switch create 5.3.0
# Install dev dependencies from OPAM
opam install . --deps-only --with-test --with-dev-setup -y
```
> [!TIP]
> If you have
>
> ```sh
> $ git config --global core.fsmonitor
> true
> ```
>
> and get an error when running the `opam install` command
>
> ```sh
> #=== ERROR while compiling flow_parser.0.267.0 ================================#
> Copying sockets (rescript/_opam/.opam-switch/sources/flow_parser/.git/fsmonitor--daemon.ipc) is unsupported
> ```
>
> Run this:
>
> ```sh
> cd _opam/.opam-switch/sources/flow_parser \
> && git config core.fsmonitor false \
> && rm -f .git/fsmonitor--daemon.ipc
> ```
#### npm install
Run `yarn install`. This will install the npm dependencies required for the build scripts.
#### rustup install
[Rewatch](./rewatch/) is built with rust. Make sure you have [rustup](https://rustup.rs/) installed to manage rust versions.
```sh
rustup toolchain install 1.91
rustup override set 1.91
```
### B. Devcontainer
As an alternative to the manual installation, the repository provides a [development container](https://containers.dev/) definition that can be used with [VS Code's Remote Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). Use this to get a stable development environment without having to install anything locally other than VS Code and Docker.
Run the `Dev Containers: Rebuild and Reopen in Container` action to get started.
You can also open this dev container with [GitHub Codespaces](https://github.com/features/codespaces/).
## Building the Compiler
Main targets:
```sh
# Build the compiler and the build system (rewatch)
make
# Build the runtime/standard library
make lib
# Run the tests
make test
```
Additional targets:
```sh
# Build the compiler executables only
make compiler
# Build rewatch only
make rewatch
# Run syntax tests
make test-syntax
# Run syntax tests including roundtrip tests
make test-syntax-roundtrip
# Update artifact list
make artifacts
# Format code
make format
# Check formatting
make checkformat
```
## Coding Style
- OCaml Code: snake case format is used, e.g, `to_string`
- ReScript Code: the camel case format is used, e.g `toString`
## Adding new Files to the Npm Packages
To make sure that no files are added to or removed from the `rescript` or `@rescript/runtime` npm package inadvertently, an artifact list is kept at `packages/artifacts.json`. During CI build, it is verified that only the files that are listed there are actually included in the npm packages.
After adding a new file to the repository that should go into one of the npm packages - e.g., a new stdlib module -, run `make artifacts`.
## Test the compiler
### Single file
```sh
make lib # Build compiler and standard library
./cli/bsc.js myTestFile.res
```
To view the tokens of a file run:
```sh
dune exec res_parser -- -print tokens myTestFile.res
```
To view the untyped tree of the file run:
```sh
./cli/bsc.js -dparsetree myTestFile.res
```
or
```sh
dune exec res_parser -- -print ast -recover myTestFile.res
```
To view the typed tree of the file run:
```sh
./cli/bsc.js -dtypedtree myTestFile.res
```
### Project
```sh
make artifacts # Build compiler and standard library and populate lib/ocaml
npm link
cd myProject
npm install
npm link rescript
```
#### Use Local BSC with Existing ReScript Installation
Alternatively, you can set the `RESCRIPT_BSC_EXE` environment variable to point to your locally compiled `bsc.exe`.
```sh
RESCRIPT_BSC_EXE=your-rescript-repo/packages/@rescript/darwin-arm64/bin/bsc.exe npx rescript
```
This will test the local compiler while still using the build system from the installed Node module.
### Running Automatic Tests
We provide different test suites for different levels of the compiler and build system infrastructure. Always make sure to locally build your compiler before running any tests.
To run all tests:
```sh
make test
```
**Run Mocha tests only (for our runtime code):**
This will run our `mocha` unit test suite defined in `tests/tests`.
```
node scripts/test.js -mocha
```
**Run build system test (integration tests):**
This will run the build system test suite defined in `tests/build_tests`.
```
node scripts/test.js -build
```
**Run ounit tests:**
This will run unit tests for compiler related modules. The tests can be found in `tests/ounit_tests`.
```
node scripts/test.js -ounit
```
## Contributing to the Runtime
The runtime implementation is written in ReScript with some raw JS code embedded (`runtime` directory).
The goal is to implement the runtime **purely in ReScript**. This includes removing all existing occurrences of embedded raw JS code as well whenever possible, and you can help!
Each new PR should include appropriate testing.
Currently all tests are located in the `tests/tests` directory and you should either add / update test files according to your changes to the compiler.
There are currently two formats for test files:
1. Mocha test files that run javascript test code
2. Plain `.res` files to check the result of compilation to JS (expectation tests)
Below we will discuss on how to write, build and run these test files.
### 1) Write a Mocha Test File
- Create a file `tests/tests/src/feature_abc_test.res`. Make sure to end the file name with `_test.res`.
- Inside the file, add a mocha test suite. The mocha bindings are defined in `tests/tests/src/mt.res`. To get you started, here is a simple scaffold for a test suite with multiple test cases:
```rescript
open Mocha
open Test_utils
describe(__MODULE__, () => {
test("hey", () => {
ok(__LOC__, 3 > 2)
})
test("hi", () => {
eq(__LOC__, 2, 2)
eq(__LOC__, 3, 3)
})
test("throw", () => {
throws(__LOC__, () => throw(SomeException))
})
})
```
- Build the test files and run the tests: `node scripts/test.js -mocha`.
### 2) Write a Plain `.res` Test File
This is usually the file you want to create to test certain compile behavior without running the JS code formally as a test, i.e., when you just want to check that the ReScript code compiles and produces the expected JS code.
- Create your test file `tests/tests/src/my_file_test.res`. Make sure to end the file name with `_test.res`.
- Build the `.js` artifact: `node scripts/test.js -mocha`.
- Verify the output, check in the `tests/tests/src/my_file_test.res` and `tests/tests/src/my_file_test.js` to version control. The checked in `.js` file is essential for verifying regressions later on.
- Eventually check in other relevant files changed during the rebuild (depends on your compiler changes).
## Contribute to the ReScript Playground Bundle
The "Playground bundle" is a JS version of the ReScript compiler; including all necessary dependency files (stdlib / belt etc). It is useful for building tools where you want to compile and execute arbitrary ReScript code in the browser.
The ReScript source code is compiled with a tool called [JSOO (js_of_ocaml)](https://ocsigen.org/js_of_ocaml/latest/manual/overview), which uses OCaml bytecode to compile to JavaScript and is part of the bigger OCaml ecosystem.
### Building the Bundle
The entry point of the JSOO bundle is located in `compiler/jsoo/jsoo_playground_main.ml`, the compiler and its relevant runtime cmij files can be built via make:
```sh
make playground
make playground-cmijs
```
Note that building the cmijs is based on the dependencies defined in `packages/playground/package.json`. In case you want to build some different version of e.g. `@rescript/react` or just want to add a new package, change the definition within the `package.json` file and run `yarn workspace playground build` again.
After a successful compilation, you will find following files in your project:
- `playground/compiler.js` -> This is the ReScript compiler, which binds the ReScript API to the `window` object.
- `playground/packages/compiler-builtins` -> The compiler base cmij containing all the relevant core modules (`Js`, `Belt`, `Pervasives`, etc.)
- `playground/packages/*` -> Contains third party deps with cmij.js files (as defined in `packages/playground/rescript.json`)
You can now use the `compiler.js` file either directly by using a `` and `` inside a html file, use a browser bundler infrastructure to optimize it, or use `nodejs` to run it on a command line:
```
$ node
> let { rescript_compiler } = require("./compiler.js");
> require("./packages/compiler-builtins/cmij.js")
> let { rescript } = rescript_compiler.make()
> let result = rescript.compile(`Console.log(${rescript.version})`);
> eval(result.js_code);
```
### Testing the Playground bundle
Run `yarn workspace playground test` for a quick sanity check to see if all the build artifacts are working together correctly. When releasing the playground bundle, the test will always be executed before publishing to catch regressions.
You can test the bundle within the playground by running a web server and playground locally.
```sh
# Serve playground bundles locally
yarn workspace playground serve-bundle
# And run the website with "PLAYGROUND_BUNDLE_ENDPOINT"
cd path/to/rescript-lang.org
PLAYGROUND_BUNDLE_ENDPOINT=http://localhost:8888 npm run dev
```
### Working on the Playground JS API
Whenever you are modifying any files in the ReScript compiler, or in the `jsoo_playground_main.ml` file, you'll need to rebuild the source and recreate the JS bundle.
```
make playground
yarn workspace playground build
# optionally run your test / arbitrary node script to verify your changes
yarn workspace playground test
```
### Publishing the Playground Bundle on Cloudflare R2
Our `compiler.js` and third-party packages bundles are hosted on [Cloudflare R2](https://developers.cloudflare.com/r2/) and uploaded via Rclone.
The full release can be executed with the following make script:
```
make playground-release
```
The script will automatically detect the ReScript version from the `compiler.js` bundle and automatically create the correct directory structure on our CDN ftp server.
Note that there's currently still a manual step involved on [rescript-lang.org](https://rescript-lang.org) to make the uploaded playground version publicly available.
## Contribute to the API Reference
The API reference is generated from doc comments in the source code. [Here](https://github.com/rescript-lang/rescript/blob/57c696b1a38f53badaddcc082ed29188d80df70d/packages/%40rescript/runtime/Stdlib_String.resi#L441-L458)'s a good example.
Some tips:
- The first sentence or line should show the function call with a very short summary.
- Ideally, every function should have an `## Examples` section with **at least one** example. The examples are compiled to check that they are correct. Use `==` to generate tests from the examples.
- Always use `@deprecated` when applicable.
- Always use `@throw` when applicable.
- Always provide a `See` section pointing to MDN for more information when available.
## Contribute to JSX `domProps`
The `domProps` type, defined in [packages/@rescript/runtime/JsxDOM.res](packages/@rescript/runtime/JsxDOM.res), dictates which properties can be used on DOM elements. This list isn't exhaustive, so you might want to contribute a missing prop.
Adding a new entry there requires re-running the analysis tests. Follow these steps:
1. Compile your changes:
```bash
make lib
```
2. (Optional) If your local compiler is outdated, rebuild:
```bash
make build
```
3. Run the analysis tests. This will likely fail due to an outdated autocomplete test snapshot:
```bash
make test-analysis
```
4. Add the updated snapshot to Git:
```bash
git add tests/analysis_tests
```
5. Re-run the analysis tests. They should now pass:
```bash
make test-analysis
```
(If a `make` command fails, consider using the [DevContainer](#b-devcontainer).)
Finally, add a line to [CHANGELOG.md](CHANGELOG.md), using the `#### :nail_care: Polish` section.
## Code structure
The highlevel architecture is illustrated as below:
```
Source Language
|
| (Parser)
v
Surface Syntax Tree
|
| (Built-in Syntax tree transformation)
v
Surface Syntax Tree
|
| (Reuse OCaml Type checker)
v
Typedtree
|
| (Reuse OCaml pattern match compiler and erase types)
v
Lambda IR (OCaml compiler libs) ---+
| ^ |
| | Lambda Passes (lam_* files)
| | Optimization/inlining/dead code elimination
| \ |
| \ --------------------------+
|
| Self tail call elimination
| Constant folding + propagation
V
JS IR (J.ml) ---------------------+
| ^ |
| | JS Passes (js_* files)
| | Optimization/inlining/dead code elimination
| \ |
| \ -------------------------+
|
| Smart printer includes scope analysis
|
V
Javascript Code
```
Note that there is one design goal to keep in mind, never introduce any meaningless symbol unless necessary, we do optimizations, however, it should also compile readable output code.
## PR target branch
Target branch `master` for development of new (breaking) features (v12).
Bug fixes and maintenance should target branch `11.0_release`.
We'll merge `11.0_release` into `master` from time to time to propagate those changes.
## Release Process
To build a new version and release it on NPM, follow these steps:
1. Verify that the version number is already set correctly for the release. (It should have been incremented after releasing the previous version.)
1. Create a PR to update `CHANGELOG.md`, removing the "(Unreleased)" for the version to be released.
1. Once that PR is merged and built successfully, tag the commit with the version number (e.g., "v10.0.0", or "v10.0.0-beta.1") and push the tag.
1. This triggers a tag build that will upload the playground bundle to Cloudflare R2 and publish the `rescript` npm package with the tag "ci".
1. Verify that the playground bundle for the new version is now present on the settings tab in https://rescript-lang.org/try.
1. Run `npm info rescript` to verify that the new version is now present with tag "ci".
1. Test the new version.
1. Tag all packages for the new version as appropriate (`latest` or `next`): `./scripts/npmRelease.js --version --tag `
1. Create a release entry for the version tag on the [Github Releases page](https://github.com/rescript-lang/rescript-compiler/releases), copying the changes from `CHANGELOG.md`.
1. Create a PR with the following changes to prepare for development of the next version:
- Increment the `EXPECTED_VERSION` number in `yarn.config.cjs` for the next version.
- Run `yarn constraints --fix` to take that version number over into other files.
- Update `CHANGELOG.md` and add an entry for the next version, e.g., "10.0.0-beta.2 (Unreleased)"
## Debugging issues from CI builds
To reproduce issues, it can be helpful to the team to install a specific version of the compiler.
ReScript uses [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for continuous releases. Once tests are passed successfully, the bot comment is available.
Follow the instructions from the comment, which are like:
```bash
# Use NPM
npm i "https://pkg.pr.new/rescript-lang/rescript@${PR_NUMBER}"
# Use Yarn
yarn add "rescript@https://pkg.pr.new/rescript-lang/rescript@${PR_NUMBER}"
# Use pnpm
pnpm add "https://pkg.pr.new/rescript-lang/rescript@${PR_NUMBER}"
```
Then attempt to rebuild your project as you would normally.
## Contribution Licensing
Since ReScript is distributed under the terms of the [LGPL Version 3](LICENSE), contributions that you make are licensed under the same terms. In order for us to be able to accept your contributions, we will need explicit confirmation from you that you are able and willing to provide them under these terms, and the mechanism we use to do this is called a Developer's Certificate of Origin [DCO](DCO.md). This is very similar to the process used by the Linux(R) kernel, Samba, and many other major open source projects.
To participate under these terms, all that you must do is include a line like the following as the last line of the commit message for each commit in your contribution:
Signed-Off-By: Random J. Developer
You must use your real name (sorry, no pseudonyms, and no anonymous contributions).