Bringing together the F# and JS worlds
Alfonso Garcia-Caro
Ping me! @alfonsogcnunez
Working at toggl.com, the insanely simple time tracking app
Give it a try!
ES2015 adds many great features and JS development tools keep getting better and better
But there are still things some programmers miss:
F# brings this and a couple more features...
And the killer feature...
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
// Explicit
"3.14".PadLeft(10)                    // "      3.14"
"3.14".PadRight(10)                   // "3.14      "
"22".PadLeft(10, '0')                 // "0000000022"
// With .NET string formatting
String.Format("{0,10:F1}", 3.14)      // "       3.1"
String.Format("{0,-10:F1}", 3.14)     // "3.1       "
// With F# typed string formatting
sprintf "%10.1f" 3.14                 // "       3.1"
sprintf "%-10.1f" 3.14                // "3.1       "
sprintf "%+010i" 22                   // "+000000022"
This is what a day in the life of a compiler looks like:
Parse and validate text into an Abstract Syntax Tree Make necessary transformations on that AST Generate new code from the AST: assembly, bytecode, JS...As Fable works with known languages it can take advantage of existing tools.
F# compiler, like Roslyn, can be used as a service: we completed the first step for free!
Unfortunately JS is not a compiled language
...or is it?
Enter Babel
Babel generates an AST from ES2015 code, applies transformations with a pluggable system and generates ES5 JavaScript code.
Fable builds a bridge between F# and Babel AST delegating the reponsibility of code parsing and generation.
Fable adds its own AST for internal operations:
Fable can be downloaded from npm
1: 2: 3: 4: 5: 6: 7: 8:
mkdir temp cd temp npm init --yes npm install -g fable-compiler npm install --save fable-core echo "printfn \"Hello World\"" > hello.fsx fable hello.fsx node hello.js
Tailor compilation to your needs:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
{
    "module": "commonjs",
    "outDir": "out",
    "sourceMaps": true,
    "projFile": "src/Fable.Samples.React.fsproj",
    "babelPlugins": ["transform-runtime"],
    "scripts": {
        "prebuild": "npm install",
        "postbuild": "node node_modules/webpack/bin/webpack"
    },
    "targets": {
        "debug": {
            "watch": true,
            "symbols": ["DEBUG"],
            "scripts": {
                "postbuild": "node out/server"
            }
        }
    }
}
Console application
https://github.com/fsprojects/Fable/blob/master/samples/node/console/index.fs
NUnit or Visual Studio tests can be compiled to JS too
1: 2: 3: 4: 5: 6: 7: 8: 9:
#r "../../../packages/NUnit/lib/nunit.framework.dll"
#load "util/util.fs"
open NUnit.Framework
[<Test>]
let ``Util.reverse works``() =
    let res = Util.reverse "yllihP"
    Assert.AreEqual("Philly", res)
Compile the tests using NUnit plugin and run them with Mocha
1: 2: 3: 4: 5:
fable samples/node/console/tests.fsx -m commonjs
    --outDir out --plugins build/plugins/Fable.Plugins.NUnit.dll
node build/tests/node_modules/mocha/bin/mocha
    samples/node/console/out/tests.js
We don't want to just intrude the JS ecosytem, we want to take advantage of its full potential.
Because .NET community is great
F# community is fantastic
JS community is...
HUGE
...and amazing too :)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
open Fable.Core
printfn "Value: %O" jsObj?myProp    // Property access
jsObj?myProp <- 5                   // Assignment
let x = jsObj?myMethod $ (1, 2)     // Application
let y = createNew jsCons (1, 2)     // Apply `new` keyword
let data =                          // JS literal object
    createObj [
        "todos" ==> Storage.fetch()
        "newTodo" ==> ""
        "editedTodo" ==> None
        "visibility" ==> "all"
    ]
Use Emit attribute to emit JS code directly
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
open Fable.Core
[<Emit("$0 + $1")>]
let add (x: int) (y: string): float = failwith "JS only"
type Test() =
    // Rest arguments
    [<Emit("$0($1...)")>]
    member __.Invoke([<ParamArray>] args: int[]): obj =
        failwith "JS only"
    // Syntax conditioned to optional parameter
    [<Emit("$0[$1]{{=$2}}")>]
    member __.Item
        with get(): float = failwith "JS only"
        and set(v: float): unit = failwith "JS only"
Define foreign interfaces easily to get the benefits of static checking and Intellisense
1: 2: 3: 4: 5: 6: 7:
[<Import("*","string_decoder")>]
module string_decoder =
    type NodeStringDecoder =
        abstract write: buffer: Buffer -> strings
        abstract detectIncompleteChar: buffer: Buffer -> float
    let StringDecoder: NodeStringDecoder = failwith "JS only"
Use Import attribute to import external JS modules in ES2015 fashion
Node static server
https://github.com/fsprojects/Fable/blob/master/samples/node/server/index.fsx
Debugging a node express server with VS Code
1: 2: 3: 4: 5: 6: 7: 8:
{
    "name": "Launch Node",
    "type": "node",
    "request": "launch",
    "program": "${workspaceRoot}/samples/node/express/index.fsx",
    "outDir": "${workspaceRoot}/samples/node/express/out",
    "sourceMaps": true,
}
Front end applications with JS frameworks: Vue, React
https://github.com/fsprojects/Fable/tree/master/samples/browser/todomvc
https://github.com/fsprojects/Fable/tree/master/samples/browser/react
(1) CMUF: Completely made up figures
Questions?
https://github.com/fsprojects/Fable
@alfonsogcnunez