Writing rich web apps with Fable

This post is the third part in a short series on programming web-enabled, functional-friendly applications in F# and has kindly been contributed with help from Alfonso Garcia-Caro. Thanks, Alfonso!

Introduction

This article provides a short introduction to the Fable project, a powerful library that allows F# code to run wherever Javascript does.

The rise of the web developer

When it comes to client side development on the .NET stack, the approach that comes to mind for most people is Windows Forms or WPF, or perhaps Xamarin for a mobile application. Whilst these programming models have served the test of time, they're not the easiest development tools to use and there isn't a single cross platform approach to developing user interfaces. Thanks to advances with web browser technology, it's now possible to write applications which run across multiple devices within the web browser, whilst still achieving all of the functionality of a native application. For example, web browsers are now able to play back audio and video content, render 2D and 3D graphics through the GPU or even perform cryptographic tasks, all without the need for third-party plugins.

This continued development of web browsers has lead to a surge in usage of JavaScript in recent years as the language has evolved to make it easier to develop larger applications. This increase in usage has lead to a diverse range of libraries and tooling to flourish within the JavaScript ecosystem.

Why not Javascript?

Unfortunately for many developers JavaScript remains the only way to develop interactive websites, and whilst many developers enjoy using JavaScript, it presents a number of significant features which for an F# developer can be quite jarring. Some of the aspects of JavaScript which make it a challenging language to use include:

  • No type safety. Whereas F# has a strong type system which prevents us from making mistakes both rudimentary and complex, JavaScript instead relies on dynamic typing which means we don't have a formal contract for what types functions operate on. Indeed, even at runtime, JavaScript will permit you to perform operations that would be considered "illegal" at compile-time in F#, such as assigning a value to a non-existent field on an object (see below). Additionally, the lack of a type system also prevents automated refactoring tools such as renaming a variable.
1: 
2: 
3: 
var car = {type:"Fiat", model:"500", color:"white"};
car.typo = "foo"; // car now has a new property, "typo". no exception.
var typo = car.typos // typo = undefined. no exception.
  • Mutable by default. JavaScript prioritises the mutation of variables rather than the creation of fixed values which make it easier to inadvertently manipulate state across applications. When dealing with a large application with lots of moving parts, mutability can make debugging significantly more difficult.

  • Unfamiliarity. Approaching a new language is no easy task, requiring an understanding of the nuances of the underlying language design. This can frustrate developers if expectations of the language don't match reality, and JavaScript is no different with a number of well-known quirks with the language and standard library design. Here, we perform a number of simple checks in Javascript which appear to be contradictory, yet are completely legal and features within the language.

1: 
2: 
3: 
4: 
5: 
typeof NaN // number
NaN == NaN // false!
typeof null // object
null instanceof Object // false!
"string" instanceof String // false!
  • Limited scope for reuse By using JavaScript on the front end and F# on the back end, it increases the amount of code we have to maintain. For example, we may end up duplicating much of our domain code when calling a web API, with a model on the client written in JavaScript and a model on the server written in F#. When that API contract changes, we'll need to update both instances of the model in two different languages, each with completely different mechanisms for domain modelling.

Introducing Fable

In an ideal world, we'd want to be able to develop browser-based applications in any language we choose to, much like we do when developing desktop and server applications. This is where the open-source project Fable comes in:

an F# to JavaScript compiler powered by Babel, designed to produce readable and standard code.

The Babel project

In large part thanks to the Babel project, there has been significant work on providing libraries which allow us to interactively work with a JavaScript codebase through an abtract syntax tree (AST). With Babel, it's possible to generate a JavaScript AST and then emit high quality JavaScript code similar to what a human would write. Fable builds on top of Babel by allowing us to take an F# codebase using all the features of F# which we'd typically use and turn this into JavaScript AST.

By using Fable, we're able to write applications which run on JavaScript runtimes using F# by transpiling our F# code into JavaScript code instead. This opens up a whole new range of potential execution environments for F# code and allows us to target JavaScript as a platform in addition to the existing options of Mono, .NET Core and the full .NET framework.

