ReScript & TypeScript
The ReScript compiler includes a code generation tool that lets you export ReScript values and types to use in TypeScript, and import TypeScript values and types into ReScript. It is called "genType".
The implementation of genType performs a type-directed transformation of ReScript programs after compilation. The transformed programs operate on data types idiomatic to TypeScript.
For example, a ReScript variant (which is represented as custom objects with tags at runtime):
RES@genType
type t = | A(int) | B(string)
is exported to a TypeScript type:
TStype t = { TAG: "A"; _0: number } | { TAG: "B"; _0: string };
A Quick Example
Let's assume we are working on a TypeScript codebase and we want to integrate a single ReScript function.
We want to be able to import the function like any other one in our existing TypeScript code, but we also want to preserve all the ReScript types in the TypeScript type system.
That's exactly what genType was made for!
First we'll set up a function:
RES// src/Color.res
@genType
type color =
| Red
| Blue
@genType
let printColorMessage = (~color, ~message) => {
let prefix = switch color {
| Red => "\x1b[91m"
| Blue => "\x1b[94m"
}
let reset = "\x1b[0m"
Console.log(prefix ++ message ++ reset)
}
On a successful compile, genType
will convert src/Color.res
to a TypeScript file called src/Color.gen.tsx
which will look something like this:
TS// src/Color.gen.tsx
/* TypeScript file generated from Color.res by genType. */
/* eslint-disable */
/* tslint:disable */
import * as ColorJS from "./Color.res.js";
export type color = "Red" | "Blue";
export const printColorMessage: (
color: color
) => void = ColorJS.printColorMessage as any;
genType automatically maps the color
variant to TS via a string union type "Red" | "Blue"
.
Within our TypeScript application, we can now import and use the function in the following manner:
TS// src/app.ts
import { printColorMessage } from "./Color.gen.tsx";
printColorMessage("Red", "Hello, genType!");
Exporting an entire module
Since ReScript 11.0.0
modules can be annotated with @genType
as well. In that case, all types and values of the module will be converted to TS types. Example:
Setup
Add a gentypeconfig
section to your rescript.json
(See Configuration for details).
Every genType
powered project requires a configuration item "gentypeconfig"
at top level in the project's rescript.json
.
The minimal configuration of genType is following:
JSON{
"gentypeconfig": {
"module": "esmodule",
"moduleResolution": "node",
"generatedFileExtension": ".gen.tsx"
}
}
And don't forget to make sure allowJs
is set to true
in the project's tsconfig.json
:
JSON{
"compilerOptions": {
"allowJs": true
}
}
TypeScript Module Resolutions
Make sure to set the same moduleResolution
value in both rescript.json
and tsconfig.json
, so that the output of genType is done with the preferred module resolution.
For example if the TypeScript project uses JavaScript modules with Node16
/ NodeNext
module resolution:
JSON// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node16"
}
}
Then moduleResolution
in gentypeconfig
should be same value:
JSON// rescript.json
{
"gentypeconfig": {
"moduleResolution": "node16"
}
}
In case of the TypeScript project using Bundler
module resolution, allowImportingTsExtensions
should also be true
:
JSON// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
}
}
JSON// rescript.json
{
"gentypeconfig": {
"moduleResolution": "bundler"
}
}
Testing the Whole Setup
Open any relevant *.res
file and add @genType
annotations to any bindings / values / functions to be used from JavaScript. If an annotated value uses a type, the type must be annotated too. See e.g. Hooks.res.
Save the file and rebuild the project via npm run res:build
or similar. You should now see a *.gen.tsx
file with the same name (e.g. MyComponent.res
-> MyComponent.gen.tsx
).
Any values exported from MyComponent.res
can then be imported from TypeScript. For example:
JSimport MyComponent from "./components/MyComponent.gen.tsx";
Experimental features
These features are for experimentation only. They could be changed/removed any time, and not be considered breaking changes.
Export object and record types as interfaces. To activate, add
"exportInterfaces": true
to the configuration. The types are also renamed fromname
toIname
.
Shims
A shim is a TS file that provides user-provided definitions for library types.
Required only if one needs to export certain basic ReScript data types to JS when one cannot modify the sources to add annotations (e.g. exporting ReScript lists), and if the types are not first-classed in genType.
Example:
Array<string>
with format:"RescriptModule=JavaScriptModule"
Configure your shim files within "gentypeconfig"
in your [rescript.json
]:
JSON{
"gentypeconfig": {
"shims": {
"Js": "Js",
"ReactEvent": "ReactEvent",
"RescriptPervasives": "RescriptPervasives",
"ReasonReact": "ReactShim"
},
},
}
and add relevant .shim.ts
files in a directory which is visible by ReScript e.g.
├── rescript.json ├── src │ ├── shims │ │ ├── Js.shim.ts │ │ ├── ReactEvent.shim.ts │ │ └── RescriptPervasives.shim.ts
Here are some examples:
TS// Excerpt from https://github.com/rescript-lang/rescript-compiler/blob/master/jscomp/gentype_tests/typescript-react-example/src/shims/Js.shim.ts
export type Json_t = unknown;
export type t = unknown;
TS// Excerpt from https://github.com/rescript-lang/rescript-compiler/tree/master/jscomp/gentype_tests/typescript-react-example/src/shims
export type inputFocusEvent = React.FocusEvent<HTMLInputElement>;
More complete example shims can be found here.
Deprecated features
Features related to generating runtimes were deprecated since v11 and should no longer be used.
@genType("alias")
and@genType.as("alias")
@genType.opaque
@genType.import
TypeScript Shims
genType does not generate anything runtime-related, and in the near future it generates definition files (*.d.ts
) directly (See the roadmap).
If any runtime code is required for interoperability with JavaScript / TypeScript projects, it can be written by hand, or request a relevant features (e.g. @deriving
) to the compiler.
Limitations
in-source = true. Currently only supports ReScript projects with in-source generation and file suffixes that end on
.js
, like.res.js
or.bs.js
.Limited namespace support. Currently there's limited namespace support, and only
namespace:true
is possible, not e.g.namespace:"custom"
.