rescript-lang / rescript-compiler

The compiler for ReScript.
https://rescript-lang.org
Other
6.65k stars 442 forks source link

RFC: conditional module FFI #6946

Open cometkim opened 1 month ago

cometkim commented 1 month ago

Background

Imagine a class statement written from external JS.

// rescript_error.mjs
export class RescriptError extends Error {
  constructor(exnId, payload) {
    super();
  }
}

// and in rescript_error.cjs
module.exports = {
  RescriptError,
};

This is a pretty common situation because ReScript doesn't support ES classes.

However, ReScript's current FFI approach cannot select the appropriate resolution by its syntax.

type t

@module("./rescript_error.mjs") @new external makeErrorEsm: unit => t = "RescriptError"
@module("./rescript_error.cjs") @new external makeErrorCjs: unit => t = "RescriptError"

// and it might need a runtime like UMD

This can be solved today using bundlers' module alias or Node.js' conditional exports/imports resolution, but these are all platform-dependent features.

{
  "imports": {
    "#error": {
      "import": "./rescript_error.mjs",
      "require": "./rescript_error.cjs"
    }
  }
}

The toolchain must be able to produce output that is appropriate for its target. That's true even for FFIs.

Suggested Solution

There are currently two module specs: esmoudle and commonjs (a composite of the two, dual, will be added in the future #6209).

Users can specify either of these two as target in the @module statement.

type t

@module({ target: "esmodule", from: "./rescript_error.mjs" })
@module({ target: "commonjs", from: "./rescript_error.cjs" })
@new external makeError: unit => t = "RescriptError"

The library may not support all conditions. The compiler should raise an error if it cannot find a target that matches a module in the project.

If target omitted, the * pattern is implicitly used.

@module("./rescript_error.ts")
// is equivalent
@module({ target: "*", from: "./rescript_error.ts" })
zth commented 1 month ago

Yes, that's a great catch and would be very useful. I'm all for it 👍

cknitt commented 1 month ago

This is great! I am not sure I like the name "cond" though.

zth commented 1 month ago

Maybe we should call it the same as we are calling it in rescript.json?

cometkim commented 1 month ago

Maybe we should call it the same as we are calling it in rescript.json?

That's "module" (the format), already duplicated with @module keyword. And If we use the "module" here, we should use something like module_ to avoid keyword collisions.

"cond" is inspired by Node.js' and bundlers' "conditions"

Some tools does something similar with "env" or "mode". It's basically a flag to customize full config.

Rust has "target", which is probably the most common way to express compilation conditions.

cometkim commented 1 month ago

The @module resolution must be selected for all conditions. If omitted, the * pattern is implicitly used.

This should be relaxed based on the actual project configuration. Then implementation is a bit more complex, but it helps to maintain forward compatibility with future targets.

cometkim commented 1 month ago

@zth @cknitt Does the last change make sense?

image
zth commented 1 month ago

Looks good to me!

DZakh commented 1 month ago

I think for the example with RescriptError what we actually need is a feature for @module to resolve relative path to the compiled ReScript output.

DZakh commented 1 month ago

But the feature is also very nice and will be useful in any way

cknitt commented 1 month ago

Or what about type instead of target or cond?

@module({ type: "esmodule", from: "./rescript_error.mjs" })
cometkim commented 1 month ago

Personally, I don't prefer to call the "targeted module format" config as just "type"

And it might be confusing with the standard "type" attribute.

@module({ type_: "esmodule", with: { type_: "json", from: "./file.json" } })
@val external content: Json.t = "default"
cknitt commented 1 month ago

Ok, and what about format?

@module({ format: "esmodule", from: "./rescript_error.mjs" })

?

cometkim commented 1 month ago

Ok, and what about format?

I think that it's good if its mental model is the format of the from source, not to tailor the compilation output.

DZakh commented 1 month ago

I like target

cknitt commented 1 month ago

Ok, seems people like target most. Fine with me too! 👍