Interacting with JavaScript

Fable features for easy interoperability

Attention: This document corresponds to Fable 0.6.x and needs to be updated to the latest version. Please check the migration guide.

There are several ways to interact with the JavaScript world:

Dynamic programming

Fable.Core.JsInterop implements the F# dynamic operators so you can easily access an object property by name (without static check) as follows:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open Fable.Core.JsInterop

printfn "Value: %O" jsObject?myProperty

let pname = "myProperty"

printfn "Value: %O" jsObject?(pname) // Access with a string reference

jsObject?myProperty <- 5 // Assignment is also possible

When you combine the dynamic operator with application, Fable will destructure tuple arguments as with normal method calls. These operations can also be chained to replicate JS fluent APIs.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
open Fable.Core.JsInterop

let result = jsObject?myMethod(1, 2)
// var result = jsObject.myMethod(1, 2)

chart
    ?width(768.)
    ?height(480.)
    ?group(speedSumGroup)
    ?on("renderlet", fun chart ->
        chart?selectAll("rect")?on("click", fun d ->
            Browser.console.log("click!", d))
// chart
//     .width(768)
//     .height(480)
//     .group(speedSumGroup)
//     .on("renderlet", function (chart) {
//         return chart.selectAll("rect").on("click", function (d) {
//             return console.log("click!", d);
//         });
//      });

CAUTION: When you don't use the dynamic operator and apply a tuple to a function value, it won't be destructured (tuples are translated to JS arrays). See Calling F# code from JavaScript below for more info. However, you can still use the $ operator to destructure and apply a tuple to an arbitrary value.

If you want to call the function with the new keyword, use Fable.Core.createNew instead.

1: 
2: 
3: 
open Fable.Core.JsInterop

let instance = createNew jsObject?method (1, 2)

And when you need to create JS object literals, use createObj:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open Fable.Core.JsInterop

let data =
    createObj [
        "todos" ==> Storage.fetch()
        "newTodo" ==> ""
        "editedTodo" ==> None
        "visibility" ==> "all"
    ]

Foreign interfaces

Defining a foreign interface is trivial: just create a F# interface and the compiler will call its properties or methods by name. The tricky part is to tell the compiler where the objects should be retrieved from. Normally, they will be exposed as values of an imported module, so you just need to indicate the compiler where this module is coming from using the Import attribute (see below). For example, if you want to use string_decoder from node, just write:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open Fable.Core

[<Import("*","string_decoder")>]
module string_decoder =
    type NodeStringDecoder =
        abstract write: buffer: Buffer -> strings
        abstract detectIncompleteChar: buffer: Buffer -> float

    let StringDecoder: NodeStringDecoder = jsNative

If a method accepts a lambda make sure to use System.Func in the signature to force the compiler uncurry any lambda passed as parameter (see below).

A good starting point for foreign interfaces are Typescript definition files and there's a script to make the bulk work of translating the file into F#. You can install it from npm. See the README for more information.

1: 
npm install -g ts2fable

You can find common definitions already parsed here. Some of them are available in npm, just search for fable-import packages.

Special attributes

There are some attributes available in the Fable.Core namespace to ease the interaction with JS.

Emit attribute

You can use the Emit attribute to decorate a function. Every call to the function will then be replaced inline by the content of the attribute with the placeholders $0, $1, $2... replaced by the arguments. For example, the following code will generate JavaScript as seen below.

1: 
2: 
3: 
4: 
5: 
6: 
open Fable.Core

[<Emit("$0 + $1")>]
let add (x: int) (y: string): float = jsNative

let result = add 1 "2"
1: 
var result = 1 + "2"

When you don't know the exact number of arguments you can use the following syntax:

1: 
2: 
3: 
type Test() =
    [<Emit("$0($1...)")>]
    member __.Invoke([<ParamArray>] args: int[]): obj = jsNative

It's also possible to pass syntax conditioned to optional parameters.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
type Test() =
    [<Emit("$0[$1]{{=$2}}")>]
    member __.Item with get(): float = jsNative and set(v: float): unit = jsNative

    // This syntax means: if second arg evals to true in JS print 'i' and nothing otherwise
    [<Emit("new RegExp($0,'g{{$1?i:}}')")>]
    member __.ParseRegex(pattern: string, ?ignoreCase: bool): Regex = jsNative

The content of Emit will actually be parsed by Babel so it will still be validated somehow. However, it's not advised to abuse this method, as the code in the template will remain obscure to Fable and may prevent some optimizations.

Import attribute

The Import attribute can be applied to modules, types and even functions. It will translate to ES2015 import statements, which can be later transformed to commonjs, amd or umd imports by Babel.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
// Namespace imports
[<Import("*", from="my-module")>]
// import * from "my-module"

// Member imports
[<Import("myFunction", from="my-module")>]
// import { myFunction } from "my-module"

// Default imports
[<Import("default", from="express")>]
// import express from "express"

If the module or value is globally accessible in JavaScript, you can use the Global attribute without parameters instead. When importing a relative path (starting with . as in ./js/myLib.js), the path will be resolved so it can be reached from outDir in the compiled JS code.

Fable.Core.JsInterop also contains import expressions. These are mostly useful when you need to import JS libraries without a foreign interface.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
open Fable.Core.JsInterop

let buttons = importAll<obj> "material-ui/buttons"
// import * as buttons from "material-ui/buttons"

let deepOrange500 = importMember<string> "material-ui/styles/colors"
// import { deepOrange500 } from "material-ui/styles/colors"

let getMuiTheme = importDefault<obj->obj> "material-ui/styles/getMuiTheme"
// import getMuiTheme from "material-ui/styles/getMuiTheme"

Note that Fable automatically uses the name of the let-bound variable in the second example, this means you must always immediately assign the result of importMember to a named value. A difference between import attributes and expressions is the former are not resolved in the file where they're declared (as this can be erased bindings) but in the file where thery're used. While import expressions are always resolved where they're declared. But if you don't understand this, don't worry too much, this is usually transparent to the user :)

Erase attribute

In TypeScript there's a concept of Union Types which differs from union types in F#. The former are just used to statically check a function argument accepting different types. In Fable, they're translated as Erased Union Types whose cases must have one and only one single data field. After compilation, the wrapping will be erased and only the data field will remain. To define an erased union type, just attach the Erase attribute to the type. Example:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
open Fable.Core

[<Erase>]
type MyErasedType =
    | String of string
    | Number of int

myLib.myMethod(String "test")
1: 
myLib.myMethod("test")

Fable.Core already includes predefined erased types which can be used as follows:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
open Fable.Core

type Test() =
    member x.Value = "Test"

let myMethod (arg: U3<string, int, Test>) =
    match arg with
    | U3.Case1 s -> s
    | U3.Case2 i -> string i
    | U3.Case3 t -> t.Value

StringEnum attribute

Similarly, in TypeScript it's possible to define String Literal Types which are similar to enumerations with an underlying string value. Fable allows the same feature by using union types and the StringEnum attribute. These union types must not have any data fields as they will be compiled to a string matching the name of the union case.

By default, the compiled string will have the first letter lowered. If you want to prevent this or use a different text than the union case name, use the CompiledName attribute:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
open Fable.Core

[<StringEnum>]
type MyStrings =
    | Vertical
    | [<CompiledName("Horizontal")>] Horizontal

myLib.myMethod(Vertical, Horizontal)
1: 
myLib.myMethod("vertical", "Horizontal")

KeyValueList attribute

Many JS libraries accept a plain object to specify different options. With Fable, you can use union types to define these options in a more static-safe and F#-idiomatic manner. The union cases of a type with the KeyValueList attribute act as a key value pair, so they should have a single data field (if there's no data field the value is assumed to be true). When Fable encounters a list of such an union type, it will compile it as a plain JS object.

As with StringEnum the first letter of the key (the union case name) will be lowered. Again, you can modify this behaviour with the CompiledName attribute.

You can also allow custom key value pairs by adding an extra union case with the Erase attribute.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
open Fable.Core

[<KeyValueList>]
type MyOptions =
    | Flag1
    | Name of string
    | [<CompiledName("QTY")>] QTY of int
    | [<Erase>] Extra of string * obj

myLib.myMethod [
    Name "Fable"
    QTY 5
    Flag1
    Extra ("newOption", 10.5)
]
1: 
2: 
3: 
4: 
5: 
6: 
myLib.myMethod({
    name: "Fable",
    QTY: 5,
    flag1: true,
    newOption: 10.5
})

Note: Using tuples directly will have the same effect as the Erase attribute:

1: 
2: 
myLib.myMethod [Name "Fable"; unbox("level", 4)]
// myLib.myMethod({ name: "Fable", level: 4 })

As these lists will be compiled as JS objects, please note you cannot apply the usual list operations to them (e.g. appending). If you want to manipulate the "fake" lists you must implement the methods yourself. For example:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
[<KeyValueList>]
type CSSProp =
    | Border of string
    | Display of string

[<Emit("Object.assign({}, $0, $1)")>]
let ( ++ ) (a:'a list) (b:'a list) : 'a list = jsNative

let niceBorder = [ Border "1px solid blue" ]
let style = [ Display "inline-block" ] ++ niceBorder

Calling F# code from JavaScript

When interacting with JavaScript libraries, it's usual to send callbacks that will be called from JS code. For this to work properly with F# functions you must take a few things into consideration:

  • In F# commas are always used for tuples:
1: 
2: 
fun (x, y) -> x + y
// JS: function (tupledArg) { return tupledArg[0] + tupledArg[1]; }
  • In F# function arguments are always curried, so lambdas with multiple arguments translate to single-argument functions returning another function. To make it a multi-argument function in JS, you must convert it to a delegate (like System.Func<_,_,_>):
1: 
2: 
3: 
4: 
5: 
fun x y -> x + y
// JS: function (x) { return function (y) { return x + y; } }

System.Func<_,_,_>(fun x y -> x + y)
// JS: function (x, y) { return x + y; }
  • Fable will automatically convert F# functions to delegates in some situations:
    • When passing an F# lambda to a method accepting a delegate.
    • When passing functions as arguments to EmitAttribute.
    • When using dynamic programming, with ?, $, createObj or createNew.

Note: If you experience problems make the conversion explicit.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type ITest =
    abstract setCallback: Func<int,int,int> -> unit

let test(o: ITest) =
    o.setCallback(fun x y -> x + y)

// function test(o) {
//   o.setCallback(function (x, y) {
//     return x + y;
//   });
// }
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
let myMeth x y = x + y

let test(o: obj) =
    o?foo(fun x y -> x + y) |> ignore
    o?bar <- fun x y -> x + y

    // Direct application with ($) operator
    o $ (fun x y -> x * y) |> ignore

    createObj [
        "bar" ==> fun x y z -> x * y * z
        "bar2" ==> myMeth
    ]

// function test(o) {
//   o.foo(function (x, y) {
//     return x + y;
//   });

//   o.bar = function (x, y) {
//     return x + y;
//   };

//   o(function (x, y) {
//     return x * y;
//   });

//   return {
//     bar: function bar(x, y, z) {
//       return x * y * z;
//     },
//     bar2: function bar2(x, y) {
//       return myMeth(x, y);
//     }
//   };
// }

JSON serialization

It's possible to use JSON.stringify and JSON.parse to serialize objects back and forth, particularly with record and union types. Records will serialize as plain JS objects and unions will be serialized the same way as with Json.NET, making it easier to interact with a .NET server.

The only problem is JSON.parse will produce a plain JS object which won't work if you need to type test it or access the prototype members. When this is necessary, you can use toJson and ofJson functions in Fable.Core.JsInterop module. This will save the type full name in a $type field so Fable will know which type to construct when deserializing:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
open Fable.Core.JsInterop

type Tree =
    | Leaf of int
    | Branch of Tree[]
    member this.Sum() =
        match this with
        | Leaf i -> i
        | Branch trees ->
            trees |> Seq.map (fun x -> x.Sum()) |> Seq.sum

let tree =
    Branch [|Leaf 1; Leaf 2; Branch [|Leaf 3; Leaf 4|]|]

let json = toJson tree
let tree2 = ofJson<Tree> json

let typeTest = box tree2 :? Tree    // Type is kept
let sum = tree2.Sum()   // Prototype members can be accessed

This will work when exchanging objects with a server, if the server includes the type full name in a $type field and the client code knows the type definiton. With Json.NET you can do this by simply using the TypeNameHandling.All setting.

1: 
2: 
3: 
4: 
5: 
6: 
type R = { i: int; s: string }

let x = { i=1; s="1" }
let json = JsonConvert.SerializeObject(x, JsonSerializerSettings(TypeNameHandling=TypeNameHandling.All))

// {"$type":"Fable.Tests.Lists+R, Fable.Tests","i":1,"s":"1"}

Caveats:

  • Works with records, unions and classes with an argumentless primary constructor.
  • For classes, only properties (getters) will be serialized. Properties must also have a public setter for correct deserialization ([<CLIMutable>] doesn't work).
  • Arrays will always be deserialized as dynamic JS arrays, not typed arrays.
  • Not compatible with atttibutes like [<JsonIgnore>].
  • Fable doesn't include the same type information as Json.NET (generic arguments and assembly name are missing), so you need to specify the target type for proper deserialization on the server side.
  • DateTime will always be serialized in UTC format and any string matching the DateTime ISO Format will be deserialized as a string.
  • Be aware when using type attrbitues like [<PoJo>] that they are applied at both sides serialising and deserialising. Otherwise you will see unexpected runtime behaviour.
  • Json.NET doesn't serialize type info for F# unions even when using the TypeNameHandling.All setting, but Fable.JsonConverter overcomes this limitation.

Fable.JsonConverter:

You can install directly via nuget by simply adding it as a dependency to you paket.dependencies file:

1: 
nuget Fable.JsonConverter

Now you can use the converter directly in your code by passing it to the JsonConvert.SerializeObject method.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
open Newtonsoft.Json

let jsonConverter = Fable.JsonConverter() :> JsonConverter

let toJson value =
    JsonConvert.SerializeObject(value, [|jsonConverter|])

type U = A of int | B of string * float

let a = A 4
toJson a

let b = B("hi",5.6)
toJson b

Publishing a Fable package

See fable-helpers-sample to know how to publish a Fable package.

namespace Microsoft.FSharp.Core
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val pname : string

Full name: interacting.pname
val result : obj

Full name: interacting.result
val chart : 'a (requires member ( ? ) and member ( ? ))
val d : 'a
val log : value:'T -> 'T (requires member Log)

Full name: Microsoft.FSharp.Core.Operators.log
union case Option.None: Option<'T>
Multiple items
val float : value:'T -> float (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.float

--------------------
type float = System.Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
type obj = System.Object

Full name: Microsoft.FSharp.Core.obj
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
module String

from Microsoft.FSharp.Core
Multiple items
type CompiledNameAttribute =
  inherit Attribute
  new : compiledName:string -> CompiledNameAttribute
  member CompiledName : string

Full name: Microsoft.FSharp.Core.CompiledNameAttribute

--------------------
new : compiledName:string -> CompiledNameAttribute
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
namespace System
Multiple items
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'T14,'T15,'T16,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 * 'T14 * 'T15 * 'T16 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'T14,'T15,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 * 'T14 * 'T15 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'T14,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 * 'T14 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'T13,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 * 'T13 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'T12,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 * 'T12 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'T11,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 * 'T11 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'T10,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 * 'T10 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'T9,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 * 'T9 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'T8,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 * 'T8 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'T7,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 * 'T7 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'T6,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 * 'T6 -> 'TResult

Full name: System.Func<_,_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'T5,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 * 'T5 -> 'TResult

Full name: System.Func<_,_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'T4,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 * 'T4 -> 'TResult

Full name: System.Func<_,_,_,_,_>

--------------------
type Func<'T1,'T2,'T3,'TResult> =
  delegate of 'T1 * 'T2 * 'T3 -> 'TResult

Full name: System.Func<_,_,_,_>

--------------------
type Func<'T1,'T2,'TResult> =
  delegate of 'T1 * 'T2 -> 'TResult

Full name: System.Func<_,_,_>

--------------------
type Func<'T,'TResult> =
  delegate of 'T -> 'TResult

Full name: System.Func<_,_>

--------------------
type Func<'TResult> =
  delegate of unit -> 'TResult

Full name: System.Func<_>
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val sum : source:seq<'T> -> 'T (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.Seq.sum
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
Fork me on GitHub