F# Guide — Fable: Compiling F# to JavaScript
In this tutorial, you will learn about F# Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Fable is a compiler that translates F# code to JavaScript, enabling .NET developers to write browser applications with F#'s type system, with seamless React integration through Elmish.
What You'll Learn
- Setting up Fable projects
- F# to JavaScript compilation
- React integration with Elmish
- Interop with JavaScript libraries
- Bundle optimization
Why It Matters
Fable lets F# developers target the browser without learning JavaScript. Shared types between server and client reduce duplication. Doda Browser uses Fable for extension development.
Real-World Use
Full-stack F# applications with SAFE stack (Saturn, Azure, Fable, Elmish), React dashboards, and Node.js services.
flowchart LR
A["Fable"] --> B["Setup"]
B --> C["Compilation"]
C --> D["Elmish"]
D --> E["Deployment"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Project Setup
# Install Fable template
dotnet new install Fable.Template
dotnet new fable -n MyApp
cd MyApp
npm install
npm start
Basic F# to JavaScript
module App
open Browser
open Browser.Dom
let greet name =
document.getElementById("root").innerText <- sprintf "Hello, %s!" name
greet "Fable"
Elmish Architecture
module App
open Elmish
open Elmish.React
type Model = { Count: int }
type Msg =
| Increment
| Decrement
| Reset
let init() = { Count = 0 }
let update msg model =
match msg with
| Increment -> { model with Count = model.Count + 1 }
| Decrement -> { model with Count = model.Count - 1 }
| Reset -> { model with Count = 0 }
let view model dispatch =
Html.div [
Html.h1 [ prop.text (sprintf "Count: %d" model.Count) ]
Html.button [ prop.text "+"; prop.onClick (fun _ -> dispatch Increment) ]
Html.button [ prop.text "-"; prop.onClick (fun _ -> dispatch Decrement) ]
Html.button [ prop.text "Reset"; prop.onClick (fun _ -> dispatch Reset) ]
]
Program.mkSimple init update view
|> Program.withReactSynchronous "root"
|> Program.run
JavaScript Interop
open Fable.Core
open Fable.Core.JsInterop
// Import JavaScript module
[<Import("default", "moment")>]
let moment: obj = jsNative
// Call JavaScript function
[<Emit("console.log($0)")>]
let consoleLog (x: obj) : unit = jsNative
// Use JavaScript object
let date = moment?().format("YYYY-MM-DD")
consoleLog date
Promises
open Fable.Core.JS
let fetchData url = promise {
let! response = fetch url []
let! json = response.text()
return json
}
// Usage
let loadData() = promise {
let! data = fetchData "http://api.example.com/data"
console.log(data)
}
Common Mistakes
1. Using .NET-specific APIs
Not all .NET APIs are available. Browser APIs replace System.IO, System.Net, etc.
2. Forgetting JS interop patterns
Use [<Import>], [<Emit>], and jsNative for JavaScript interop.
3. Large bundle sizes
Fable generates optimized JS but be mindful of imports. Use tree-shaking.
4. Async confusion
Fable uses JavaScript Promises. Use promise { } instead of async { }.
5. Type mismatches
JavaScript types are dynamic at runtime. Use Fable's type mapping for correct interop.
Practice Questions
1. What is Fable? A compiler that translates F# code to JavaScript for browser and Node.js targets.
2. What is Elmish? A UI framework for Fable implementing the Model-View-Update architecture, similar to Elm.
3. How do you call JavaScript from Fable?
Use [<Import>], [<Emit>], or the jsNative placeholder for JS interop.
Challenge: Build a simple counter application with Fable and Elmish.
FAQ
{{< faq question="Is Fable production-ready?" >} Yes. Fable v4+ is stable and used in production. Many companies use Fable for their frontend applications. {{< /faq >}}
{{< faq question="Can I use React components from Fable?" >} Yes. Elmish.React renders React components. You can also use Feliz for a more React-like syntax. {{< /faq >}}
{{< faq question="How does Fable compare to TypeScript?" >} Fable uses F#'s type system which is more expressive than TypeScript. It supports discriminated unions, type providers, and computation expressions. {{< /faq >}}
{{< faq question="Can I use npm packages with Fable?" >}
Yes. Fable projects use npm for JavaScript dependencies. Use [<Import>] to import JS modules.
{{< /faq >}}
{{< faq question="What is the SAFE stack?" >} A full-stack F# framework: Saturn (server), Azure (cloud), Fable (client), Elmish (UI). All in F#. {{< /faq >}}
Mini Project
Build a simple counter with Fable and Elmish:
module Counter
open Elmish
open Elmish.React
type Model = { Value: int }
type Msg = Increment | Decrement
let init() = { Value = 0 }
let update msg model =
match msg with
| Increment -> { model with Value = model.Value + 1 }
| Decrement -> { model with Value = model.Value - 1 }
let view model dispatch =
Html.div [
Html.h1 [ prop.text (string model.Value) ]
Html.button [ prop.text "+"; prop.onClick (fun _ -> dispatch Increment) ]
Html.button [ prop.text "-"; prop.onClick (fun _ -> dispatch Decrement) ]
]
Program.mkSimple init update view
|> Program.withReactSynchronous "app"
|> Program.run
What's Next
Now that you understand Fable, explore Paket for .NET package management.
| Topic | Description | Link |
|---|---|---|
| F# Paket | Package management | {{< ref "29-paket" >}} |
| F# Common Libraries | Ecosystem overview | {{< ref "30-common-libraries" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro