How to publish a rescript project for javascript users

Hello,

I want to share a react component written in rescript with an existing javascript project.
I’d like the user be able to run npm install my-rescript-component and use the component with import MyComponent from 'my-rescript-component.

So far, I got it working with this package.json:

{ 
  "files": ["bsconfig.json", "src"],
  "main": "./src/MyComponent.bs.js",
  "keywords": ["rescript"],
  "dependencies": { 
    "reason-react": "^0.9.1",
    "bs-platform": "^8.2.0"
  },
}

And at the end of src/MyComponent.re:

let default = make;

Without bs-platform in the depdencies, javascript project are not able to execute the require("bs-platform/lib...") from the bs.js file. Is there a better configuration to share rescript code with js?

3 Likes

“Without bs-platform in the depdencies” – what’s the major downside to have it as a dependency?

Could this be an issue if the user also has bs-platform in their devDependencies? And why not always put the bs-platform in the dependencies?

If using bs-platform, put it in dependencies, as it comes with runtime (JS) libraries.

1 Like

Good question.

I was under the impression that the generated js output would not be dependant on bs-platform. A bit surprising to hear about that hard dependency at runtime. Thought bs-platform was only for compile/build time.

1 Like

There is also the peerDependencies configuration, but npm doesn’t seems to install them. I’m not familiar with javascript or npm ecosystem, and it would be useful if the correct package.json configuration was detailed in https://rescript-lang.org/docs/manual/latest/project-structure#publishing .

Thanks in advance!

It’s possible to write ReScript code that doesn’t require any of the bs-platform JS stdlib, although that would be very difficult for any non-trivial project. bs-platform ships a lot of JS functions that are automatically included for basic operations.

Another option is to use a bundler. I believe this is what Wonka does, for example.

If you came across any materials which gave that impression, please file an issue as that is incorrect :slight_smile:

Hey! I’m currently trying to publish a rescript package for use for javascript users. However I have a similar issue.

I depend on rationale (https://github.com/jonlaing/rationale). The dependency doesn’t include any built js files when installing it with yarn, so you must run rescript build -with-deps, which puts the built js files inside the node_modules/rationale directory so that the js can be used.

Clearly this is a problem, as just putting rationale inside package.json would pull the npm package without any of the js in it.

Is this an issue with rationale? Should I be requesting that rationale includes built js files inside their npm distributions? Or should I run parcel over my build to therefore include the dependencies in one build file? Or is there something I’m missing?

It’s pretty rare to find JS files in ReScript projects published to NPM. IMO this is because the contents of the JS files depends on the bsconfig settings of the project using it as a dependency.

In this scenario I recommend bundling everything and publishing that. You could include the ReScript files as well so it can also be used by ReScript projects.

4 Likes

In my opinion, this is one of the things that would allow for ReScript to have a wider adoption.

ReScript has great tools for interoping JavaScript. The different output formats, genType and the relative ease of creating bindings are all super nice.

However, when creating a package to be consumed in a JS or TypeScript setting it is largely inconvenient that consumers — for any non-trivial libraries — need to install and configure the ReScript-toolchain (due to transitive dependencies not having JS artifacts published with the library).

We have a few services using ReScript (with React and GenType) at Wolt which are consumed in a TypeScript + React-application, and for us this has without a doubt been the one thing that has hindered ReScript from wider adoption.

The good news is obviously that ReScript has all the tools to make this possible and I imagine one solution would be to make it the convention and default to both publish the JS-artifacts (to solve the issue of transitive dependencies), and also follow the convention of specifying a package entry point and potential type definitions in the package manifest (for JS/TS consumption).

I do however think that the team needs to make it even more trivial to do so, and also to provide clear guidelines for the community on how to publish libraries.

ReScript is such a great language and I’d like to use it for any library I write regardless if it’s consumed in JS/TS or ReScript.

EDIT:

A couple of links to previous posts on this subject:

2 Likes

Js consumers of a package shouldn’t configure ReScript-toolchain. The approach of developing ReScript libraries for Js consumers is the same as for TypeScript libraries for Js consumers. Where instead of publishing source code, you need to bundle and build javascript for multiple targets.

Perhaps I was unclear, but yes, this was the point. In theory, this is true. In practice, however — because most ReScript-library authors do not publish their JS-artifacts — it’ll require JS-consumers to install and configure the ReScript-toolchain.

EDIT:

For clarity, here’s a minimal example.

My Index.bs.js uses a function from some third-party library that hasn’t published its JS artefacts. When my library is consumed this will throw an error for module not found.

// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

var Foo$SomeRescriptLibrary = require("some-rescript-library/lib/js/src/Foo.bs.js");
var foo = Foo$SomeRescriptLibrary.doFoo;

exports.foo = foo;

(I’d imagine that this would also apply if our bsconfig.json have different values for e.g. in-source or suffix.)

Regarding this:

(I’d imagine that this would also apply if our bsconfig.json have different values for e.g. in-source or suffix.)

For TS, which (as far as I know) doesn’t build from source, it’s rather straight-forward because they’d simply use whatever is specified in package.json, e.g.

"main": "dist/index.js",
"types": "dist/index.d.ts"

In ReScript’s case I imagine this information would also be available in a library’s bsconfig.json, e.g.

"package-specs": {
  "module": "commonjs",
  "in-source": false
},
"suffix": ".bs.js"

Which for it to work, I assume ReScript would need to respect this for every dependency when compiling, in the simple example:

// Generated by ReScript, PLEASE EDIT WITH CARE*
'use strict';

// in-source: false
var Foo$SomeRescriptLibrary = require("a/lib/js/src/Foo.bs.js"); 
// in-source: true
var Bar$SomeOtherRescriptLibrary = require("b/src/Bar.bs.js"); 

var foo = Foo$SomeRescriptLibrary.doFoo;
var bar = Bar$SomeOtherRescriptLibrary.doBar;

exports.foo = foo;
exports.bar = bar;

Although, I think ideally some other approach may be preferable, perhaps something similar to TS but specified in bsconfig

"compilerOptions": {
  "module": "commonjs",
  "outDir": "./dist"
  "suffix": "bs.js",
}

I think it might also be worth discussing if there may be some benefits of having e.g. an option not to compile from source (with some sort of compatibility check). I think it’d be especially useful when targeting consumers other than ReScript.