Creating rescript libraries is hard (or what am I doing wrong?)

I don’t really get how to set things up, nothing seems obvious.

  1. what do I do when my lib uses modules internally that are not to be exposed? for example, I might have a library that has bindings for “chalk” because it does logging, but it should not “re-export” chalk. right now, anyone who uses the library cannot have a module named chalk in their code. how is this to be handled in real-world projects?

  2. is it ok to use RescriptCore in libraries? I’m getting duplicated package log messages all over.

  3. my code started doing REALLY weird stuff after dividing it into packages.

for example, Result.Map just stopped working:

...
Console.log(r) // logs { TAG: 'Ok', _0: [Function (anonymous)] }
let x = r->Result.map(x => {
  Console.log(x)
  1234
}) // doesnt log
Console.log(x) // logs { TAG: 'Ok', _0: [Function (anonymous)] }

looking inside Core__Result.mjs:

function map(opt, f) {
  if (opt.TAG === /* Ok */0) {
    return {
            TAG: /* Ok */0,
            _0: Curry._1(f, opt._0)
          };
  } else {
    return opt;
  }
}

so it’s not looking for “Ok”, it’s looking for 0. what?

this is rescript 11 rc8

I absolutely have no idea what’s going on now

  Console.log2("1", Ok(1)->Result.map(x => {
    Console.log2("2", x)
    2
  }))

output:

1 { TAG: 'Ok', _0: 1 }

For #1, you can use the public property in sources to specify which modules you want to expose:

1 Like

ok so I figured out what’s going on

rescript clean cleans the current project but also the libraries (maybe because using pnpm with link?)

but it diesn’t rebuild the library deps, so they were running curried mode and did not get rebuilt.

@hellos3b replied for #1, I also hit #2 issue and I’m pretty curious to know the answer to this too.

#4 are ppx’s not allowed in libraries?

imagine this setup:

libA - uses some_ppx

appB - uses libA

libA compiles fine. but I can’t compile appB because it can’t find the ppx. unsure if ppx’s are resolved incorrectly? installing some_ppx in appB fixes everything

re #2, it seems rescriptcore does not work properly in libraries when published. I have to pre-build it, and then it works BUT only if I pre-build it with the suffix identical to the client app, otherwise my libraris is unusable

just to keep convo going - I am still stuck on this. not only does it say duplicate package, but I just cannot get rescriptcore to build properly with libs.

if I have projects myLib (uses RescriptCore) and myApp (also uses RescriptCore, and also myLib) it just does not work.

first off, I have to set the suffix to .mjs. with .res.mjs, it does not run properly because rescriptcore does not build .res.mjs files or something. when I run myApp with suffix .mjs, it loads the curried (I think!) code instead of uncurried, or something along the lines of that. and everything fails at runtime.

the only way I can get it to work is to pre-build the libs, but I have to pre-build them with the same suffix as used in myApp rescript.json because otherwise it won’t work.

this is really frustrating and I absolutely despise the npm/js ecosystem which is such a horrible clusterfrack that it’s depressing. I had less problems with c++ and conan back i nthe day than this.

hmm, there must be something else wrong in your setup because all I have in the same situation is the “duplicate package warning”, could you maybe publish a minimal repro?

I just did here. have a look. simple case of lib a and app b, where a and b both depend on rescriptcore, and b also depends on a. ie:

 rescriptcore
  ^ ^
 /  |           
a   |
 ^  |
  \ |
    b

(beautiful ascii art, I know)

clone the repo, then:

cd a
npm i
cd ../b
npm i
rescript build
node src/Main.res.mjs

builds ok, but at startup:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../rescript-lib-fail/a/node_modules/@rescript/core/src/Core__Result.res.mjs' imported from .../rescript-lib-fail/a/src/Lib.res.mjs

it won’t even run.

EVEN WORSE:

change suffix to .mjs instead of .res.mjs in both projects, then do the above again. this time, it runs with the following output:

{ TAG: 'Ok', _0: 123 }

meaning it’s actually running, but Result.map is now broken:

let a = () => {
  Console.log(Ok(123)->Result.map(x => {
    Console.log(x)
    456
  }))
}

(it should output 123, and then 456)

this has to do with the file that previously couldn’t be found, can now be found. HOWEVER, it’s different in a and b, because rescriptcore gets built with b, but not with a:

this is Core__Result.mjs in a:

// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Curry from "rescript/lib/es6/curry.js";

...

function map(opt, f) {
  if (opt.TAG === /* Ok */0) {
    return {
            TAG: /* Ok */0,
            _0: Curry._1(f, opt._0)
          };
  } else {
    return opt;
  }
}

and this is Core__Result.mjs in b:

// Generated by ReScript, PLEASE EDIT WITH CARE

function map(opt, f) {
  if (opt.TAG === "Ok") {
    return {
            TAG: "Ok",
            _0: f(opt._0)
          };
  } else {
    return opt;
  }
}

as you can see, the one in b is different from the one in a (b has no load of Curry and also looks for TAG === “Ok” instead of TAG === 0)

because when I build b, Core__Result.mjs gets built with it, but the one that a uses does not get built when I build b.

this is a really serious bug I think, I would appreciate help in working around it.

if there isn’t a clean workaround, I have to rewrite my lib using Belt or something and hope that that works

I’ve spent like 4-5 hours debugging this but I honestly don’t know enough about the internals of rescript, I just noticed it’s got rescript 10 installed but in my real world project I have rescript11rc8 installed and it has the same issue

a few things look broken in your setup, you use rescript.json which only works with v11 but you defined v10.14 in the dependencies, so things just can’t work. Then, why not using a regular monorepo setup, here you have a setup that will likely just not work, whether it is rescript or not.

yeah the rescript 10 install was by mistake, i fixed it (I actually compiled with rescript 11 before though as that is the npm i -g version I have)

still the same thing.

as for monorepo, that’s not possible if a is a lib that is supposed to be published, no?

by the way, what makes you say it’s a setup that won’t work (apart from rescript v10 mistake)

I’m not a specialist of npm, but it seems that the local dependency doesn’t work as expected, and rescript wouldn’t detect when you make changes to the library, so a monorepo setup would seem like a good fit here. Why would a monorepo setup prevent the lib from being published?

imagine that a is supposed to be published and then npm-installed - I would have the same issue, no?

I don’t think so, I use libraries that depend on RescriptCore and all I have is a warning about duplicate dependencies, so I think your repro setup is likely broken since I can’t repro it when the library is published and consumed this way.

and I can’t repro it with a monorepo setup either.

imagine that I publish library a, and then someone npm installs it. how would they work around the issue?

well it would work but they would have the “duplicated package” warning, I’m not sure what should be done about this, maybe introducing the concept of peer dependencies for rescript, or maybe using package.json peer dependency is enough, it’d be easy to test.

what makes you say it would work? trying to understand what the actual problem is. are you saying the problem is that it’s a local npm install?