JS API to compile ReScript to JS

I am playing around with trying to use ReScript in Svelte, and so I was wondering: is there was a JavaScript API which I can call, that takes ReScript and returns the compiled JS?

At its simplest form, something like:

let javascript = ReScript.compile(input);

Where input is a string with the ReScript to compile.

Such a JS API would be used in the Svelte pre-processor I’m setting up.

3 Likes

This is kinda complicated. We have a JS bundle of the rescript-compiler, but it doesn’t really feature all the functionality that ReScript brings. The most important one is the build system that links all interdependent modules and libraries, and you’d basically need to reimplement the whole build system logic in JS as well, which is probably really really complex. And then you need to figure out how to store the build artifacts (you probably will require file access to have some caching).

This PR is basically the current state of the playground that is currently in use on rescript-lang.org. I had to tweak a few bits to make this thing a “reentrant compiler”, which means that for each compile, the bundle resets its internal compiler state (the compiler itself is designed to be a one-shot process, not a long living JS instance).

1 Like

I haven’t used svelte yet, nor have I thought pretty hard about it but:
Svelte (the part you want to do the compilation) is running on node, right?
So you could just create a wrapper which actually calls bsb.
But it’s getting complicated pretty fast if you want to compile multiple interacting modules together.

Why not just compile res files to js first and use them in svelte?

After posting the question, and seeing the helpful responses (thanks @ryyppy, and @woeps) , I wondered if I was just making a rod for my own back trying to do this.

@woeps that’s right, it’s running on node.

The common method of writing components in Svelte is to have the code, markup, and CSS all in the one file. To support Rescript in Svelte, I’d need to compile res files to js using a custom Svelte preprocessor. Essentially, all that is is being able to take the Rescript in a Svelte component:

<script lang="res">
   // rescript here
</script>

The preprocessor receives anything in script tags with lang="res" (this could be anything, I just set the preprocessor to look for "res"), and converts it to Javascript:

let javascript = ReScript.compile(scriptTagResString);

1 Like

@dbarros Oh man, i was thinking about the same thing lately. I don’t write any svelte yet, but the reactivity pattern is incredible.

When I plan to work on something similar i was thinking about creating decorator for rescript, like right now we have @react.component. I would like to have something different for example @svelte.component (not sure if it is even possible though, but definitely will be fun to try implement it).

Anyway, your approach seems much more reasonable (putting ReScript to Svelte, not the other way around like i was planning to do).

Please let me know if you were able to do it, because honestly, having typesafe language for logic like rescript, and reactivity pattern from svelte, it will be endgame for me when it comes to writing UI’s.

4 Likes

@faliszek I agree that this would be a good combination. Basically I’d prefer something like Rescript over Javascript, and I have little interest in TypeScript.

3 Likes

@dbarros Right now I am doing a small project in Svelte and ReScript. What I am doing right now is using Svelte for Html/CSS and to subscribe to the Store whereas all the app logic, api request, Store and updating the store is written in ReScript.
I am not using any pre-processor at the moment. Whenever I save the ReScript file it gets converted to JS file which I import in Svelte.

Are you doing it the same way perhaps? or differently?

1 Like

@sabinbajracharya I’m sorry but I put this idea in to the background, and never came back to it.

Not sure if this example project (using ReScript with Svelte) will help, but thought I’d share it nonetheless.

1 Like

I’ve done this recently, but very hacky. so Svelte has the concept of pre-processors that take differently types of language like typescript, SCSS, Pug and turns it into JS, CSS and HTML in a preprocessing stage, so what’s roughly needed is way or documentation now how to turn Rescript code as string into JS as string, maybe a command that works with stdin and stdout. again below is very hacky but did work.

