Anyone using vite-plugin-rescript to build a library?

I am working on an Electron project and I just recently started to try to introduce ReScript to the “main” process. I’m kinda working off of other vite-electron projects that are set up with TypeScript and trying to translate that into ReScript and I’m running into some issues:

If I set the build.lib.entry to something like src/Index.res, I get the following error:

npx vite build
[@jihchi/vite-plugin-rescript] Dependency on @ryyppy/rescript-promise
[@jihchi/vite-plugin-rescript] Dependency Finished
vite v2.8.6 building for production...
✓ 0 modules transformed.
[rollup-plugin-dynamic-import-variables] Unexpected token (1:5)

I’ve stripped the config down to the following minimal reproduction configuration:

import { defineConfig } from "vite";
import rescript from "@jihchi/vite-plugin-rescript";

export default defineConfig({
  plugins: [rescript()],
  build: {
    lib: {
      entry: "src/Index.res",
      formats: ["cjs"],
    },
  },
});

If I run just npx vite it successfully compiles the ReScript inline, but doesn’t try to bundle, which is what I’m guessing fails. Any idea of where to be looking? For now, I’m just using entry: "src/Index.bs.js" and manually running npx rescript build before launching vite, but I’m hoping I can get this set up to work in a way that I can run a watcher script that watches and builds both the “main” and “renderer” processes. Any help or examples would be appreciated!

I didn’t use it, but by looking at https://github.com/jihchi/vitejs-template-react-rescript I have couple ideas:

It would be helpful if you could provide a minimum reproducible repository to show the issues.

By reading the error message [..] [rollup-plugin-dynamic-import-variables] Unexpected token (1:5), it looks like there is something unrecognized by the plugin in the generated JavaScript files.

You probably need to tell ReScript to compile to es6 modules, if you haven’t already:

Yep! I’ve done that part.

In the Vite config, they reference bs.js files rather than .res

Right, but in the repo documentation it says explicitly that you can use res files which is really what I want: I want to be able to make changes to the ReScript source and have vite run the plugin which runs the compiler which then triggers a bundle to be built. That’s the part that I can find a little bit of documentation help, but no examples on GitHub to go off of. FWIW, it works fine to just point to the .bs.js files, but then I don’t even need the plugin since that requires me to run the ReScript compiler myself out-of-band.

:wave: @jihchi I bet you can help me here :slight_smile:

I tried to make a minimal repro, but got an even more basic error, but running rescript build directly definitely works to compile the inline JavaScript:

$ npm run build

> vite-react-rescript-starter@1.0.0 build
> vite build

[@jihchi/vite-plugin-rescript] Dependency Finished
vite v2.9.6 building for production...
✓ 0 modules transformed.
[@jihchi/vite-plugin-rescript] Could not load /Users/scotttrinh/projects/repro/src/Main.res: ENOENT: no such file or directory, open '/Users/scotttrinh/projects/repro/lib/es6/src/Main.bs.js'
error during build:
Error: Could not load /Users/scotttrinh/projects/repro/src/Main.res: ENOENT: no such file or directory, open '/Users/scotttrinh/projects/repro/lib/es6/src/Main.bs.js'
bash-5.1$ npx rescript build
bash-5.1$ 

Hey! I’m not using the plugin, but happy to share our set-up as comparison which just calls ReScript and then Vite.

Going to be a bit rude and just paste some code here since this isn’t in a public repo, sorry.

Package.json

We use nodemon for automatic reloading of changed server code. Vite builds a client bundle and a server bundle for SSR. The app also uses ReScript Relay though I’ve removed that from the examples here. It’s mostly and extra build and dev script. We use npm-run-all to run scripts serially or in parallel (that library is named npm but works with yarn too which we use).

Of note is type: module to make things work with mjs files.

