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