const rescriptPreprocess = () => {
	return {
		name: 'svelte-rescript',
		/**
		 * @param {object} options
		 * @param {string} options.content
		 * @param {string} options.filename
		 * @param {object} options.attributes
		 * @param {'res'} options.attributes.lang
		 */
		script: async ({ content, attributes }) => {
			if (attributes.lang == 'res') {

                console.info('content', content)

                const cmd = `./node_modules/.bin/bsc -bs-package-name svelte-rescript -bs-package-output esmodule:.:esmodule -o tmp -e "${content}"`;

				await promisify(exec)(cmd);

                const jsContent = readFileSync('tmpesmodule').toString();

				return {
					code: jsContent
				};
			}
		}
	};
};
1 Like

I’m thinking something like this would increase the reach of Rescript too, as it would make it easier for third parties to adopt it into the tooling(LSP, Build systems, etc …)

This looks extremely interesting, though I am curious how would it integrate with Svelte 5, which adds “runes” for creating signals

Don’t think I tried it with runes. I would assume it works. The preprocessing stage just turns the svelte component into something the svelte compiler would understand, I think there is support for pre-processing other JS based languages like CoffeeScript too.

I’ve tried adding this to Svelte like so:

import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { exec } from "node:child_process";
import { readFileSync } from "node:fs";
import { promisify } from "node:util";
import { defineConfig } from "vite";

const rescriptPreprocess = () => {
	return {
		name: 'svelte-rescript',
		/**
		 * @param {object} options
		 * @param {string} options.content
		 * @param {string} options.filename
		 * @param {object} options.attributes
		 * @param {'res'} options.attributes.lang
		 */
		script: async ({ content, attributes }) => {
			if (attributes.lang == 'res') {
        console.info('content', content)
        const cmd = `./node_modules/.bin/bsc -bs-package-name svelte-rescript -bs-package-output esmodule:.:esmodule -o tmp -e "${content}"`;

				await promisify(exec)(cmd);
        const jsContent = readFileSync('tmpesmodule').toString();

				return {
					code: jsContent
				};
			}
		}
	};
};

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    svelte({
      preprocess: [
        vitePreprocess(),
        rescriptPreprocess(),
      ]
    })
  ],
});

I have a module like this:

let a = 19

module Testing = {
  let b = a + 10
}

And then a svelte component like this

<script lang="res">
    let a = 44;
    let b = Test.Testing.b;
</script>

<h1>{a + b}</h1>

Then it fails, because it can’t see that module, so this might be one limitation of this method, or maybe I missed something.

9:16:31 AM [vite] Pre-transform error: Error while preprocessing /home/kamov/Desktop/svelte-test/src/App.svelte - Command failed: ./node_modules/.bin/bsc -bs-package-name svelte-rescript -bs-package-output esmodule:.:esmodule -o tmp -e "
    let a = 44;
    let b = Test.Testing.b;
"

  We've found a bug for you!
  /tmp/evalba84ce.res:3:13-26

  1 ┆
  2 ┆ let a = 44;
  3 ┆ let b = Test.Testing.b;
  4 ┆

  The module or file Test can't be found.
  - If it's a third-party dependency:
    - Did you add it to the "bs-dependencies" or "bs-dev-dependencies" in bsconfig.json?
  - Did you include the file's directory to the "sources" in bsconfig.json?
2 Likes

You’re close! You need to add each folder that can contain ReScript files to the bsc command via -I and pointing to the source folder, but in lib/bs. So if I have a folder called src/bindings in my project, I’d want to add this to bsc:

 -I /Users/zth/git/whatever/lib/bs/src/bindings

Then you need to do something similar for any library you add. These things are slightly complicated and mostly something the build system creates for you. IIRC @jfrolich you did something for outputting the relevant bsc command for a project with Rewatch, or am I misremembering that? Maybe that could be of help here.

2 Likes

@kamov I find this very interesting! Is your code available on GitHub?

I’ve uploaded the current code on GitHub, it’s just the Vite starter with Svelte added instead of React, feel free to take a look.

I haven’t been able to get the modules to work via -I yet, I’m not sure about this part especially, as I’m not really that familiar with how ReScript compiler works

I’ll try to take a look at it again later.

1 Like

I think this is a very interesting project, and I wonder what it’d take to make this experience nice. Looking forward to following what you come up with.

2 Likes