Quick start with Fable

This article is also published in Thai at FSharp Thai website.

Fable is F# script to Javascript compiler, just like Elm or Purescript, it is a functional language by extending F# language to be suitable with JS technology. As current writing, Fable seems to be in an experimental beta version. The community is somewhat small (from my observation in Github). Not sure if it will survive.. Anyway..

Installation

Pre-requisites: F# 4.0 and NodeJS 4.4+ (I use Node 6.4.0) must be installed in the machine in order to use fable.

Fable compiler can be installed via NodeJS.

npm install -g fable-compiler  

Quick Start

There are some project samples written with Fable. I try to summarize what you need the least in order to start a JS client side project.

Hello World with RequireJS

Create a directory and initialize Node package file.

mkdir demo && cd demo  
npm init  

Fable uses Plugin concept to extend its libraries. fable-core is a main JS client side interface library. It can be installed via npm.

core-js is a ES6-7 polyfill library, this should also be installed to ensure that code generated from Fable should be able to run in most browsers.

And of course, this sample needs requirejs.

npm install --save fable-core core-js requirejs  

Let's start with an HTML page. Create index.html at the root of project. This HTML should be served via file protocol so the path references in it are all relative.

We are going to generate JS into out sub-directory so we configure RequireJS to find needed JS in out. The Fable generated JS will refer to fable-core module, so it must be declared too.

<!doctype html>  
<html>  
<body>  
  <div id="content" />
  <script src="node_modules/core-js/client/core.min.js"></script>
  <script src="node_modules/requirejs/require.js"></script>
  <script>
    requirejs.config({
        baseUrl: 'out',  // Set the baseUrl to the path of the compiled JS code
        paths: {
            // Explicit path to core lib (relative to baseUrl, omit .js)
            'fable-core': '../node_modules/fable-core/fable-core.min'
        }
    });
    requirejs(["hello"]);
  </script>
</body>  
</html>  

Create hello.fsx (F# script) in the root of directory. We are going to put Hello world! inside the div tag content. We will also write the words in the browser's console log. Let's create hello.fsx.

#r "node_modules/fable-core/Fable.Core.dll"

open Fable.Core  
open Fable.Import

let GreetingWords = "Hello world!"

// Browser is in Fable.Import namespace.
let content = Browser.document.getElementById "content"  
content.innerHTML <- (sprintf "<h1>%s</h1>" GreetingWords)

Browser.window.console.log GreetingWords  

Unfortunately, there is no API document currently. You have to look into the library code directly to see what is available. At least they are all written in F# which I hope you are already familair :) For example, all browsers related code are in https://github.com/fable-compiler/Fable/blob/master/src/fable/Fable.Core/Import/Fable.Import.Browser.fs

To compile Fable, you can either use only command line fable or use Fable configuration file. Most examples use the config file so do I. Fable config file is named fableconfig.json.

{
    "module": "amd",
    "sourceMaps": true,
    "projFile": "./hello.fsx",
    "outDir": "out",
    "scripts": {
        "prebuild": "npm install"
    }
}

Fable can also generate CommonJS module format. This sample we use RequireJS so it is set to AMD.

Then we are ready to go. Just compile it with fable.

fable  

hello.js and hello.js.map should be generated in out directory. Open index.html with a browser to see Hello world!.

Hello world with Webpack

Krzysztof has written a blog about using Fable with Webpack. Here I use his technique with Hello world example and trim some code to show only minimum requirements.

First, install webpack if your machine does not have it yet. (sudo is needed if on *nix).

npm install -g webpack  

Create a directory and install needed components.

mkdir demo && cd demo  
npm init  
npm install --save-dev webpack source-map-loader  
npm install --save core-js fable-core  

Create index.html. It is shorter since no RequireJS setup.

<!doctype html>  
<html>  
<body>  
  <div id="content" />
  <script src="out/bundle.js"></script>
</body>  
</html>  

There is no change in hello.fsx.

#r "node_modules/fable-core/Fable.Core.dll"

open Fable.Core  
open Fable.Import

let GreetingWords = "Hello world!"

// Browser is in Fable.Import namespace.
let content = Browser.document.getElementById "content"  
content.innerHTML <- (sprintf "<h1>%s</h1>" GreetingWords)

Browser.window.console.log GreetingWords  

Create fableconfig.json. Note that we issue webpack command after building.

{
  "module": "commonjs",
  "sourceMaps": true,
  "projFile": "./hello.fsx",
  "outDir": "out",
  "scripts": {
    "prebuild": "npm install",
    "postbuild": "webpack"
  }
}

Finally, create webpack.config.js on the root of directory.

var path = require("path")  
var webpack = require("webpack")

module.exports = {  
  devtool: "source-map",
  entry: "./out/hello.js",
  output: {
    path: path.join(__dirname, "out"),
    filename: "bundle.js"
  },
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "source-map-loader"
      }
    ]
  }
}

That's it. Just run fable and open index.html in your browser.