Compiling to JavaScript

Using Fable as part of your node applications

You can install the fable-compiler package from npm either locally or globally. Here we'll assume it's been installed globally so the fable command is available from any directory.

The simplest way to compile a F# project (.fsproj) or script (.fsx) is to pass its path as an argument to fable:

1: 
2: 
3: 
npm install -g fable-compiler

fable path/to/your/project.fsproj

CLI options

Besides the default argument (--projFile), the following options are available:

Option

Short

Description

--outDir

-o

Where to put compiled JS files. Defaults to project directory.

--module

-m

Specify module code generation: umd (default), commonjs, amd or es2015.

--sourceMaps

-s

Generate source maps: false (default), true or inline.

--watch

-w

Recompile project much faster on file modifications.

--ecma

Specify ECMAScript target version: es5 (default) or es2015.

--symbols

F# symbols for conditional compilation, like DEBUG.

--plugins

Paths to Fable plugins.

--babelPlugins

Additional Babel plugins (without babel-plugin- prefix). Must be installed in the current directory.

--refs

Specify project references in Project=js/import/path format (see below)

--clamp

Compile unsigned byte arrays as Uint8ClampedArray.

--copyExt

Copy external files into a .fable.external folder.

--target

-t

Use options from a specific target in fableconfig.json.

--debug

-d

Shortcut for --target debug.

--production

-p

Shortcut for --target production.

--code

Pass a string of code directly to Fable.

--help

-h

Display usage guide.

Project references

You can use --refs argument to link referenced projects with the JS import path that must be used, using the following format: [Project name without extension]=[JS import path].

Example:

1: 
2: 
3: 
4: 
5: 
6: 
fable src/lib/MyLib.fsproj --outDir out/lib
fable src/another/MyNs.AnotherProject.fsproj
  --outDir out/another
fable src/main/MyProject.fsproj
  --outDir out/main
  --refs MyLib=../lib MyNs.AnotherProject=../another

fableconfig.json

Rather than passing all the options to the CLI, it may be more convenient to put them in JSON format in a file named fableconfig.json and let the compiler read them for you. You can combine options from the CLI and fableconfig.json, when this happens the former will have preference.

To use fableconfig.json just pass the directory where the JSON file resides to Fable. If you omit the path, the compiler will assume it's in the current directory.

1: 
fable my/path/

Project references can be passed using a plain object:

1: 
2: 
3: 
4: 
5: 
6: 
{
  "refs": {
    "MyLib": "../lib",
    "MyNs.AnotherProject": "../another"
  }
}

There are some options exclusive to fableconfig.json.

  • scripts: Commands that should be executed during specific phases of compilation. Currently prebuild, postbuild and onwatch are accepted. For example, if you want to run tests defined in the npm package.json file after the build you can write.
1: 
2: 
3: 
4: 
5: 
{
    "scripts": {
        "postbuild": "npm run test"
    }
}
  • targets: You can group different options in targets. If you don't want, say, source maps when deploying for production, you can use a config file as seen below. When a target is specified, the options in the target will override the default ones. Activate the target by passing it to the CLI: fable --target production.
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
{
    "sourceMaps": true,
    "targets": {
        "production": {
            "sourceMaps": false
        }
    }
}

When using a node package.json file, it's also possible to specify the minimum version of Fable required to compile the project.

1: 
2: 
3: 
4: 
5: 
{
    "engines": {
        "fable": "0.1.3"
    }
}

fable-core

Fable's core library must be included in the project. When targeting node or using a module bundler you only need to add the dependency:

1: 
npm install --save fable-core

If targeting the browser and using AMD instead, you can load Fable's core lib with require.js as follows:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
<script src="node_modules/requirejs/require.js"></script>
<script>
requirejs.config({
    // Set the baseUrl to the path of the compiled JS code
    baseUrl: 'out',
    paths: {
        // Explicit path to core lib (relative to baseUrl, omit .js)
        'fable-core': '../node_modules/fable-core/fable-core.min'
    }
});
// Load the entry file of the app (use array, omit .js)
requirejs(["app"]);
</script>

Polyfill

When not using --ecma es2015 or --module es2015 (see below), after going through Babel pipeline the code won't include any syntax foreign to ES5. However several ES2015 classes (like Symbol) are used so it's advisable to include a polyfill like core-js to make sure the code works fine in any browser.

You can include the polyfill in a script tag in your HTML file before loading the generated JS code like:

1: 
<script src="node_modules/core-js/client/core.min.js"></script>

Or you can import it directly in your F# code if you're using a bundler like Webpack or Browserify right before the entry point of your app.

1: 
Node.require.Invoke("core-js") |> ignore

The polyfill is not necessary when targeting node 4.4 or above. Babel includes its own polyfill with a lazy-sequence generator, but this is not needed as one is already included in [fable-core.js]https://github.com/fsprojects/Fable/blob/master/import/core/fable-core.js).

Modules

The compiler will keep the file structure of the F# project, wrapping each file in a ES2015 module.

According to the --module argument (see above), these modules can be transformed again by Babel to umd (the default), amd, commonjs, or not at all.

In the browser, when not using a bundler like Webpack or Browserify, you'll need a module loader like require.js to start up the app.

When a F# file makes a reference to another, the compiler will create an import statement in the generated Javascript code. You can also generate imports by using the Import attribute.

As JS must import external modules with an alias, there's no risk of namespace collision so, for convenience, the compiler will use the minimum route to access external objects. Meaning that if you have a F# file with one root module:

1: 
2: 
3: 
module MyNs1.MyNs2.MyModule

let myProperty = "Hello"

To access myProperty the generated code will import the file with an alias, say $import0, and directly access the property from it: $import0.myProperty. The route has been eluded as it's not necessary to prevent name conflicts. In the same way, if you have a file with two modules:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
namespace MyNs1.MyNs2

module MyModule1 =
    let myProperty = "Hello"

module MyModule2 =
    let myProperty = "Bye"

This time the compiler will omit the namespace but keep the F# module names, as they're necessary to prevent name conflicts in the same file:

1: 
$import0.MyModule1.myProperty !== $import0.MyModule2.myProperty

Debugging

You can debug the generated JS code normally. Also, if you pass the sourceMaps option to the compiler, it'll be possible to debug the F# code (with some limitations). This is automatic for browser apps. For node, you'll need a tool like node-inspector or a capable IDE. In the case of Visual Studio Code, you can find instructions here (see Node Debugging > JavaScript Source Maps).

Testing

You can use any JS testing library to write tests for your project, but to make it easier to share code across platforms, a plugin is available to make NUnit tests compatible with Mocha and this is what Fable uses for its own tests. The tests are compiled and run automatically when building the project:

1: 
2: 
build.cmd   // on windows
./build.sh  // on unix

The most commonly used attributes (TestFixture and Test) and their respective SetUp/TearDown counterparts are implemented. For assertions, however, only Assert.AreEqual is available. But more features will be available soon.

Note: As attributes are only read by name, it's possible to use custom-defined attributes without the NUnit dependency if needed.

For Visual Studio users, there's a similar plugin to convert Visual Studio Unit Tests to Mocha.

Samples

There are some samples available in the repository and you can also download them from here. Every sample includes a fableconfig.json file so they can be compiled just by running the fable command in the sample directory. Just be sure to install the npm dependencies the first time.

1: 
2: 
npm install
fable

Now it's your turn to build a great app with Fable and show it to the world! Check Compatibility and Interacting with JavaScript to learn what you need to take into account when diving into JS.

val ignore : value:'T -> unit

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