How to share Rescript Components between two Rescript Apps?

I have a Remix app and a Next.js app which have to share some Rescript components.
I would like to do take a monorepo approach.

monorepo/
├─ next-app/
│  ├─ components/
│  │  ├─ Component1.res (uses Shared.res)
│  ├─ node_modules/
│  ├─ bsconfig
│  ├─ package.json
├─ remix-app/
│  ├─ components/
│  │  ├─ Component2.res (uses Shared.res)
│  ├─ node_modules/
│  ├─ bsconfig
│  ├─ package.json
├─ shared/
│  ├─ Shared.res

I tried specifying bsconfig the relative path

"sources": [
    {
      "dir": "app/res",
      "subdirs": false
    },
    {
      "dir": "../shared",
      "subdirs": false
    }
  ],

but it breaks when building the app because it can’t resolve some dependencies

 ERROR: Could not resolve "rescript/lib/es6/curry.js"
ERROR: Could not resolve "rescript/lib/es6/belt_List.js"
// etc

Any ideas how to achieve this? I wonder if I make a package.json in /shared and install node_modules with just rescript.

I am not an expert in this; I was looking up the definition of “monorepo” today. But I tried to do something similar today - 2 React apps (client, admin) and a “shared” library. It seemed to work to have 3 separate folders each with its own package.json and bsconfig.json. Every package installs Rescript and whatever dependencies it needs and has its own node_modules folder. The way these projects talk to eachother is…

(1) install the “shared” library using the file: syntax, which creates a symbolic link in the node_modules folder. So my dependency in package.json looks something like…

"shared": "file:../shared"

(2) specifying shared in the bs-dependencies section of the client and admin packages bsconfig.json. Like this…

"bs-dependencies": ["shared"]

(3) might be optional, but I also turned on the namespace option in bsconfig.json.

"namespace": true,

Overall it seemed to work but I think I experienced some flakiness with the VS Code rescript extension. That message about “Do you want to start Rescript to get the latest build” popped up for each separate project.

2 Likes

You could make shared a separate standalone NPM package. That way you can put the shared package in pinned-dependencies and go from there:

{
  "bs-dependencies": ["shared"],
  "pinned-dependencies": ["shared"]
}

Building works when this approach is coupled with NPM workspaces (so that there is a single node_modules directory and the packages depend on each other correctly also on package.json level). Building might work with other monorepo systems, too, but just a warning: in my brief experience the centralized way pnpm stores node_modules is not compatible with ReScript.

Editor support for monorepos is currently not very good. The VSCode plugin is a bit more functional than the vim plugin.

1 Like

What do you mean by that? Does the VS Code plug in work for this situation? In my quick tests it seemed a little flaky but also seemed to work with those three bsconfig.json files. Are there known problems?

that’s good idea! thanks for pointing me to pinned-dependencies. This builds nicely on what @jmagaram tested out.

I am also looking into npm workspaces to complement this

I experienced assorted oddities that I assumed were caused by the monorepo setup because they were happening on npm module to npm module boundaries. I now realize it’s entirely possible these were not specific to monorepos and having similar constructs on just ReScript module to ReScript module level could’ve been the same!

My biggest problem with the vim plugin is that it is picky about the current working directory. See https://github.com/rescript-lang/vim-rescript/issues/45. It works only when cwd is the monorepo root. It is all too tempting to cd to a package directory to have a more focused view. :slight_smile:

  1. Create a package.json file in the shared directory and initialize it with npm init. This will allow you to install ReScript as a dependency for the shared components.
  2. Install ReScript as a dependency in the shared package. Run the following command in the shared directory:
npm install rescript
3. In the `bsconfig.json` file of both the Next.js app and the Remix app, add the shared directory as a source directory:

“sources”: [
{
“dir”: “app/res”,
“subdirs”: false
},
{
“dir”: “…/shared”,
“subdirs”: true
}
],


  1. In each app’s bsconfig.json, make sure to specify the correct root directory for the ReScript compiler:
"bsc-flags": ["-bs-super-errors"],
"ppx-flags": ["-ppx", "node_modules/rescript/bin/bs-ppx.exe"],
"sources": [
  {
    "dir": "src",
    "subdirs": true
  },
  {
    "dir": "../shared",
    "subdirs": true
  }
],
5. In your ReScript components, you can now import and use the shared components. For example, in `Component1.res` and `Component2.res`:

// In Component1.res
open Shared;

// In Component2.res
open Shared;


  1. Make sure to build both the Next.js app and the Remix app using their respective build commands. The ReScript compiler should be able to resolve the shared components correctly now.