{"id":13494803,"url":"https://github.com/rescriptbr/rescript-bindings-cookbook","last_synced_at":"2025-12-26T16:14:10.375Z","repository":{"id":114863089,"uuid":"400671718","full_name":"rescriptbr/rescript-bindings-cookbook","owner":"rescriptbr","description":"Task-oriented guide to writing JavaScript bindings for ReScript","archived":false,"fork":false,"pushed_at":"2021-08-28T03:39:28.000Z","size":6,"stargazers_count":56,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-10T20:08:51.303Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rescriptbr.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-08-28T01:12:01.000Z","updated_at":"2025-02-15T19:48:15.000Z","dependencies_parsed_at":"2024-01-07T18:18:38.934Z","dependency_job_id":"b173b689-f985-43ca-b40e-187668ef1b0a","html_url":"https://github.com/rescriptbr/rescript-bindings-cookbook","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescriptbr%2Frescript-bindings-cookbook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescriptbr%2Frescript-bindings-cookbook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescriptbr%2Frescript-bindings-cookbook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rescriptbr%2Frescript-bindings-cookbook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rescriptbr","download_url":"https://codeload.github.com/rescriptbr/rescript-bindings-cookbook/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243330259,"owners_count":20274037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T19:01:28.407Z","updated_at":"2025-12-26T16:14:05.313Z","avatar_url":"https://github.com/rescriptbr.png","language":null,"readme":"#+title: ReScript Bindings Cookbook\n\nThis cookbook is an update to the [[https://github.com/yawaramin/bucklescript-bindings-cookbook][yawaramin's cookbook]]!\n\n** Introduction\n    Writing =ReScript= bindings can be somewhere between an art and a science, taking some learning investment into both the =JavaScript= and =ReScript= type systems to get a proper feel for it.\n\n This cookbook aims to be a quickstart, task-focused guide for writing bindings. The idea is that you have in mind some =JavaScript= that you want to write, and look up the binding that should (hopefully) produce that output =JavaScript=.\n\n Along the way, I will try to introduce standard types for modelling various =JavaScript= data.\n\n** Raw JavaScript\n    /ReScript:/\n    #+begin_src rescript\n      let add = %raw(\"(a, b) =\u003e a + b\")\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let result = add(2, 2)\n    #+end_src\n\n** Globals\n*** Reference a global value\n    /JavaScript:/\n    #+begin_src js\n      setTimeout(() =\u003e console.log(\"Hey there\"), 40 * 100);\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @val external setTimeout: (unit =\u003e unit, int) =\u003e unit = \"setTimeout\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      setTimeout(() =\u003e Js.Console.log(\"Hey There\"), 40 * 100)\n    #+end_src\n\n    Ref: https://rescript-lang.org/docs/manual/latest/interop-cheatsheet#global-value\n\n*** Check if the global value exists\n    /JavaScript:/\n    #+begin_src js\n      if (window) console.log(\"window exists\")\n      else console.log(\"window does not exist\")\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      swicth (%external window) {\n      | Some(_) =\u003e Js.Console.log(\"window exists\")\n      | None =\u003e Js.Console.log(\"window does not exist\")\n      }\n    #+end_src\n\n    =%external NAME= makes =NAME= available as an value of type =option\u003c'a\u003e=, meaning its wrapped value is compatible with any type. I recommend that, if you use the value, to cast it safely into a know type first.\n\n*** Reference a variable in a global module\n    /JavaScript:/\n    #+begin_src js\n      Math.PI;\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @val @scope(\"Math\") \n      external pi: float = \"PI\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let circleArea = (~radio: float) =\u003e pi * (radio * radio)\n    #+end_src\n\n    Ref: https://rescript-lang.org/docs/manual/latest/interop-cheatsheet#global-modules-value\n\n** Modules\n*** Function in CJS/ES module\n    /JavaScript:/\n    #+begin_src js\n      const path = require('path');\n\n      const dir = path.join('a', 'b');\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @module(\"path\") external join: (string, string) =\u003e string = \"join\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let dir = join(\"a\", \"b\")\n    #+end_src\n\n    Ref: https://rescript-lang.org/docs/manual/latest/bind-to-js-function\n\n*** Import entire module as a value\n    /JavaScript:/\n    #+begin_src js\n      const foo = require('foo')\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @module external foo: int =\u003e unit = \"foo\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let () = foo(1)\n    #+end_src\n\n*** Import ES6 module default export\n    /JavaScript:/\n    #+begin_src js\n      import foo from 'foo';\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @module(\"foo\") external foo: int =\u003e unit  \"default\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let () = foo(1)\n    #+end_src\n\n*** A function scoped inside an object in a module\n    /JavaScript:/\n    #+begin_src js\n      import { foo } from 'foo';\n\n      foo.bar.baz();\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      module Foo = {\n        module Bar = {\n          @module(\"foo\") @scope(\"bar\")\n          external baz: unit =\u003e unit = \"baz\"\n        }\n      }\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let () = Foo.Bar.baz()\n    #+end_src\n\n    It's not necessary to nest the binding inside =ReScript= modules, but mirroring the structure of the =JavaScript= module layout does make the binding more discoverable.\n\n    Note that =@scope= works not just with =@module=, but also with =@val= (as shown earlier), and with combinations of =@module=, =@new= (covered in the OOP section), etc.\n\n    Tip: the =@scope(...)= attribute supports an arbitrary level of scoping by passing the scope as a tuple argument, e.g. =@scope((\"a\", \"b\", \"c\"))=.\n\n** Functions\n:PROPERTIES:\n:CUSTOM_ID: functions\n:END:\n*** Functions with rest args\n    /JavaScript:/\n    #+begin_src js\n      const path = require('path');\n\n      const xs = ['b', 'c'];\n      const dir = path.join('a', ...xs);\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @module(\"path\") @variadic\n      external join: array\u003cstring\u003e =\u003e string = \"join\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let dir = join([\"a\", \"b\", \"c\"])\n    #+end_src\n\n   Note that the rest args must all be of the same type for =@variadic= to work. If they really have different types, then more advanced techniques are needed. \n\n    Ref: https://rescript-lang.org/docs/manual/latest/bind-to-js-function#variadic-function-arguments\n\n*** Call a function with named arguments for readability\n    /ReScript:/\n    #+begin_src rescript\n      @val external range(~start: int, ~stop: int, ~step: int) =\u003e array\u003cint\u003e = \"range\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let nums = range(~start=1, ~stop=10, ~step=2)\n    #+end_src\n\n*** Polymorphic function\n    /JavaScript:/\n    #+begin_src js\n      foo(\"string\");\n      foo(true);\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @val external fooString: string =\u003e unit = \"foo\"\n      @val external fooBool: bool =\u003e unit = \"foo\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      fooString(\"string\")\n      fooBool(false)\n    #+end_src\n\n    Ref: https://rescript-lang.org/docs/manual/latest/bind-to-js-function#modeling-polymorphic-function\n\n*** Function with optional final argument(s)\n    /JavaScript:/\n    #+begin_src js\n      foo(1);\n      foo(1, 2);\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @val external foo: (int, int=?) =\u003e unit = \"foo\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      foo(1, ())\n      foo(1, 2)\n    #+end_src\n\n    If a =ReScript= function or binding has an optional parameter, it needs a positional parameter at the end of the parameter list to help the compiler understand when function application is finished and the function can actually execute. If this seems tedious, remember that no other language gives you out-of-the-box curried parameters and named parameters and optional parameters.\n\n*** Options object argument\n    /JavaScript:/\n    #+begin_src js\n      const fs = require('fs');\n\n      fs.mkdir('src', { recursive: true });\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      type mkdirOptions\n\n      @obj external mkdirOptions: (~recursive: bool=?, unit) =\u003e mkdirOptions = \"\"\n      @module(\"fs\") external mkdir: (string, ~options: mkdirOptions=?, unit) =\u003e unit = \"mkdir\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let () = mkdir(\"src\", ())\n      let () = mkdir(\"src/main\", ~options=mkdirOptions(~recursive=true, ()), ())\n    #+end_src\n\n    The =@obj= attribute allows creating a function that will output a =JavaScript= object. There are simpler ways to create =JavaScript= objects (see OOP section), but this is the only way that allows omitting optional fields like recursive from the output object. By making the binding parameter optional (~\\nbsprecursive: bool=?~), you indicate that the field is also optional in the object.\n\n**** Alternative way\n    Calling a function like ~mkdir(\"src/main\", \\nbspoptions=..., ())~ can be syntactically pretty heavy, for the benefit of allowing the optional argument. But there is another way: binding to the same underlying function twice and treating the different invocations as overloads.\n\n    /ReScript:/\n    #+begin_src rescript\n      @module(\"fs\") external mkdir: string =\u003e unit = \"mkdir\"\n      @module(\"fs\") external mkdirWith: (string, mkdirOptions) =\u003e unit = \"mkdir\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let () = mkdir(\"src/main\")\n      let () = mkdirWith(\"src/main\", mkdirOptions(~recursive=true, ()))\n    #+end_src\n\n    This way you don't need optional arguments, and no final =()= argument for =mkdirWith=.\n\n*** Function with callback\n    /Javascript:/\n    #+begin_src js\n      const fs = require('fs')\n\n      const cb = (err, data) =\u003e err ? console.log(\"ERROR\") : console.log(data);\n      fs.readFile('./file.txt', cb);\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      type fileError = {\n        errno: int,\n        code: string,\n        syscall: string,\n        path: string\n      }\n\n      @module(\"fs\") \n      external readFile: (string, @uncurry (option\u003cfileError\u003e, option\u003cstring\u003e) =\u003e unit) =\u003e unit = \"readFile\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      readFile(\"./file.txt\", (err, data) =\u003e {\n        switch ((err, data)) {\n        | (None, Some(data)) =\u003e Js.Conole.log(data)\n        | (Some(_), None) =\u003e Js.Console.log(\"ERROR\")\n        | _ =\u003e Js.Console.log(\"That clause will never happen...\")\n        }\n      })\n    #+end_src\n\n** Objects\n*** Create an object\n    /JavaScript:/\n    #+begin_src js\n      const person = {name: \"jhon\", age: 18};\n\n      const {name, age} = person;\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      type person = {\n        name: string,\n        age: int\n      }\n\n      let person = {name: \"jhon\", age: 18}\n      let {name, age} = person\n    #+end_src\n\n    Ref: https://rescript-lang.org/docs/manual/latest/bind-to-js-object#bind-to-record-like-js-objects\n\n** Classes and OOP\n    In =ReScript= it's idiomatic to bind to class properties and methods as functions which take the instance as just a normal function argument. So e.g., instead of \n    #+begin_src js\n      const foo = new Foo();\n      foo.bar();\n    #+end_src\n\n    You'll write:\n    #+begin_src rescript\n      let foo = Foo.make()\n      let () = Foo.bar(foo)\n    #+end_src\n\n    Note that many of techiniques shown in the [[#functions][Functions]] secton are applicable to the instance members shown below.\n\n**** I don't see what I need here\n    Try looking in the [[#functions][Functions]] section; in =ReScript= functions and instance methods can share many of the same binding techniques.\n\n*** Call a class constructor\n    /JavaScript:/\n    #+begin_src js\n      const foo = new Foo();\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      // Foo.res or module Foo { ... }\n      type t\n\n      @new external make = unit =\u003e t = \"Foo\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let foo = Foo.make()\n    #+end_src\n    \n    Note the abstract type =t=. In =ReScript= you will model any class that's not a shared data type as an abstract data type. This means you won't expose the internals of the definition of the class, only its interface (accessors, methods), using functions which include the type t in their signatures. This is shown in the next few sections.\n\n    A =ReScript= function binding doesn't have the context that it's binding to a JavaScript class like Foo, so you will want to explicitly put it inside a corresponding module Foo to denote the class it belongs to. In other words, model =JavaScript= classes as =ReScript= modules.\n\n    Ref: https://rescript-lang.org/docs/manual/latest/bind-to-js-object#bind-to-a-js-object-thats-a-class\n\n*** Get a instance property\n    /JavaScript:/\n    #+begin_src js\n      const foo = new Foo();\n      let bar = foo.bar;\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      // In Foo.res or module Foo { ... }\n      // [...]\n      @get external bar: t =\u003e int \"bar\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let foo = Foo.make()\n      let bar = Foo.bar(foo)\n    #+end_src\n\n*** Call a instance method\n    /JavaScript:/\n    #+begin_src js\n      const foo = new Foo();\n\n      foo.baz();\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      // In Foo.res or module Foo { ... }\n      @send external baz: t =\u003e unit = \"baz\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let foo = Foo.make()\n      let () = Foo.baz(foo)\n    #+end_src\n\n** Null and undefined\n*** Check for undefined\n    /JavaScript:/\n    #+begin_src js\n      const foo = new Foo();\n\n      // if (!foo.bar)\n      if (foo.bar === undefined) console.log('undefined');\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @get external bar: t =\u003e option\u003cint\u003e = \"bar\"\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let foo = Foo.make()\n      let bar = Foo.bar(foo)\n\n      switch (bar) {\n      | Some(val) =\u003e Js.Console.log(val)\n      | None =\u003e Js.Console.log(\"undefined\")\n      }\n    #+end_src\n\n    If you know some value may be =undefined= (but not =null=, see next section), and if you know its type is monomorphic (i.e. not generic), then you can model it directly as an =option(...)= type.\n\n*** Check for null and undefined\n    /JavaScript:/\n    #+begin_src js\n      const foo = new Foo();\n\n      // if (!foo.bar)\n      if (foo.bar === null || foo.bar === undefined) \n        console.log(\"null or undefined\");\n    #+end_src\n\n    /ReScript:/\n    #+begin_src rescript\n      @get @return(nullable) external bar: t =\u003e option\u003ct\u003e\n    #+end_src\n\n    /Usage:/\n    #+begin_src rescript\n      let foo = Foo.make()\n      let bar = Foo.bar(foo)\n\n      switch (bar) {\n      | Some(val) =\u003e Js.Console.log(val)\n      | None =\u003e Js.Console.log(\"undefined\")\n      }\n    #+end_src\n\n    If you know the value is 'nullable' (i.e. could be =null= or =undefined=), or if the value could be polymorphic, then =@return(nullable)= is appropriate to use.\n\n    Note that this attribute requires the return type of the binding to be an =option(...)= type as well.\n","funding_links":[],"categories":["Others","ReScript"],"sub_categories":["Tutorials"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frescriptbr%2Frescript-bindings-cookbook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frescriptbr%2Frescript-bindings-cookbook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frescriptbr%2Frescript-bindings-cookbook/lists"}