Bridging the gap between .NET and JavaScript

But Fable is more than simply a library that cross-compiles into Javascript - it behaves as a first-class citizen of both the F# world in .NET and in the Javascript world. So F# users can use core types in the BCL seamlessly - Fable will translate them to appropriate Javascript calls (and you can easily plug in your own mappers where needed). On the other hand, Fable doesn't ignore the Javascript ecosystem. It plays nicely with NPM packages and Typescript definitions, has no runtime overhead (indeed, it can perform specific JS optimisations) and - importantly - creates easy-to-read Javascript.

Working with the DOM in F#

The ability to interactively manipulate a website's content through the use of the DOM still remains the most common use case for JavaScript. Fable exists as more than just an F#-to-JavaScript transpiler and also provides a standard library which lets us interact with the browser through the web browser's JavaScript API.

One of the APIs provided by all modern browsers is the canvas API which allows us to draw shapes and graphics at arbitrary points on the canvas. This makes it ideal for creating more rich experiences like games. Whilst we won't create a full game here, we will see how simple it is to draw shapes in the browser with Fable.

Assuming we already have a HTML page in place with a canvas on it, Fable allows us to easily write F# code which will retrieve a reference to the canvas and modify its size. The code then retrieves the canvas context which is what allows us to draw shapes on the canvas. Once we've got the canvas context, we're able to draw a number of rectangles with different colours.

Thanks to the F# type system, we're able to ensure at compile time that we're calling the JavaScript APIs with the correct arguments. For example, when setting the size of the canvas, we have to use a float - if we don't, then the code will fail to compile rather than failing at runtime (or even worse, simply carrying on with the incorrect values).

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
module FableApp

open Fable.Core
open Fable.Core.JsInterop
open Fable.Import

let init() =
    let canvas = Browser.document.getElementsByTagName_canvas().[0]
    canvas.width <- 1000.
    canvas.height <- 800.
    let ctx = canvas.getContext_2d()
    ctx.fillStyle <- !^"rgb(200,0,0)"
    ctx.fillRect (10., 10., 55., 50.)
    ctx.fillStyle <- !^"rgba(0, 0, 200, 0.5)"
    ctx.fillRect (30., 30., 55., 50.)

init()

If you're interested in seeing more, there's a complete set of samples on available here.

Developing back end services on Node with Fable

As part of the JavaScript community's effort to increase adoption of the language, it's possible to run JavaScript-based applications on the server thanks to the Node.JS project. Since Node.JS applications are simply JavaScript, we're able to use Fable here, too. Whilst it may seem strange to transpile F# to JavaScript to run server or desktop applications, it opens up a number of new scenarios for F# developers such as: - Extending existing SaaS applications. Where webhooks were once the realm of SaaS application configuration, more and more organisations are starting to allow JavaScript to be executed within a sandbox in response to certain events. For example, Azure Stream Analytics allows User Defined Functions to be written in JavaScript and then consumed from within the SQL query. Auth0 allow developers to tailor the login flow as users login to the Auth0 identity platform. - Serverless hosting. As the serverless mindset continues to grow, more cloud providers are operating platforms for serverless execution. With the likes of IBM, Google and others providing serverless execution environments, all of these have common support for JavaScript applications. By compiling an F# application to JavaScript, it's able to operate on any of these serverless platforms. - Embedded devices. Due to the prevelance of JavaScript, a number of Internet of Things platforms have been released which allow for the development of IoT devices using JavaScript APIs. With Fable, we're able to write code in F# which is then deployed onto these devices.

By using the Fable compiler, we're able to run F# on devices and services we might not have previously been able to target. In the example below, we can see how we're able to write a short web service which can be run on a Node.JS server.

Summary

In this post, we looked at what the Fable compiler is and how we can use it to generate idiomatic JavaScript directly from F# code. We saw how this allows us to write F# which interacts with a web browser in order to draw shapes but we also saw how thanks to the compilation to JavaScript, we're able to get F# running on more platforms and services than previously possible. You can try Fable out in your browser right now, at http://fable.io/repl!

namespace Microsoft.FSharp.Core