# ReScript JavaScript Interop Focused documentation for binding to JavaScript values, modules, functions, objects, JSON, TypeScript, and other runtime interop patterns. # Interop Cheatsheet This is a glossary with examples. All the features are described by later pages. ## List of Decorators > **Note:** In ReScript < 8.3, all our attributes started with the `bs.` prefix. This is no longer needed and our formatter automatically removes them in newer ReScript versions. {/* Synced from https://github.com/rescript-lang/syntax/blob/123760c5a264da5288eeee5213ddd25eb86d62fe/src/res_printer.ml#L19-L51 */} ### Attributes - `@as`: [here](./variant.mdx#tagged-variants), [here](./attribute.mdx#usage), [here](./bind-to-js-function.mdx#fixed-arguments), [here](./bind-to-js-function.mdx#constrain-arguments-better) and [here](./generate-converters-accessors.mdx#usage-3) - [`@deriving`](./generate-converters-accessors.mdx#generate-functions--plain-values-for-variants) - [`@get`](./bind-to-js-object.mdx#bind-using-special-bs-getters--setters) - [`@get_index`](./bind-to-js-object.mdx#bind-using-special-bs-getters--setters) - [`@inline`](./inlining-constants.mdx) - [`@int`](./bind-to-js-function.mdx#constrain-arguments-better) - [`@module`](./import-from-export-to-js.mdx#import-a-javascript-modules-content) - [`@new`](./bind-to-js-object.mdx#bind-to-a-js-object-thats-a-class) - [`@optional`](./generate-converters-accessors.mdx#optional-labels) - [`@return`](./bind-to-js-function.mdx#function-nullable-return-value-wrapping) - `@send`: [here](./bind-to-js-function.mdx#object-method) and [here](./pipe.mdx#js-method-chaining) - [`@scope`](./bind-to-global-js-values.mdx#global-modules) - [`@set`](./bind-to-js-object.mdx#bind-using-special-bs-getters--setters) - [`@set_index`](./bind-to-js-object.mdx#bind-using-special-bs-getters--setters) - [`@variadic`](./bind-to-js-function.mdx#variadic-function-arguments) - [`@string`](./bind-to-js-function.mdx#constrain-arguments-better) - [`@this`](./bind-to-js-function.mdx#modeling-this-based-callbacks) - [`@uncurry`](./bind-to-js-function.mdx#extra-solution) - [`@unwrap`](./bind-to-js-function.mdx#trick-2-polymorphic-variant--bsunwrap) - [`@val`](./bind-to-global-js-values.mdx#global-modules) - [`@taggedTemplate`](./bind-to-js-function.mdx#tagged_template-functions) - [`@deprecated`](./attribute.mdx#usage) - [`genType`](https://github.com/reason-association/genType) - [`@JSX`](./jsx.mdx) - `@react.component`: [here](../react/introduction.mdx) and [here](https://github.com/rescript-lang/rescript-react) - [`@warning`](./attribute.mdx#usage) - [`@unboxed`](./variant.mdx#untagged-variants) ### Extension Points - [`%debugger`](./embed-raw-javascript.mdx#debugger) - [`%external`](./bind-to-global-js-values.mdx#special-global-values) - [`%raw`](./embed-raw-javascript.mdx#paste-raw-js-code) - [`%todo`](../../syntax-lookup/extension_todo.mdx) ## Raw JS ```res let add = %raw("(a, b) => a + b") %%raw("const a = 1") ``` ```js let add = (a, b) => a + b; const a = 1; export { add }; ``` ## Global Value ```res @val external setTimeout: (unit => unit, int) => float = "setTimeout" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ## Global Module's Value ```res @val @scope("Math") external random: unit => float = "random" let someNumber = random() @val @scope(("window", "location", "ancestorOrigins")) external length: int = "length" ``` ```js let someNumber = Math.random(); export { someNumber }; ``` ## Nullable ```res let a = Some(5) // compiles to 5 let b = None // compiles to undefined ``` ```js let a = 5; let b; export { a, b }; ``` Handling a value that can be `undefined` and `null`, by ditching the `option` type and using `Nullable.t`: ```res let jsNull = Nullable.null let jsUndefined = Nullable.undefined let result1: Nullable.t = Nullable.make("hello") let result2: Nullable.t = Nullable.fromOption(Some(10)) let result3: option = Nullable.toOption(Nullable.make(10)) ``` ```js import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js"; import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; let result2 = Stdlib_Nullable.fromOption(10); let jsNull = null; let jsUndefined; let result1 = "hello"; let result3 = Primitive_option.fromNullable(10); export { jsNull, jsUndefined, result1, result2, result3 }; ``` ## JS Object - [Bind to a JS object as a ReScript record](./bind-to-js-object.mdx#bind-to-record-like-js-objects). - [Bind to a JS object that acts like a hash map](./bind-to-js-object.mdx#bind-to-hash-map-like-js-object). - [Bind to a JS object that's a class](./bind-to-js-object.mdx#bind-to-a-js-object-thats-a-class). ## Function ### Object Method & Chaining ```res @send external map: (array<'a>, 'a => 'b) => array<'b> = "map" @send external filter: (array<'a>, 'a => 'b) => array<'b> = "filter" [1, 2, 3] ->map(a => a + 1) ->filter(a => mod(a, 2) == 0) ->Console.log ``` ```js console.log([1, 2, 3].map((a) => (a + 1) | 0).filter((a) => a % 2 === 0)); ``` ### Variadic Arguments ```res @module("path") @variadic external join: array => string = "join" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ### Tagged template functions ```res // see https://bun.sh/docs/runtime/shell type result = {exitCode: int} @module("bun") @taggedTemplate external sh: (array, array) => promise = "$" let filename = "index.res" let result = await sh`ls ${filename}` ``` ```js import * as $$Bun from "bun"; let filename = "index.res"; let result = await $$Bun.$`ls ${filename}`; export { filename, result }; ``` ### Polymorphic Function ```res @module("Drawing") external drawCat: unit => unit = "draw" @module("Drawing") external drawDog: (~giveName: string) => unit = "draw" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` ```res @val external padLeft: ( string, @unwrap [ | #Str(string) | #Int(int) ]) => string = "padLeft" padLeft("Hello World", #Int(4)) padLeft("Hello World", #Str("Message from ReScript: ")) ``` ```js padLeft("Hello World", 4); padLeft("Hello World", "Message from ReScript: "); ``` ## JS Module Interop [See here](./import-from-export-to-js.mdx) ## Dangerous Type Cast Final escape hatch converter. Do not abuse. ```res external convertToFloat: int => float = "%identity" let age = 10 let gpa = 2.1 + convertToFloat(age) ``` ```js let gpa = 2.1 + 10; let age = 10; export { age, gpa }; ``` # Embed Raw JavaScript ## Paste Raw JS Code First thing first. If you're ever stuck learning ReScript, remember that you can always just paste raw JavaScript code into our source file: ```res %%raw(` // look ma, regular JavaScript! var message = "hello"; function greet(m) { console.log(m) } `) ``` ```js // look ma, regular JavaScript! var message = "hello"; function greet(m) { console.log(m); } ``` The `%%raw` special ReScript call takes your code string and pastes it as-is into the output. **You've now technically written your first ReScript file!** (The backtick syntax is a multiline string. No escaping is needed inside the string.) While `%%raw` lets you embed top-level raw JS code, `%raw` lets you embed expression-level JS code: ```res let add = %raw(` function(a, b) { console.log("hello from raw JavaScript!"); return a + b } `) Console.log(add(1, 2)) ``` ```js let add = function (a, b) { console.log("hello from raw JavaScript!"); return a + b; }; console.log(add(1, 2)); export { add }; ``` The above code: - declared a ReScript variable `add`, - with the raw JavaScript value of a function declaration, - then called that function in ReScript. Existing JavaScript code can live inside ReScript files during migration. ## Debugger You can also drop a `%debugger` expression in a body: ```res let f = (x, y) => { %debugger x + y } ``` ```js function f(x, y) { debugger; return (x + y) | 0; } export { f }; ``` Output: ```js function f(x, y) { debugger; // JavaScript developer tools will set an breakpoint and stop here x + y; } ``` ## Tips & Tricks Embedding raw JS snippets isn't the best way to experience ReScript, though it's also highly useful if you're just starting out. As a matter of fact, the first few ReScript projects were converted through: - pasting raw JS snippets inside a file - examining the JS output (identical to the old hand-written JS) - gradually extract a few values and functions and making sure the output still looks OK At the end, we get a fully safe, converted ReScript file whose JS output is clean enough that we can confidently assert that no new bug has been introduced during the conversion process. See the [Converting from JS](./converting-from-js.mdx) guide for a detailed walkthrough. # Shared Data Types ReScript's built-in values of type `string`, `float`, `array` and a few others have a rather interesting property: they compile to the exact same value in JavaScript! This means that if you're passing e.g. a ReScript string to the JavaScript side, the JavaScript side can directly use it as a native string. It also means that you can import a JavaScript string and use it as a native ReScript string. ReScript values compile to their JavaScript equivalents directly, so **no data converters are needed for most types**. **Shared, bidirectionally usable types**: - String. ReScript strings are JavaScript strings, vice-versa. (Caveat: only our backtick string `` `hello 👋 ${personName}` `` supports unicode and interpolation). - Float. ReScript floats are JavaScript numbers, vice-versa. - Array. Use the [Array API](/docs/manual/api/stdlib/array) for array operations. - Tuple. Compiles to an array at runtime. You can treat a fixed-sized, heterogenous JavaScript array as a ReScript tuple too. - Boolean. - Record. Record compiles to a JavaScript object. Therefore you can also treat JavaScript objects as records. If they're too dynamic, consider modeling them on the ReScript side as a hashmap/dictionary [`Dict`](/docs/manual/api/stdlib/dict) or a ReScript object. - Object. ReScript objects are JavaScript objects, vice-versa. - Function. They compile to clean JavaScript functions. - Module. ReScript files are considered top-level modules, and are compiled to JavaScript files 1 to 1. Nested modules are compiled to JavaScript objects. - Polymorphic variants. - Unit. The `unit` type, which has a single value `()`, compiles to `undefined` too. Likewise, you can treat an incoming `undefined` as `()` if that's the only value it'll ever be. **Types that are slightly different, but that you can still use from JavaScript**: - Int. **Ints are 32-bits**! Be careful, you can potentially treat them as JavaScript numbers and vice-versa, but if the number's large, then you better treat JavaScript numbers as floats. For example, we bind to `Date` using `float`s. - Option. The `option` type's `None` value compiles into `undefined`. The `Some` value, e.g. `Some(5)`, compiles to `5`. Likewise, you can treat an incoming `undefined` as `None`. **`null` isn't handled here**. If your JavaScript value can be `null`, use [Nullable](/docs/manual/api/stdlib/nullable) helpers. - Exception. - Variant. Check the compiled JavaScript output of variant to see its shape. We don't recommend exporting a ReScript variant for pure JavaScript usage, since they're harder to read as plain JavaScript code, but you can do it. - List, which is just a regular variant. **Non-shared types (aka internal types)**: - Character. - Int64. - Lazy values. - Everything else. Many of these are stable, which means that you can still serialize/deserialize them as-is without manual conversions. But we discourage actively peeking into their structure otherwise. These types require manual conversions if you want to export them for JavaScript consumption. For a seamless JavaScript/TypeScript integration experience, check out the [TypeScript Integration](./typescript-integration.mdx) page instead of doing conversions by hand. # External (Bind to Any JS Library) `external` is the primary ReScript feature for bringing in and using JavaScript values. `external` is like a let binding, but: - The right side of `=` isn't a value; it's the name of the JS value you're referring to. - The type for the binding is mandatory, since we need to know what the type of that JS value is. - Can only exist at the top level of a file or module. ```res @val external setTimeout: (unit => unit, int) => float = "setTimeout" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` There are several kinds of `external`s, differentiated and/or augmented through the [attribute](./attribute.mdx) they carry. This page deals with the general, shared mechanism behind most `external`s. The different `external`s are documented in their respective pages later. A few notable ones: - `@val`, `@scope`: [bind to global JS values](./bind-to-global-js-values.mdx). - `@module`: [bind to JS imported/exported values](./import-from-export-to-js.mdx). - `@send`: [bind to JS methods](./bind-to-js-function.mdx). You can also use our [Syntax Lookup](../../syntax-lookup/) tool to find them. Related: See our [interop cheatsheet](./interop-cheatsheet.mdx) for an overview. ## Usage Once declared, you can use an `external` as a normal value, just like a let binding. ## Tips & Tricks `external` + ReScript objects are a wonderful combination for quick prototyping. Check the JS output tab: ```res // The type of document is just some random type 'a // that we won't bother to specify @val external document: 'a = "document" // call a method document["addEventListener"]("mouseup", _event => { Console.log("clicked!") }) // get a property let loc = document["location"] // set a property document["location"]["href"] = "rescript-lang.org" ``` ```js document.addEventListener("mouseup", (_event) => { console.log("clicked!"); }); let loc = document.location; document.location.href = "rescript-lang.org"; export { loc }; ``` We've specified `document`'s type as `'a`, a placeholder type that's polymorphic. Any value can be passed there, so you're not getting much type safety (except the inferences at various call sites). This is useful for quickly getting started with a JavaScript library in ReScript since you can write bindings directly for any API you need. For more rigidly typed bindings, see the other interop pages in this section. ## Performance & Output Readability `external`s declarations are inlined into their callers during compilation, **and completely disappear from the JS output**. This means any time you use one, you can be sure that you're not incurring extra JavaScript \<-> ReScript conversion cost. Additionally, no extra ReScript-specific runtime is better for output readability. > **Note:** do also use `external`s and the `@blabla` attributes in the interface files. Otherwise the inlining won't happen. ## Design Decisions ReScript takes interoperating with existing code very seriously. Our type system has very strong guarantees. However, such strong feature also means that, without a great interop system, it'd be very hard to gradually convert a codebase over to ReScript. Fortunately, our interop are comprehensive and cooperate very well with most existing JavaScript code. The combination of a sound type system + great interop means that we get the benefits of a traditional gradual type system regarding incremental codebase coverage & conversion, without the downside of such gradual type system: complex features to support existing patterns, slow analysis, diminishing return in terms of type coverage, etc. # Bind to JS Object JavaScript objects are a combination of several use-cases: - As a "record" or "struct" in other languages (like ReScript and C). - As a hash map. - As a class. - As a module to import/export. ReScript cleanly separates the binding methods for JS object based on these 4 use-cases. This page documents the first three. Binding to JS module objects is described in the [Import from/Export to JS](./import-from-export-to-js.mdx) section. {/* TODO: mention scope here too? */} ## Bind to Record-like JS Objects ### Bind Using ReScript Record If your JavaScript object has fixed fields, then it's conceptually like a ReScript record. Since a ReScript record compiles to a clean JavaScript object, you can definitely type a JS object as a ReScript record! ```res type person = { name: string, friends: array, age: int, } @module("MySchool") external john: person = "john" let johnName = john.name ``` ```js import * as MySchool from "MySchool"; let johnName = MySchool.john.name; export { johnName }; ``` External is documented [here](./external.mdx). `@module` is documented [here](./import-from-export-to-js.mdx). If you want or need to use different field names on the ReScript and the JavaScript side, you can use the `@as` decorator: ```res type action = { @as("type") type_: string } let action = {type_: "ADD_USER"} ``` ```js let action = { type: "ADD_USER", }; export { action }; ``` This is useful to map to JavaScript attribute names that cannot be expressed in ReScript (such as keywords). It is also possible to map a ReScript record to a JavaScript array by passing indices to the `@as` decorator: ```res type t = { @as("0") foo: int, @as("1") bar: string, } let value = {foo: 7, bar: "baz"} ``` ```js let value = [7, "baz"]; export { value }; ``` ### Bind Using ReScript Object Alternatively, you can use [ReScript object](./object.mdx) to model a JS object too: ```res type person = { "name": string, "friends": array, "age": int, } @module("MySchool") external john: person = "john" let johnName = john["name"] ``` ```js import * as MySchool from "MySchool"; let johnName = MySchool.john.name; export { johnName }; ``` ### Bind Using Special Getter and Setter Attributes Alternatively, you can use `get` and `set` to bind to individual fields of a JS object: ```res type textarea @set external setName: (textarea, string) => unit = "name" @get external getName: textarea => string = "name" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` You can also use `get_index` and `set_index` to access a dynamic property or an index: ```res type t @new external create: int => t = "Int32Array" @get_index external get: (t, int) => int = "" @set_index external set: (t, int, int) => unit = "" let i32arr = create(3) i32arr->set(0, 42) Console.log(i32arr->get(0)) ``` ```js let i32arr = new Int32Array(3); i32arr[0] = 42; console.log(i32arr[0]); export { i32arr }; ``` ## Bind to Hash Map-like JS Object If your JavaScript object: - might or might not add/remove keys - contains only values that are of the same type Then it's not really an object, it's a hash map. Use [Dict](/docs/manual/api/stdlib/dict), which contains operations like `get`, `set`, etc. and cleanly compiles to a JavaScript object still. ## Bind to a JS Object That's a Class Use `new` to emulate e.g. `new Date()`: ```res type t @new external createDate: unit => t = "Date" let date = createDate() ``` ```js let date = new Date(); export { date }; ``` You can chain `new` and `module` if the JS module you're importing is itself a class: ```res type t @new @module external book: unit => t = "Book" let myBook = book() ``` ```js import * as Book from "Book"; let myBook = new Book(); export { myBook }; ``` # Function Binding a JS function is like binding any other value: ```res // Import nodejs' path.dirname @module("path") external dirname: string => string = "dirname" let root = dirname("/User/github") // returns "User" ``` ```js import * as Path from "path"; let root = Path.dirname("/User/github"); export { root }; ``` We also expose a few special features, described below. ## Labeled Arguments ReScript has [function](./function.mdx) signature. These work on an `external` too! You'd use them to _fix_ a JS function's unclear usage. Assuming we're modeling this: ```js // MyGame.js function draw(x, y, border) { // suppose `border` is optional and defaults to false } draw(10, 20); draw(20, 20, true); ``` It'd be nice if on ReScript's side, we can bind & call `draw` while labeling things a bit: ```res @module("MyGame") external draw: (~x: int, ~y: int, ~border: bool=?) => unit = "draw" draw(~x=10, ~y=20, ~border=true) draw(~x=10, ~y=20) ``` ```js import * as MyGame from "MyGame"; MyGame.draw(10, 20, true); MyGame.draw(10, 20); ``` We've compiled to the same function, but now the usage is much clearer on the ReScript side thanks to labels! Note that you can freely reorder the labels on the ReScript side; they'll always correctly appear in their declaration order in the JavaScript output: ```res @module("MyGame") external draw: (~x: int, ~y: int, ~border: bool=?) => unit = "draw" draw(~x=10, ~y=20) draw(~y=20, ~x=10) ``` ```js import * as MyGame from "MyGame"; MyGame.draw(10, 20); MyGame.draw(10, 20); ``` ## Object Method Functions attached to JS objects (other than JS modules) require a special way of binding to them, using `send`: ```res type document // abstract type for a document object @send external getElementById: (document, string) => Dom.element = "getElementById" @val external doc: document = "document" let el = getElementById(doc, "myId") ``` ```js let el = document.getElementById("myId"); export { el }; ``` In a `send`, the object is always the first argument. Actual arguments of the method follow (this is a bit what modern OOP objects are really). ### Chaining Ever used `foo().bar().baz()` chaining ("fluent api") in JS OOP? We can model that in ReScript too, through the [pipe operator](./pipe.mdx). ### Nested function call `@send` can also accept a `@scope(("itemOne","itemTwo"))` to access a function on a nested property. ```res type stripe @module("stripe") @new external make: string => stripe = "default" type createSession = {} type sessionResult @send @scope(("checkout", "sessions")) external createCheckoutSession: (stripe, createSession) => Promise.t = "create" let stripe = make("sk\_...") let session = stripe->createCheckoutSession({}) ```` ```js import Stripe from "stripe"; let stripe = new Stripe("sk\_..."); let session = stripe.checkout.sessions.create({}); export { stripe, session, } ```` ## Variadic Function Arguments You might have JS functions that take an arbitrary amount of arguments. ReScript supports modeling those, under the condition that the arbitrary arguments part is homogenous (aka of the same type). If so, add `variadic` to your `external`. ```res @module("path") @variadic external join: array => string = "join" let v = join(["a", "b"]) ``` ```js import * as Path from "path"; let v = Path.join("a", "b"); export { v }; ``` `module` will be explained in [Import from/Export to JS](./import-from-export-to-js.mdx). ## Modeling Polymorphic Function Apart from the above special-case, JS functions in general are often arbitrarily overloaded in terms of argument types and number. How would you bind to those? ### Trick 1: Multiple `external`s If you can exhaustively enumerate the many forms an overloaded JS function can take, simply bind to each differently: ```res @module("MyGame") external drawCat: unit => unit = "draw" @module("MyGame") external drawDog: (~giveName: string) => unit = "draw" @module("MyGame") external draw: (string, ~useRandomAnimal: bool) => unit = "draw" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` Note how all three externals bind to the same JS function, `draw`. ### Trick 2: Polymorphic Variant + `unwrap` If you have the irresistible urge of saying "if only this JS function argument was a variant instead of informally being either `string` or `int`", then good news: we do provide such `external` features through annotating a parameter as a polymorphic variant! Assuming you have the following JS function you'd like to bind to: ```js function padLeft(value, padding) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } ``` Here, `padding` is really conceptually a variant. Let's model it as such. ```res @val external padLeft: ( string, @unwrap [ | #Str(string) | #Int(int) ]) => string = "padLeft" padLeft("Hello World", #Int(4)) padLeft("Hello World", #Str("Message from ReScript: ")) ``` ```js padLeft("Hello World", 4); padLeft("Hello World", "Message from ReScript: "); ``` Obviously, the JS side couldn't have an argument that's a polymorphic variant! But here, we're just piggy backing on poly variants' type checking and syntax. The secret is the `@unwrap` annotation on the type. It strips the variant constructors and compile to just the payload's value. See the output. ## Constrain Arguments Better Consider the Node `fs.readFileSync`'s second argument. It can take a string, but really only a defined set: `"ascii"`, `"utf8"`, etc. You can still bind it as a string, but we can use poly variants + `string` to ensure that our usage's more correct: ```res @module("fs") external readFileSync: ( ~name: string, @string [ | #utf8 | @as("ascii") #useAscii ], ) => string = "readFileSync" readFileSync(~name="xx.txt", #useAscii) ``` ```js import * as Fs from "fs"; Fs.readFileSync("xx.txt", "ascii"); ``` - Attaching `@string` to the whole poly variant type makes its constructor compile to a string of the same name. - Attaching a `@as("bla")` to a constructor lets you customize the final string. And now, passing something like `"myOwnUnicode"` or other variant constructor names to `readFileSync` would correctly error. Aside from string, you can also compile an argument to an int, using `int` instead of `string` in a similar way: ```res @val external testIntType: ( @int [ | #onClosed | @as(20) #onOpen | #inBinary ]) => int = "testIntType" testIntType(#inBinary) ``` ```js testIntType(21); ``` `onClosed` compiles to `0`, `onOpen` to `20` and `inBinary` to **`21`**. ## Unknown for type safety It is best practice to inspect data received from untrusted external functions to ensure it contains what you expect. This helps avoid run-time crashes and unexpected behavior. If you're certain about what an external function returns, simply assert the return value as `string` or `array` or whatever you want it to be. Otherwise use `unknown`. The ReScript type system will prevent you from using an `unknown` until you first inspect it and "convert" it using JSON parsing utilities or similar tools. Consider the example below of two external functions that access the value of a property on a JavaScript object. `getPropertyUnsafe` returns an `'a`, which means "anything you want it to be." ReScript allows you to use this value as a `string` or `array` or any other type. Quite convenient! But if the property is missing or contains something unexpected, your code might break. You can make the binding more safe by changing `'a` to `string` or `option<'a>`, but this doesn't completely eliminate the problem. The `getPropertySafe` function returns an `unknown`, which could be `null` or a `string` or anything else. But ReScript prevents you from using this value inappropriately until it has been safely parsed. ```res @get_index external getPropertyUnsafe: ({..}, string) => 'a = "" @get_index external getPropertySafe: ({..}, string) => unknown = "" let person = {"name": "Bob", "age": 12} let greeting1 = "Hello, " ++ getPropertyUnsafe(person, "name") // works (this time!) // let greeting2 = "Hello, " ++ getPropertySafe(person, "name") // syntax error ``` ## Special-case: Event Listeners One last trick with polymorphic variants: ```res type readline @send external on: ( readline, @string [ | #close(unit => unit) | #line(string => unit) ] ) => readline = "on" let register = rl => rl ->on(#close(event => ())) ->on(#line(line => Console.log(line))); ``` ```js function register(rl) { return rl .on("close", (event) => {}) .on("line", (line) => { console.log(line); }); } export { register }; ``` {/* TODO: GADT phantom type */} ## Fixed Arguments Sometimes it's convenient to bind to a function using an `external`, while passing predetermined argument values to the JS function: ```res @val external processOnExit: ( @as("exit") _, int => unit ) => unit = "process.on" processOnExit(exitCode => Console.log("error code: " ++ Int.toString(exitCode)) ); ``` ```js process.on("exit", (exitCode) => { console.log("error code: " + exitCode.toString()); }); ``` The `@as("exit")` and the placeholder `_` argument together indicates that you want the first argument to compile to the string `"exit"`. You can also use any JSON literal with `as`: ``@as(json`true`)``, ``@as(json`{"name": "John"}`)``, etc. ## Ignore arguments You can also explicitly "hide" `external` function parameters in the JS output, which may be useful if you want to add type constraints to other parameters without impacting the JS side: ```res @val external doSomething: (@ignore 'a, 'a) => unit = "doSomething" doSomething("this only shows up in ReScript code", "test") ``` ```js doSomething("test"); ``` **Note:** It's a pretty niche feature, mostly used to map to polymorphic JS APIs. ## Modeling `this`-based Callbacks Many JS libraries have callbacks which rely on this (the source), for example: ```js x.onload = function (v) { console.log(this.response + v); }; ``` Here, `this` would point to `x` (actually, it depends on how `onload` is called, but we digress). It's not correct to declare `x.onload` of type `(. unit) -> unit`. Instead, we introduced a special attribute, `this`, which allows us to type `x` as so: ```res type x @val external x: x = "x" @set external setOnload: (x, @this ((x, int) => unit)) => unit = "onload" @get external resp: x => int = "response" setOnload(x, @this (o, v) => Console.log(resp(o) + v)) ``` ```js x.onload = function (v) { let o = this; console.log((o.response + v) | 0); }; ``` `@this` reserves the first parameter for the `this` value, and for arity of 0, there is no need for a redundant `unit` type. ## Function Nullable Return Value Wrapping For JS functions that return a value that can also be `undefined` or `null`, we provide `@return(...)`. To automatically convert that value to an `option` type (recall that ReScript `option` type's `None` value only compiles to `undefined` and not `null`). ```res type element type dom @send @return(nullable) external getElementById: (dom, string) => option = "getElementById" let test = dom => { let elem = dom->(getElementById("haha")) switch (elem) { | None => 1 | Some(_ui) => 2 } } ``` ```js function test(dom) { let elem = dom.getElementById("haha"); if (elem == null) { return 1; } else { return 2; } } export { test }; ``` `return(nullable)` attribute will automatically convert `null` and `undefined` to `option` type. Currently 4 directives are supported: `null_to_opt`, `undefined_to_opt`, `nullable` and `identity`. {/* When the return type is unit: the compiler will append its return value with an OCaml unit literal to make sure it does return unit. Its main purpose is to make the user consume FFI in idiomatic OCaml code, the cost is very very small and the compiler will do smart optimizations to remove it when the returned value is not used (mostly likely). */} `identity` will make sure that compiler will do nothing about the returned value. It is rarely used, but introduced here for debugging purpose. ## Tagged template functions **Since 11.1** **Experimental** You can easily bind to [JS tagged template functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). Tag functions in JS expect as input an array of strings and variadic parameters for the arguments of the interpolation. To bind to those functions in ReScript, the binding signature must have two arrays as arguments, the first one being an array of strings and the second can be an array of anything. You add the `@taggedTemplate` annotation and you're good to go! ```res // see https://bun.sh/docs/runtime/shell type result = {exitCode: int} @module("bun") @taggedTemplate external sh: (array, array) => promise = "$" let filename = "index.res" let result = await sh`ls ${filename}` ``` ```js import * as $$Bun from "bun"; let filename = "index.res"; let result = await $$Bun.$`ls ${filename}`; export { filename, result }; ``` Notice that it gets compiled to tagged template literals in JS, which allows to use JS tools that only work on the literals and not by calling directly the tag function. There are plenty of useful JS tools you can bind to, like [`gql`](https://github.com/apollographql/graphql-tag), [`sql`](https://github.com/porsager/postgres), [`css`](https://github.com/mayank99/ecsstatic) and a lot others! # Import from/Export to JS You've seen how ReScript's idiomatic [Import & Export](./import-export.mdx) works. This section describes how we work with importing stuff from JavaScript and exporting stuff for JavaScript consumption. If you're looking for react-specific interop guidance, check out the [React JS Interop guide](../react/import-export-reactjs.mdx). **Tip**: keep your compiled JS output open in a tab to verify the generated import/export code. In short: **make sure your bindings below output what you'd have manually written in JS**. ## Output Format We support 2 JavaScript import/export formats: - JavaScript module: `import * from 'MyReScriptFile'` and `export let ...`. - CommonJS: `require('myFile')` and `module.exports = ...`. The format is [configurable in via `rescript.json`](./build-configuration.mdx#package-specs). ## Import From JavaScript ### Import a JavaScript Module's Named Export Use the `module` [external](./external.mdx): ```res // Import nodejs' path.dirname @module("path") external dirname: string => string = "dirname" let root = dirname("/User/github") // returns "User" ``` ```js import * as Path from "path"; let root = Path.dirname("/User/github"); export { root }; ``` ```js var Path = require("path"); var root = Path.dirname("/User/github"); exports.root = root; ``` Here's what the `external` does: - `@module("path")`: pass the name of the JS module; in this case, `"path"`. The string can be anything: `"./src/myJsFile"`, `"@myNpmNamespace/myLib"`, etc. - `external`: the general keyword for declaring a value that exists on the JS side. - `dirname`: the binding name you'll use on the ReScript side. - `string => string`: the type signature of `dirname`. Mandatory for `external`s. - `= "dirname"`: the name of the variable inside the `path` JS module. There's repetition in writing the first and second `dirname`, because sometime the binding name you want to use on the ReScript side is different than the variable name the JS module exported. ### Import a JavaScript Module As a Single Value By omitting the string argument to `module`, you bind to the whole JS module: ```res @module external leftPad: (string, int) => string = "./leftPad" let paddedResult = leftPad("hi", 5) ``` ```js import * as LeftPad from "./leftPad"; function leftPad(prim0, prim1) { return LeftPad(prim0, prim1); } let paddedResult = LeftPad("hi", 5); export { leftPad, paddedResult }; ``` ```js var LeftPad = require("./leftPad"); var paddedResult = LeftPad("hi", 5); ``` Depending on whether you're compiling ReScript to JavaScript module or CommonJS, **this feature will generate subtly different code**. Please check both output tabs to see the difference. The JavaScript module output here would be wrong! ### Import an `default` Export Use the value `default` on the right hand side: ```res @module("./student") external studentName: string = "default" Console.log(studentName) ``` ```js import Student from "./student"; let studentName = Student; console.log(studentName); export { studentName }; ``` ### Use Import Attributes **Since 11.1** [Import attributes](https://github.com/tc39/proposal-import-attributes) can be used in ReScript, as long as ReScript is configured to output JavaScript module. You do that by passing configuration to the `@module` attribute: ```rescript @module({from: "./myJson.json", with: {type_: "json", \"some-exotic-identifier": "someValue"}}) external myJson: JSON.t = "default" Console.log(myJson) ```` ```javascript import MyJsonJson from "./myJson.json" with {"type": "json", "some-exotic-identifier": "someValue"}; var myJson = MyJsonJson; console.log(myJson); ```` This above imports the local `./myJson.json` file, adding import attributes. This is how it works: 1. Instead of passing a string or tuple to `@module`, pass a record. 2. This record should have a `from` key. The value of that is where you want the module to be imported from (just like the regular string to `@module` is). 3. It should also have a `with` key, with another record where you put all the import attributes you want emitted. Notice `\"some-exotic-identifier"` - you'll need to escape any key that's not a valid ReScript record key. Also notice `type_`. Since `type` is a reserved keyword in ReScript, you can use `type_` instead. It will be output as `type` in the JavaScript code. ## Dynamic Import Leveraging JavaScript's [dynamic `import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) to reduce bundle size and lazy load code as needed is easy in ReScript. It's also a little bit more convenient than in regular JavaScript because you don't need to keep track of file paths manually with ReScript's module system. ### Dynamically Importing Parts of a Module Use the `import` function to dynamically import a specific part of a module. Put whatever `let` binding you want to import in there, and you'll get a `promise` back resolving to that specific binding. Let's look at an example. Imagine the following file `MathUtils.res`: ```rescript let add = (a, b) => a + b let sub = (a, b) => a - b ``` Now let's dynamically import the add function in another module, e.g. `App.res`: ```rescript // App.res let main = async () => { let add = await import(MathUtils.add) let onePlusOne = add(1, 1) Console.log(onePlusOne) } ```` ```javascript async function main() { var add = await import("./MathUtils.mjs").then(function(m) { return m.add; }); var onePlusOne = add(1, 1); console.log(onePlusOne); } ```` ### Dynamically Importing an Entire Module The syntax for importing a whole module looks a little different, since we are operating on the module syntax level; instead of using `import`, you may simply `await` the module itself: ```rescript // App.res let main = async () => { module Utils = await MathUtils let twoPlusTwo = Utils.add(2, 2) Console.log(twoPlusTwo) } ```` ```javascript async function main() { var Utils = await import("./MathUtils.mjs"); var twoPlusTwo = Utils.add(2, 2); console.log(twoPlusTwo); } ```` ## Export To JavaScript ### Export a Named Value As mentioned in ReScript's idiomatic [Import & Export](./import-export.mdx), every let binding and module is exported by default to other ReScript modules (unless you use a `.resi` [interface file](./module.mdx#signatures)). If you open up the compiled JS file, you'll see that these values can also directly be used by a _JavaScript_ file too. ### Export a `default` Value If your JS project uses JavaScript module, you're likely exporting & importing some default values: ```js // student.js export default name = "Al"; ``` ```js // teacher.js import studentName from "student.js"; ``` A JavaScript default export is really just syntax sugar for a named export implicitly called `default` (now you know!). So to export a default value from ReScript, you can just do: ```res // ReScriptStudent.res let default = "Bob" ``` ```js let $$default = "Bob"; export { $$default as default }; ``` ```js var $$default = "Bob"; export { $$default, $$default as default }; ``` You can then import this default export as usual on the JS side: ```js // teacher2.js import studentName from "ReScriptStudent.js"; ``` If your JavaScript's default import is transpiled by Babel/Webpack/Jest into CommonJS `require`s, we've taken care of that too! See the CommonJS output tab for `__esModule`. # Bind to Global JS Values **First**, make sure the value you'd like to model doesn't already exist in our [provided API](/docs/manual/api/stdlib). Some JS values, like `setTimeout`, live in the global scope. You can bind to them like so: ```res @val external setTimeout: (unit => unit, int) => float = "setTimeout" @val external clearTimeout: float => unit = "clearTimeout" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` (We already provide `setTimeout`, `clearTimeout` and others in the [Core API](/docs/manual/api/stdlib) module). This binds to the JavaScript [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrworkerGlobalScope/setTimeout) methods and the corresponding `clearTimeout`. The `external`'s type annotation specifies that `setTimeout`: - Takes a function that accepts `unit` and returns `unit` (which on the JS side turns into a function that accepts nothing and returns nothing aka `undefined`), - and an integer that specifies the duration before calling said function, - returns a number that is the timeout's ID. This number might be big, so we're modeling it as a float rather than the 32-bit int. ### Tips & Tricks **The above isn't ideal**. See how `setTimeout` returns a `float` and `clearTimeout` accepts one. There's no guarantee that you're passing the float created by `setTimeout` into `clearTimeout`! For all we know, someone might pass it `Math.random()` into the latter. We're in a language with a great type system now! Let's leverage a popular feature to solve this problem: abstract types. ```res type timerId @val external setTimeout: (unit => unit, int) => timerId = "setTimeout" @val external clearTimeout: timerId => unit = "clearTimeout" let id = setTimeout(() => Console.log("hello"), 100) clearTimeout(id) ``` ```js let id = setTimeout(() => { console.log("hello"); }, 100); clearTimeout(id); export { id }; ``` Clearly, `timerId` is a type that can only be created by `setTimeout`! Now we've guaranteed that `clearTimeout` _will_ be passed a valid ID. Whether it's a number under the hood is now a mere implementation detail. Since `external`s are inlined, we end up with JS output as readable as hand-written JS. ## Global Modules If you want to bind to a value inside a global module, e.g. `Math.random`, attach a `scope` to your `val` external: ```res @scope("Math") @val external random: unit => float = "random" let someNumber = random() ``` ```js let someNumber = Math.random(); export { someNumber }; ``` you can bind to an arbitrarily deep object by passing a tuple to `scope`: ```res @val @scope(("window", "location", "ancestorOrigins")) external length: int = "length" ``` ```js /* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ ``` This binds to `window.location.ancestorOrigins.length`. ## Special Global Values Global values like `__filename` and `__DEV__` don't always exist; you can't even model them as an `option`, since the mere act of referring to them in ReScript (then compiled into JS) would trigger the usual `Uncaught ReferenceError: __filename is not defined` error in e.g. the browser environment. For these troublesome global values, ReScript provides a special approach: `%external(a_single_identifier)`. ```res switch %external(__DEV__) { | Some(_) => Console.log("dev mode") | None => Console.log("production mode") } ``` ```js import * as Js from "@rescript/runtime/lib/es6/Js.js"; let match = Js.undefinedToOption( typeof __DEV__ === "undefined" ? undefined : __DEV__, ); if (match !== undefined) { console.log("dev mode"); } else { console.log("production mode"); } ``` That first line's `typeof` check won't trigger a JS ReferenceError. Another example: ```res switch %external(__filename) { | Some(f) => Console.log(f) | None => Console.log("non-node environment") }; ``` ```js import * as Js from "@rescript/runtime/lib/es6/Js.js"; import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js"; let f = Js.undefinedToOption( typeof __filename === "undefined" ? undefined : __filename, ); if (f !== undefined) { console.log(Primitive_option.valFromOption(f)); } else { console.log("non-node environment"); } ``` {/* TODO: revamp this page. Not good. Tell to use globalThis["foo"], and look in our stdlib */} # JSON ## Parse Bind to JavaScript's `JSON.parse` and type the return value as the type you're expecting: ```res // declare the shape of the json you're binding to type data = {names: array} // bind to JS' JSON.parse @scope("JSON") @val external parseIntoMyData: string => data = "parse" let result = parseIntoMyData(`{"names": ["Luke", "Christine"]}`) let name1 = result.names[0] ``` ```js let result = JSON.parse(`{"names": ["Luke", "Christine"]}`); let name1 = result.names[0]; export { result, name1 }; ``` Where `data` can be any type you assume the JSON is. As you can see, this compiles to a straightforward `JSON.parse` call. As with regular JS, this is convenient, but has no guarantee that e.g. the data is correctly shaped, or even syntactically valid. Slightly dangerous. ## Stringify Use [`JSON.stringify`](/docs/manual/api/stdlib/json#value-stringify) if your data is of type `JSON.t` or [`JSON.stringifyAny`](/docs/manual/api/stdlib/json#value-stringifyAny) if it is not. ```res Console.log(JSON.stringifyAny(["Amy", "Joe"])) ``` ```js console.log(JSON.stringify(["Amy", "Joe"])); ``` ## Import a JSON file Use the `@module` attribute to import JSON files directly. ```res @module external studentNames: JSON.t = "./students.json" Console.log(studentNames) ``` ```js import * as StudentsJson from "./students.json"; let studentNames = StudentsJson; console.log(studentNames); export { studentNames }; ``` ```js var StudentsJson = require("./students.json"); var studentNames = StudentsJson; console.log(studentNames); ``` ## Advanced The generated types are [variants](./variant.mdx), and decoding them requires you to drill down as much # Inlining Constants Sometimes, in the JavaScript output, you might want a certain value to be forcefully inlined. For example: ```js if (process.env.mode === "development") { console.log("Dev-only code here!"); } ``` The reason is that your JavaScript bundler (e.g. Webpack) might turn that into: ```js if ("production" === "development") { console.log("Dev-only code here!"); } ``` Then your subsequent Uglifyjs optimization would remove that entire `if` block. This is how projects like ReactJS provide a development mode code with plenty of dev warnings, while ensuring that the uglified (minified) production code is free of those expensive blocks. So, in ReScript, producing that example `if (process.env.mode === 'development')` output is important. This first try doesn't work: ```res @val external process: 'a = "process" let mode = "development" if (process["env"]["mode"] === mode) { Console.log("Dev-only code here!") } ``` ```js let mode = "development"; if (process.env.mode === mode) { console.log("Dev-only code here!"); } export { mode }; ``` The JS output shows `if (process.env.mode === mode)`, which isn't what we wanted. To inline `mode`'s value, use `@inline`: ```res @val external process: 'a = "process" @inline let mode = "development" if (process["env"]["mode"] === mode) { Console.log("Dev-only code here!") } ``` ```js if (process.env.mode === "development") { console.log("Dev-only code here!"); } ``` Now your resulting JS code can pass through Webpack and Uglifyjs like the rest of your JavaScript code, and that whole `console.log` can be removed. The inlining currently only works for **string, float and boolean**. ## Tips & Tricks This is **not** an optimization. This is an edge-case feature for folks who absolutely need particular values inlined for a JavaScript post-processing step, like conditional compilation. Beside the difference in code that the conditional compilation might end up outputting, there's no performance difference between inlining and not inlining simple values in the eyes of a JavaScript engine. # Use Illegal Identifier Names Sometime, for e.g. a let binding or a record field, you might want to use: - A capitalized name. - A name that contains illegal characters (e.g. emojis, hyphen, space). - A name that's one of ReScript's reserved keywords. We provide an escape hatch syntax for these cases: ```res let \"my-🍎" = 10 type element = { \"aria-label": string } let myElement = { \"aria-label": "close" } let label = myElement.\"aria-label" let calculate = (~\"Props") => { \"Props" + 1 } ``` ```js function calculate(Props) { return (Props + 1) | 0; } let my$$unknown$unknown$unknown$unknown = 10; let myElement = { "aria-label": "close", }; let label = "close"; export { my$$unknown$unknown$unknown$unknown, myElement, label, calculate }; ``` See the output. **Use them only when necessary**, for interop with JavaScript. This is a last-resort feature. If you abuse this, many of the compiler guarantees will go away. # Generate Converters & Helpers **Note**: if you're looking for: - `@deriving(jsConverter)` for records - `@deriving({jsConverter: newType})` for records - `@deriving(abstract)` for records - `@deriving(jsConverter)` for plain and polymorphic variants These particular ones are no longer needed. Select a doc version lower than `9.0` in the sidebar to see their old docs. {/* TODO: genType */} When using ReScript, you will sometimes come into situations where you want to - Automatically generate functions that convert between ReScript's internal and JS runtime values (e.g. variants). - Convert a record type into an abstract type with generated creation, accessor and method functions. - Generate some other helper functions, such as functions from record attribute names. You can use the `@deriving` decorator for different code generation scenarios. All different options and configurations will be discussed on this page. **Note:** Please be aware that extensive use of code generation might make it harder to understand your programs (since the code being generated is not visible in the source code, and you just need to know what kind of functions / values a decorator generates). ## Generate Functions & Plain Values for Variants Use `@deriving(accessors)` on a variant type to create accessor functions for its constructors. ```res @deriving(accessors) type action = | Click | Submit(string) | Cancel; ``` ```js function submit(param_0) { return { TAG: "Submit", _0: param_0, }; } let click = "Click"; let cancel = "Cancel"; export { click, submit, cancel }; ``` Variants constructors with payloads generate functions, payload-less constructors generate plain integers (the internal representation of variants). **Note**: - The generated accessors are lower-cased. - You can now use these helpers on the JavaScript side! But don't rely on their actual values please. ### Usage ```res nocheck let s = submit("hello"); /* gives Submit("hello") */ ``` This is useful: - When you're passing the accessor function as a higher-order function (which plain variant constructors aren't). - When you'd like the JS side to use these values & functions opaquely and pass you back a variant constructor (since JS has no such thing). Please note that in case you just want to _pipe a payload into a constructor_, you don't need to generate functions for that. Use the `->` syntax instead, e.g. `"test"->Submit`. ## Generate Field Accessors for Records Use `@deriving(accessors)` on a record type to create accessors for its record field names. ```res @deriving(accessors) type pet = {name: string} let pets = [{name: "bob"}, {name: "bob2"}] pets ->Array.map(name) ->Array.joinWith("&") ->Console.log ``` ```js function name(param) { return param.name; } let pets = [ { name: "bob", }, { name: "bob2", }, ]; console.log(pets.map(name).join("&")); export { name, pets }; ``` # Libraries & Publishing ReScript libraries are just like JavaScript libraries: published & hosted on [NPM](http://npmjs.com). You can reuse your `npm`, `yarn` and `package.json`-related tools to manage them! ## Tips & Tricks ### Publish We recommend you to check in your compiled JavaScript output, for its [various benefits](./interop-with-js-build-systems.mdx#popular-js-build-systems). If not, then at least consider publishing the JavaScript output by un-ignoring them in your [npmignore](https://docs.npmjs.com/cli/v7/using-npm/developers#keeping-files-out-of-your-package). This way, your published ReScript package comes with plain JavaScript files that JS users can consume. If your project's good, JS users might not even realize that they've installed a library written in ReScript! ### Find Libraries Search `rescript`-related packages on NPM, or use our [Package Index](/packages). If you can't find what you're looking for, remember that **you don't need a wrapper** to use a JS library: - Most JS data types, such as array and objects, [map over cleanly to ReScript and vice-versa](./shared-data-types.mdx). - You also have access to the familiar [Core API](/docs/manual/api/stdlib). - You can use a JavaScript library without needing to install dedicated binding libraries. Check the [`external`](./external.mdx) page. # 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: ```ts type 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: ```res @genType module Size = { type t = | Small | Medium | Large let getNum = (size: t) => switch size { | Small => 1. | Medium => 5. | Large => 10. } } ``` ```ts import * as MyCompBS__Es6Import from "./MyComp.res"; const MyCompBS: any = MyCompBS__Es6Import; export type Size_t = "Small" | "Medium" | "Large"; export const Size_getNum: (size: Size_t) => number = MyCompBS.Size.getNum; export const Size: { getNum: (size: Size_t) => number } = MyCompBS.Size; ``` ```js function getNum(size) { switch (size) { case "Small": return 1; case "Medium": return 5; case "Large": return 10; } } let Size = { getNum: getNum, }; export { Size }; ``` ## Setup Add a `gentypeconfig` section to your `rescript.json` (See [Configuration](./build-configuration.mdx#gentypeconfig) 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](https://github.com/rescript-lang/rescript-compiler/blob/master/jscomp/gentype_tests/typescript-react-example/src/Hooks.res). Save the file and rebuild the project via `npm run build:res` 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: ```js import 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 from `name` to `Iname`. ## 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` 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; ``` More complete example shims can be found [here](https://github.com/rescript-lang/rescript-compiler/blob/master/jscomp/gentype_tests/typescript-react-example/src/shims/). ## 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](https://github.com/rescript-lang/rescript-compiler/issues/6196)). 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](./build-configuration.mdx#package-specs) and file suffixes that end on `.js`, like `.res.js` or `.bs.js`. - **Limited namespace support**. Currently there's limited [namespace](./build-configuration.mdx#name-namespace) support, and only `namespace:true` is possible, not e.g. `namespace:"custom"`.