{
  "name": "my-app",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "postinstall": "rescript clean",
    "build": "cross-env NODE_ENV=production run-s build:*",
    "build:rescript": "rescript build",
    "build:vite": "run-s build:vite:*",
    "build:vite:client": "vite build --outDir dist/client --ssrManifest --manifest",
    "build:vite:server": "vite build --outDir dist/server --ssr src/entry.server.mjs",
    "start": "cross-env NODE_ENV=production node -r dotenv-expand/config Server.mjs",
    "dev": "run-s build:rescript && run-p dev:*",
    "dev:rescript": "rescript build -w",
    "dev:vite": "nodemon -r dotenv-expand/config -w Server.mjs -w .env Server.mjs"
  },
  "dependencies": {
    "@rescript/react": "^0.10.3",
    "@ryyppy/rescript-promise": "^2.1.0",
    "cross-env": "^7.0.3",
    "dotenv": "^16.0.1",
    "dotenv-expand": "^8.0.3",
    "express": "^4.17.3",
    "node-fetch": "^3.2.4",
    "patch-package": "^6.4.7",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "rescript": "^9.1.4",
    "rescript-webapi": "^0.6.1"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^1.3.1",
    "binode": "^1.0.5",
    "nodemon": "^2.0.16",
    "npm-run-all": "^4.1.5",
    "vite": "^2.9.5"
  }
}

bsconfig.json

I think the bsconfig.json file is pretty standard. Do note we’re compiling to mjs. I’d have liked to compile to .bs.mjs to be able to differentiate between custom JS modules and ones that are generated by ReScript. Unfortunately the compiler has extensions as enum and not free-form string.

{
  "name": "my-app",
  "namespace": false,
  "reason": { "react-jsx": 3 },
  "refmt": 3,
  "bs-dependencies": [
    "@rescript/react",
    "rescript-webapi",
    "@ryyppy/rescript-promise"
  ],
  "ppx-flags": [],
  "sources": [
    { "dir": "src", "subdirs": true },
    { "dir": ".", "files": ["Server.res"] }
  ],
  "package-specs": {
    "module": "es6",
    "in-source": true
  },
  "suffix": ".mjs",
  "warnings": {
    "number": "-3",
    "error": "+101+8"
  }
}

vite.config.js

To make things work here I had to change the rollup and esbuild output to also be es6. I believe this was specifically needed to make Node.js happy. The client doesn’t care, but there was an unbundled file (Server.res) which was invoked by Node.js directly, compiled by ReScript, which would then try to import CJS files, which broke. So making everything ESM fixed that.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
  ],
  build: {
    target: 'esnext',
    rollupOptions: {
      output: {
        format: "esm"
      }
    }
  },
  // Prevent ReScript messages from being lost when we run all things at the same time.
  clearScreen: false,
})

Hope that helps anyone trying to get this working (perhaps you can spot the difference between this and a set-up using the ReScript plugin).

If we want to load .res directly with the jihchi/vite-plugin-rescript plugin, we’d need to enable “Loader” mode by providing loader configuration to the plugin, let’s take the minimal repro for example:

diff --git a/vite.config.ts b/vite.config.ts
index 828e251..26de68e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -2,7 +2,14 @@ import { defineConfig } from "vite";
 import createReScriptPlugin from "@jihchi/vite-plugin-rescript";
 
 export default defineConfig({
-  plugins: [createReScriptPlugin()],
+  plugins: [
+    createReScriptPlugin({
+      loader: {
+        output: "./",
+        suffix: ".bs.js",
+      },
+    }),
+  ],
   build: {
     lib: {
       entry: "src/Main.res",

Note:

  • loader.output is ./ because package-specs[0].in-source is true in bsconfig.json
  • loader.suffix is same as suffix in bsconfig.json.

Once you’ve configured Loader mode enabled, you should be able to build the package.

2 Likes

This absolutely helped and unlocked what I needed here, thanks for the details! This seems like a pretty desirable setting, so maybe worth adding some detail to the readme for newbies? I can open a PR if that sounds helpful.

It turns out for my specific issue, the actual problem seems to be using open with sub-modules so I’ll open a separate post to address that.

OK, this has been a wild ride, but I ended up finding this link to a totally unrelated vite plugin that described my original issue more directly and they ended up fixing it by… bumping npm. BUMPING NPM?! Wowzers. So, it took some doing, but I have it working now. It’s probably worth publishing an electron rescript vite starter kit because I’ve spent weeks trying to get this working and it’s now like actually working which is incredible. Thanks everyone for the suggestions and tips!

Yes, please, PR is always welcome!

Still working out the exact best way to set this up before attempting a documentation PR, but will definitely send something once I feel I have a good handle on this.