# ghc-wasm-bindgen
Generate JavaScript/TypeScript bindings for a pure Haskell module compiled
with GHC's WASM backend.
```haskell
module ShapeWasm (Shape (..), area, scale, largest) where
import Shape
```
```ts
import { area, Circle, largest, Rect, scale } from "shape";
import { init } from "shape/runtime";
await init(wasmUrl);
area(Rect({ width: 3, height: 4 })); // 12
scale(2, Circle(1)); // { $tag: "Circle", contents: 2 }
largest([]); // null
area(Circle("x")); // TypeScript error
```
See [`example/`](example/) for a complete project.
## Types
| Haskell | TypeScript |
| ----------------------------------------------- | ----------------------------- |
| `Double`, `Float` | `number` |
| `Int`, `Word`, `Int8`…`Int64`, `Word8`…`Word64` | `number` |
| `Integer`, `Natural` | `bigint` |
| `Bool` | `boolean` |
| `Char`, `String`, `Text` | `string` |
| `Day` | `string` (YYYY-MM-DD) |
| `UTCTime` | `Date` |
| `Maybe a` | `a \| null` |
| `Either e a` | `{ Left: e } \| { Right: a }` |
| `(a, b)` | `[a, b]` |
| `[a]`, `Vector a` | `a[]` |
| `NonEmpty a` | `[a, ...a[]]` |
| `Set a`, `HashSet a`, `IntSet` | `Set<a>` |
| `Map k v`, `HashMap k v`, `IntMap v` | `Map<k, v>` |
Your own `data` and `newtype` declarations:
| Constructors | TypeScript |
| ------------------------- | ---------------------------- |
| with record fields | `{ $tag: "C"; field: … }` |
| positional | `{ $tag: "C"; contents: … }` |
| all without fields (enum) | `"A" \| "B"` |
Notes:
- Every type the API uses must be exported with all its constructors.
- Not supported yet: `IO`, polymorphic functions, functions as arguments,
operators, types with parameters and GADTs.
## Errors
- An argument that does not fit its type (an out-of-range integer, a `Char`
that is not one code point, an unknown constructor, …) throws a `TypeError`
before reaching Haskell.
- An exception in Haskell throws a `HaskellError`.
## Usage
### `build`: cabal projects
From the cabal project that contains the module:
```sh
ghc-wasm-bindgen build src/ShapeWasm.hs --name shape --out pkg/src
```
The module must be exposed by a library, in the same package as the code it
wraps or in one that depends on it. This writes `shape.js`, `shape.d.ts`,
`shape.wasm` and the JS runtime to `pkg/src/`, and nothing else, so the
directory around it can be maintained by hand, even in another repository.
To publish it as an npm package, write its `package.json` yourself, for
example:
```json
{
"name": "shape",
"version": "0.1.0",
"type": "module",
"exports": {
".": { "types": "./src/shape.d.ts", "default": "./src/shape.js" },
"./runtime": { "types": "./src/ghc_wasm_bindgen_runtime.d.ts", "default": "./src/ghc_wasm_bindgen_runtime.js" },
"./shape.wasm": "./src/shape.wasm"
}
}
```
Then import `shape`, `shape/runtime` (`init`, `HaskellError`) and
`shape/shape.wasm`.
### `generate`: plain source files
```sh
ghc-wasm-bindgen generate api/ShapeWasm.hs -i lib --name shape --hs-out gen --js-out out
```
This writes the Haskell bindings and runtime to `gen/`, and the JS bindings
and runtime to `out/`. Compiling is up to you:
```sh
wasm32-wasi-cabal install --lib aeson text --package-env . # once
wasm32-wasi-ghc -package ghc-experimental -igen -ilib -iapi gen/ShapeWasmBindings.hs \
-no-hs-main -optl-mexec-model=reactor -outputdir build -o shape.wasm
"$(wasm32-wasi-ghc --print-libdir)"/post-link.mjs -i shape.wasm -o out/ghc_wasm_jsffi.js
wasm-opt -Oz shape.wasm -o out/shape.wasm
```
## Installation
```sh
cabal install ghc-wasm-bindgen
```
Requirements:
- GHC 9.10 and cabal.
- The WASM toolchain from
[ghc-wasm-meta](https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta) on
`PATH` (tested with GHC 9.14):
```sh
curl https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/raw/master/bootstrap.sh | FLAVOUR=9.14 sh
```