How to write rescript bindings for a react library
Bindings are nothing but FFI
According to wiki:
A foreign function interface (FFI) is a mechanism by which a program written in one programming language can call routines or make use of services written in another.
ReScript bindings are just an unsafe direct-access mechanism to the JS world. Just some external declarations and some type definitions.
In this post, we are going to write few bindings for the antd library.
Let's get started with a very simple component that doesn't accept any props or children.
Named Import
Component
// closeCircleOutlined.jsx
import { CloseOutlined } from '@ant-design/icons'
/* npm install --save @ant-design/icons */
ReactDOM.render(
<CloseOutlined />,
mountNode,
);
Binding:
// Bindings.res
module CloseCircleOutlined = {
@module("@ant-design/icons") @react.component
external make: React.element = "CloseCircleOutlined"
}
Default Import
// Bindings.res
// import CloseOutlined from '@ant-design/icons'
module CloseOutlined = {
@module("@ant-design/icons") @react.component
external make: React.element = "default"
}
I'm assuming you know what decorators are. If you haven't read about them before then you can read here
The ** basic structure ** is
module ComponentName = {
@module("<node_module_name>") @react.component
external make: React.element = "<NamedImport> or <default>"
}
This component doesn't accept any props yet.
Let's write another binding for a button.
Component:
import { Button } from 'antd';
ReactDOM.render(
<>
<Button shape="circle">Circle Button</Button>
<Button shape="round">Round Button</Button>
</>,
mountNode,
);
Binding:
Copy the structure and fill in the names.
// Bindings.res
module Button = {
@module("antd") @react.component
external make: React.element = "Button"
}
At this point, you can only use the button as
<Button/>
not as
<Button shape="circle">Text</Button>
Props
Let's add a shape
prop.
// Bindings.res
module Button = {
@module("antd") @react.component
external make: (~shape:string) => React.element = "Button"
}
Remember, we have to declare each prop as a Named argument.
Keyword prop
Now, here is a little tricky one. Let's add a type
prop.
// Bindings.res
module Button = {
@module("antd") @react.component
external make: (~\"type": string, ~shape:string) => React.element = "Button"
}
type
is a keyword in a rescript so whenever we use a keyword we have to escape it.
Children
To accept the child component, we will use children
named argument
// Bindings.res
module Button = {
@module("antd") @react.component
external make: (~children:React.element, ~shape:string, ~\"type": string) => React.element = "Button"
}
// App.res
ReactDOM.render(
<Button \"type"="primary" shape="circle">{React.string("Click me")}</Button>,
mountNode
)
React prop
// Bindings.res
module Button = {
@module("antd") @react.component
external make: (~icon: React.element, ~children:React.element, ~shape:string, ~\"type": string) => React.element = "Button"
}
// App.res
ReactDOM.render(
<Button icon={<DownloadOutlined />} shape="circle" \"type"="primary">{React.string("Click me")}</Button>,
mountNode
)
That's all folks!