Opinionated Fable - Architecture & Performance
Explore Fable architecture & performance best practices. Learn static typing benefits, Elmish patterns, and scalable frontend design for production applications.
Opinionated Fable - Architecture & Performance
There couldn't be a better time for writing this blog. As Fable 2.0 is on the horizon, it would be good to see what 1.0 already can do. And I can also wish a few things to be added to 2.0.
I have chosen Fable after evaluating a couple of options like TypeScript-React-Redux, Elm, Aurelia, and Angular. One thing I was pretty much sure about was that I wanted static typing in my project. Because I did have first-hand experience with a big fat Angular application built using JavaScript.
In that project, changing things and adding features was just so damn painful. It's like if we press a key on the keyboard, there's a chance that we may break the application. It was already in production for 3+ years when I joined the team, and still no one knows what is working and why it is working. It's damn painful to join a brown field project specifically written in JavaScript. On top of it, top-level management expects that we learn everything in the minimum possible time, using Chrome Debugger.

I did it anyway, and also converted the whole project to TypeScript. It helped me to understand the importance of good code and architecture even for Front End. Personally, I feel that Front End Development needs more discipline than back end development. In back end code, we can throw one more machine to save our poorly performing code, but for front end—Chrome will just kill our machine for good if we write some crazy stuff.
Enough of JavaScript/Front End bashing. Let's talk about Fable.
Here everything is explained in context of a big fat application. So, we might not need all of it at the start, but it's always good to know.
Architecture
Elmish in Single Page
When we start a new application with Elmish using the template, it will create a couple of pages for us in the UI. In code, all pages are divided into folders with three files: Type.fs, State.fs, and View.fs for that page.
It is a very good design if we are starting our application. But soon enough we will be in the middle of too many files to handle. And since namespaces are available for us, we can easily separate them by features. Here is a simple example:
namespace Home
module Types =
type Model = string
type Msg =
| ChangeStr of string
module State =
open Elmish
open Types
open Fable.Import.izitoast
open Fable.Core.JsInterop
let init () : Model * Cmd<Msg> =
iziToast.info(jsOptions<IziToastSettings>(fun x -> x.message <- "Home Initializing...") ) |> ignore
"", []
let update msg model : Model * Cmd<Msg> =
match msg with
| ChangeStr str ->
str, []
module View =
open Fable.Core.JsInterop
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Types
let root model dispatch =
div[ ][
p[ClassName "control" ][
input[ ClassName "input";
Type "text";
Placeholder "Type your name here";
DefaultValue model;
AutoFocus true;
OnChange (fun ev -> !!ev.target?value |> ChangeStr |> dispatch ) ]
]
br [ ]
span[ ][ str (sprintf "Hello %s" model) ]
]
Here Home is a page or component, and instead of having three different files for three different modules, we are having a single file for three different modules.
Now why bother doing this?
I have a folder called Pages. Having all the different pages and components of the application in it. Even though I am using the above style currently, I am having 68 files and 32 folders and counting. Now, if I was going with the default, it would be 68 X 3 = 204 files to manage at minimum. Here I am not counting all the helpers and extras we normally write in our application. All files are having three modules that are required for a page or component: Type, State, and View. Also, our code in a single file gets clumsy sooner than expected. So, that is an indication of breaking down the code and cleaning up our stuff.
Imports
My favorite thing from the Fable Universe is ts2fable. It generates F# code for TypeScript Definition files. It makes things way easier to work with. And trust me, we will be using this tool very frequently. So, it would always be a good idea to have an import folder for all of this—a top-level folder that we normally don't touch other than updating the library.
Common Helpers
Another beneficial thing about using Fable is that we can share code between server and client. Obviously, if both are written with the highest-paying F# language. It is very tempting to go full blown and exploit whatever we can. It might not affect the server side of the code, but remember at the end of the day JavaScript is running on the client side. So, personal advice: go one way from client to server instead of the other way around. Write for the client side and use it for the server side. And do keep a tap on the code we are using on the client. Sometimes I just write code on the server and use it on the client. It works most of the time but can get cranky on the client side. I'll talk about this in a later post.
Performance
Line of Code
There was a time when the billing amount was based on LOC. Even now I do find people time and again who just ask:
Q. You just wrote 4 lines of code, seems simple? Why did it take 4 hours to write?
A. It is 4 lines of simple code that is the reason why it took 4 hours to write.
The same goes for JavaScript code, literally. Less code would be great in the case of front end. People are trying way hard to generate less code. And as developers, we should help other developers by writing better code. We should always try to generate as little code as possible to make our application better and faster.
F# Types
F# types are the best way to represent any model. And kind of a defacto way when using Fable Elmish Model.
Let's take a simple example:
type InfoModel = {
FirstName : string
LastName : string
DOB : string
Gender : string
IsValid : bool
}
PS: I'm using Fable REPL for testing out the code. It is a great way you can see what our F# code is doing. It will generate hell pretty JavaScript.
Let's see the generated JavaScript:
import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import { compareRecords, equalsRecords } from "fable-core/Util";
export class InfoModel {
constructor(firstName, lastName, dOB, gender, isValid) {
this.FirstName = firstName;
this.LastName = lastName;
this.DOB = dOB;
this.Gender = gender;
this.IsValid = isValid;
}
[_Symbol.reflection]() {
return {
type: "Test.InfoModel",
interfaces: ["FSharpRecord", "System.IEquatable", "System.IComparable"],
properties: {
FirstName: "string",
LastName: "string",
DOB: "string",
Gender: "string",
IsValid: "boolean"
}
};
}
Equals(other) {
return equalsRecords(this, other);
}
CompareTo(other) {
return compareRecords(this, other) | 0;
}
}
setType("Test.InfoModel", InfoModel);
That seems like a lot of code for five lines. OK, let it be like that. Let's add some more code in there.
type InfoModel = {
FirstName : string
LastName : string
DOB : string
Gender : string
IsValid : bool
} with
static member Empty = {
FirstName = "Don"
LastName = "Syme"
DOB = "unknown"
Gender = "male"
IsValid = true
}
A typical F# way of writing a Record Type or Model.
And here is the JavaScript code:
import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import { compareRecords, equalsRecords } from "fable-core/Util";
export class InfoModel {
constructor(firstName, lastName, dOB, gender, isValid) {
this.FirstName = firstName;
this.LastName = lastName;
this.DOB = dOB;
this.Gender = gender;
this.IsValid = isValid;
}
[_Symbol.reflection]() {
return {
type: "Test.InfoModel",
interfaces: ["FSharpRecord", "System.IEquatable", "System.IComparable"],
properties: {
FirstName: "string",
LastName: "string",
DOB: "string",
Gender: "string",
IsValid: "boolean"
}
};
}
Equals(other) {
return equalsRecords(this, other);
}
CompareTo(other) {
return compareRecords(this, other) | 0;
}
static get Empty() {
return new InfoModel("Don", "Syme", "unknown", "male", true);
}
}
setType("Test.InfoModel", InfoModel);
So here are three imports (which are going to be part of the last single minified JavaScript file), and a pretty long JavaScript code. All the code is required to support everything that F# Type has to offer. But now the question is: do we need all of it? In this specific case, when representing our page with a model, we may not require the things we normally require when using an F# Type.
So here we can use Fable's magic [<Pojo>] and see what it can do to our JavaScript code.
open FSharp.Core
open Fable.Core
[<Pojo>]
type InfoModel = {
FirstName : string
LastName : string
DOB : string
Gender : string
IsValid : bool
}
module InfoModel =
let Empty = {
FirstName = "Don"
LastName = "Syme"
DOB = "unknown"
Gender = "male"
IsValid = true
}
In the above code, usage will not change. Even InfoModel.Empty will work as it is. How about JavaScript? Does it change?
export const InfoModelModule = function (__exports) {
const Empty = __exports.Empty = {
FirstName: "Don",
LastName: "Syme",
DOB: "unknown",
Gender: "male",
IsValid: true
};
return __exports;
}({});
Less code than what we wrote using F#. And people say F# is a concise language. Fable is doing an awesome job generating concise JavaScript.
Fable 2.0 Wish: I wish all types should be
Pojoby default. And if someone likes to use F# types, then they can use it with the help of an attribute, something likeNoPojo.
Lenses and Spectacles

The above InfoModel is perfect to represent a view. But what about validation? It is not holding any property to hold domain details. So, let's write that and see the generated code.
open FSharp.Core
open Fable.Core
[<Pojo>]
type Validate = {
IsValid : bool
ErrMsg : string
}
module Validate =
let Success = {
IsValid = true
ErrMsg = ""
}
let Failure (msg : string) = {
IsValid = false
ErrMsg = msg
}
let InitialValidate = {
IsValid = true
ErrMsg = ""
}
[<Pojo>]
type InfoModel = {
FirstName : string
LastName : string
DOB : string
Gender : string
IsValid : bool
}
module InfoModel =
let Empty = {
FirstName = "Don"
LastName = "Syme"
DOB = "unknown"
Gender = "male"
IsValid = true
}
[<Pojo>]
type InfoErrorModel = {
FirstName : Validate
LastName : Validate
DOB : Validate
Gender : Validate
}
module InfoErrorModel =
let Empty = {
FirstName = Validate.InitialValidate
LastName = Validate.InitialValidate
DOB = Validate.InitialValidate
Gender = Validate.InitialValidate
}
[<Pojo>]
type Model = {
InfoModel : InfoModel
InfoErrorModel : InfoErrorModel
}
module Model =
let Empty = {
InfoModel = InfoModel.Empty
InfoErrorModel = InfoErrorModel.Empty
}
As we can see, it is a very typical domain-modelish code. Typical F# - DDD 101 thing. I will explain the domain model in a separate section, but let's first see the generated JavaScript code.
export const ValidateModule = function (__exports) {
const Success = __exports.Success = {
IsValid: true,
ErrMsg: ""
};
const Failure = __exports.Failure = function (msg) {
return {
IsValid: false,
ErrMsg: msg
};
};
const InitialValidate = __exports.InitialValidate = {
IsValid: true,
ErrMsg: ""
};
return __exports;
}({});
export const InfoModelModule = function (__exports) {
const Empty = __exports.Empty = {
FirstName: "Don",
LastName: "Syme",
DOB: "unknown",
Gender: "male",
IsValid: true
};
return __exports;
}({});
export const InfoErrorModelModule = function (__exports) {
const Empty_1 = __exports.Empty = {
FirstName: ValidateModule.InitialValidate,
LastName: ValidateModule.InitialValidate,
DOB: ValidateModule.InitialValidate,
Gender: ValidateModule.InitialValidate
};
return __exports;
}({});
export const ModelModule = function (__exports) {
const Empty_2 = __exports.Empty = {
InfoModel: InfoModelModule.Empty,
InfoErrorModel: InfoErrorModelModule.Empty
};
return __exports;
}({});
Yes, it is less than the F# code. It seems Pojo is working hard for us. And the F# code also seems to be correct. Simple enough to represent a View and Complex enough to hold the validation output and message.
But as the saying goes, The devil is in the detail. Even though the model is quite simple, it is tough to update it. It is an immutable record type, so we need to create a new record every time we try to update it. As the validation properties are three levels deep, there is not an easy way to update it.
Luckily, it is an old problem in the functional programming world. So, we do have libraries like aether that provide support for something called Lenses. Lenses are used to update immutable record types. To do that, we need to add a few more methods to all the model modules and include a library full of supporting functions. Too much code needs to be added just to update an error message. You can check aether examples for details.
I used aether for around 15 days. And I did like it. But instead of making things better, it was making things more complex in the context of UI (front end). At the end of the day, I needed my model to represent my UI. So, I chose another route instead.
open FSharp.Core
open Fable.Core
[<Pojo>]
type Validate = {
IsValid : bool
ErrMsg : string
}
module Validate =
let Success = {
IsValid = true
ErrMsg = ""
}
let Failure (msg : string) = {
IsValid = false
ErrMsg = msg
}
let InitialValidate = {
IsValid = true
ErrMsg = ""
}
[<Pojo>]
type InfoModel = {
FirstName : string
FirstNameErr : Validate
LastName : string
LastNameErr : Validate
DOB : string
DOBErr : Validate
Gender : string
GenderErr : Validate
IsValid : bool
}
module InfoModel =
let Initial = {
FirstName = "Don"
FirstNameErr = Validate.InitialValidate
LastName = "Syme"
LastNameErr = Validate.InitialValidate
DOB = "unknown"
DOBErr = Validate.InitialValidate
Gender = "male"
GenderErr = Validate.InitialValidate
IsValid = true
}
[<Pojo>]
type Model = {
InfoModel : InfoModel
}
module Model =
let Empty = {
InfoModel = InfoModel.Initial
}
I have just flattened the code here. It might not be appropriate from a Functional Programming standard, but it is easier to update and bind with the View. Single-level depth, so no more craziness to update it.
Here is the generated JavaScript code:
export const ValidateModule = function (__exports) {
const Success = __exports.Success = {
IsValid: true,
ErrMsg: ""
};
const Failure = __exports.Failure = function (msg) {
return {
IsValid: false,
ErrMsg: msg
};
};
const InitialValidate = __exports.InitialValidate = {
IsValid: true,
ErrMsg: ""
};
return __exports;
}({});
export const InfoModelModule = function (__exports) {
const Initial = __exports.Initial = {
FirstName: "Don",
FirstNameErr: ValidateModule.InitialValidate,
LastName: "Syme",
LastNameErr: ValidateModule.InitialValidate,
DOB: "unknown",
DOBErr: ValidateModule.InitialValidate,
Gender: "male",
GenderErr: ValidateModule.InitialValidate,
IsValid: true
};
return __exports;
}({});
export const ModelModule = function (__exports) {
const Empty = __exports.Empty = {
InfoModel: InfoModelModule.Initial
};
return __exports;
}({});
Pretty small and sweet. Moral of the story is: we can and should avoid Lenses when writing our code the Elmish way. There are more reasons hidden in the sections below.
Domain Domain Domain

Just like everyone in the Functional Domain, I am a big fan of Domain Driven Design and Scott W. It makes things easier to represent. What is the use of Fable if I can't use it on the front end? Let's give it a try.
Simple string testing:
type Name private(s : string) =
member __.Name = s
static member Create(s: string) =
if (s <> "" || s <> null) then Ok s
else Error "Invalid String"
converts to:
import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import Result from "fable-core/Result";
export class Name {
[_Symbol.reflection]() {
return {
type: "Test.Name",
properties: {
Name: "string"
}
};
}
constructor(s) {
this.s = s;
}
get Name() {
return this.s;
}
static Create(s) {
if (s !== "" ? true : s != null) {
return new Result(0, s);
} else {
return new Result(1, "Invalid String");
}
}
}
setType("Test.Name", Name);
Quite a long code. Let's try the same thing another way:
type Name = private | Name of string
with
member this.String = let (Name s) = this in s
static member Create(s : string)=
if (s <> "" || s <> null ) then Ok s
else Error "Invalid string"
converts to:
import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import { compareUnions, equals } from "fable-core/Util";
import Result from "fable-core/Result";
class Name {
constructor(tag, data) {
this.tag = tag | 0;
this.data = data;
}
[_Symbol.reflection]() {
return {
type: "Test.Name",
interfaces: ["FSharpUnion", "System.IEquatable", "System.IComparable"],
cases: [["Name", "string"]]
};
}
Equals(other) {
return this === other || this.tag === other.tag && equals(this.data, other.data);
}
CompareTo(other) {
return compareUnions(this, other) | 0;
}
get String() {
return this.data;
}
static Create(s) {
if (s !== "" ? true : s != null) {
return new Result(0, s);
} else {
return new Result(1, "Invalid string");
}
}
}
setType("Test.Name", Name);
even longer code... This is a sad state...
Let's go back to the white board
What is the use of Domain Modeling? It is the representation of correct/validated data.
What is the single responsibility of a Domain model? Use the Create function to create a validated domain model from primitive values.
So, we validate a model. If validated, then we get a domain model; else we get a bunch of errors.
OK, now let's break down the whole process:
In the case of Server - Request. It is DTO -> Domain -> Validated or Errors -> Response
In the case of Client - Model. It is Model -> Domain -> Validated or Errors -> Show it to the user
Almost the same process. And if we don't look closer, we will not find a difference. In the case of server, it is one pass. We get the big fat data and we will validate and give big fat data in return. If things are validated, then an ID will be there; else validation errors. Typical CRUD case.
Now, imagine the same thing for a big fat form we are filling on the Internet. Maybe Banking or Insurance. I fill two dozen inputs and then I get errors for more than a dozen inputs. This does not work in the context of UX/User Experience. In UX, it should be multi-pass or single pass for every property.
If there is an error in a single input, the user needs to see the error message right away. Even with every stroke the user enters, the error message may change and they should get the message right away. Now, that is not possible with the typical DDD way. We might/should use some part of it, but not in the traditional way. There are a few examples of validation libraries in Elm and F# that are trying to do things the typical DDD way. They look good in samples where there are 2-4 input fields, but surely not work when there are 20-25 fields.
I know it might hurt the Domain Sharing approach we discussed earlier, but I learned the hard way while writing code for big ugly forms: It just doesn't work. Again, as I said, we can and should use part of it. More on it in the next section.
London Rail

After reading the above part, it is natural to have the feeling that we should run away from Fable. Just because we can't use ROP. Trust me, I also thought that too. I even thought about falling back to some traditional JavaScript framework. No DDD, No ROP, like seriously? But we can use ROP without any issue.
Here is code directly copy-pasted from the site with a simple example:
open FSharp.Core
open Fable.Core
open System
// convert a single value into a two-track result
let succeed x =
Ok x
// convert a single value into a two-track result
let fail (x) =
Error x
// apply either a success function or failure function
let either successFunc failureFunc twoTrackInput =
match twoTrackInput with
| Ok s -> successFunc s
| Error f -> failureFunc f
// convert a switch function into a two-track function
let bind f =
either f fail
// pipe a two-track value into a switch function
let (>>=) x f =
bind f x
// compose two switches into another switch
let (>=>) s1 s2 =
s1 >> bind s2
// convert a one-track function into a switch
let switch f =
f >> succeed
// convert a one-track function into a two-track function
let map f =
either (f >> succeed) fail
// convert a dead-end function into a one-track function
let tee f x =
f x; x
// convert a one-track function into a switch with exception handling
let tryCatch f exnHandler x =
try
f x |> succeed
with
| ex -> exnHandler ex |> fail
// convert two one-track functions into a two-track function
let doubleMap successFunc failureFunc =
either (successFunc >> succeed) (failureFunc >> fail)
// add two switches in parallel
let plus addSuccess addFailure switch1 switch2 x =
match (switch1 x),(switch2 x) with
| Ok s1, Ok s2 -> Ok (addSuccess s1 s2)
| Error f1, Ok _ -> Error f1
| Ok _ ,Error f2 -> Error f2
| Error f1,Error f2 -> Error (addFailure f1 f2)
let collect errorFn xs =
xs |> Seq.fold (fun res next ->
match res, next with
| Ok r, Ok i -> Ok (i::r)
| Ok _, Error m | Error m, Ok _ -> Error m
| Error m, Error n -> Error (errorFn m n)
) (Ok []) |> map List.rev
let validation1 (s:string) = if (String.IsNullOrEmpty(s)) then Error "String should not be empty" else Ok s
let validation2 (s:string) = if (s.Length > 50) then Error "Can't be more than 50" else Ok s
let combinedValidation = validation1 >> bind validation2
And here is the JavaScript code:
import Result from "fable-core/Result";
import CurriedLambda from "fable-core/CurriedLambda";
import { reverse } from "fable-core/List";
import List from "fable-core/List";
import { fold } from "fable-core/Seq";
import { isNullOrEmpty } from "fable-core/String";
export function succeed(x) {
return new Result(0, x);
}
export function fail(x) {
return new Result(1, x);
}
export function either(successFunc, failureFunc, twoTrackInput) {
if (twoTrackInput.tag === 1) {
return failureFunc(twoTrackInput.data);
} else {
return successFunc(twoTrackInput.data);
}
}
export function bind(f) {
var failureFunc;
return CurriedLambda((failureFunc = function (x) {
return fail(x);
}, function (twoTrackInput) {
return either(f, failureFunc, twoTrackInput);
}));
}
export function op_GreaterGreaterEquals(x, f) {
return bind(f)(x);
}
export function op_GreaterEqualsGreater(s1, s2) {
return CurriedLambda($var1 => bind(s2)(s1($var1)));
}
function _switch(f) {
return CurriedLambda($var2 => function (x) {
return succeed(x);
}(f($var2)));
}
export { _switch as switch };
export function map(f) {
var successFunc;
var failureFunc;
return CurriedLambda((successFunc = $var3 => function (x) {
return succeed(x);
}(f($var3)), failureFunc = function (x_1) {
return fail(x_1);
}, function (twoTrackInput) {
return either(successFunc, failureFunc, twoTrackInput);
}));
}
export function tee(f, x) {
f(x);
return x;
}
export function tryCatch(f, exnHandler, x) {
try {
return succeed(f(x));
} catch (ex) {
return fail(exnHandler(ex));
}
}
export function doubleMap(successFunc, failureFunc) {
var successFunc_1;
var failureFunc_1;
return CurriedLambda((successFunc_1 = $var4 => function (x) {
return succeed(x);
}(successFunc($var4)), failureFunc_1 = $var5 => function (x_1) {
return fail(x_1);
}(failureFunc($var5)), function (twoTrackInput) {
return either(successFunc_1, failureFunc_1, twoTrackInput);
}));
}
export function plus(addSuccess, addFailure, switch1, switch2, x) {
const matchValue = [switch1(x), switch2(x)];
if (matchValue[0].tag === 1) {
if (matchValue[1].tag === 1) {
return new Result(1, addFailure(matchValue[0].data, matchValue[1].data));
} else {
return new Result(1, matchValue[0].data);
}
} else if (matchValue[1].tag === 1) {
return new Result(1, matchValue[1].data);
} else {
return new Result(0, addSuccess(matchValue[0].data, matchValue[1].data));
}
}
export function collect(errorFn, xs) {
return map(function (list) {
return reverse(list);
})(fold(function (res, next) {
const matchValue = [res, next];
const $var6 = matchValue[0].tag === 1 ? matchValue[1].tag === 1 ? [2, matchValue[0].data, matchValue[1].data] : [1, matchValue[0].data] : matchValue[1].tag === 1 ? [1, matchValue[1].data] : [0, matchValue[1].data, matchValue[0].data];
switch ($var6[0]) {
case 0:
return new Result(0, new List($var6[1], $var6[2]));
case 1:
return new Result(1, $var6[1]);
case 2:
return new Result(1, errorFn($var6[1], $var6[2]));
}
}, new Result(0, new List()), xs));
}
export function validation1(s) {
if (isNullOrEmpty(s)) {
return new Result(1, "String should not be empty");
} else {
return new Result(0, s);
}
}
export function validation2(s) {
if (s.length > 50) {
return new Result(1, "Can't be more than 50");
} else {
return new Result(0, s);
}
}
export const combinedValidation = CurriedLambda($var7 => bind(function (s_1) {
return validation2(s_1);
})(function (s) {
return validation1(s);
}($var7)));
A little bit more complex code, with a couple of imports compared to other examples, but hey, the Rail is not missed, at least. This is the best thing Fable offers. We throw our F# code and it works. But again, be careful and be extra sure about what we like to throw towards JavaScript. I did port the famous Chessie to Fable. I used it for a while but then ended up removing it. Instead of cutting down the code after adding it, it is always easier to go slow and add whatever is necessary.
Slice and Dice

For a long time, performance tests of JavaScript frameworks were just numbers for me. I would never draw 1000 circles on screen. Or never gonna have a machine that cares about that nano-benchmark. I never faced a situation for that.
Until Elmish
If we start from the default template, we will create a model as a composite model. Like below:
[<Pojo>]
type Validate = {
IsValid : bool
ErrMsg : string
}
module Validate =
let Success = {
IsValid = true
ErrMsg = ""
}
let Failure (msg : string) = {
IsValid = false
ErrMsg = msg
}
let InitialValidate = {
IsValid = true
ErrMsg = ""
}
[<Pojo>]
type InfoModel = {
FirstName : string
FirstNameErr : Validate
LastName : string
LastNameErr : Validate
DOB : string
DOBErr : Validate
Gender : string
GenderErr : Validate
IsValid : bool
}
[<Pojo>]
type ContactModel = {
Mobile : string
MobileErr : Validate
Home : string
HomeErr : Validate
Office : string
OfficeErr : Validate
Email : string
EmailErr : Validate
Email2 : string
Email2Err : Validate
IsValid : bool
}
[<Pojo>]
type Model = {
Info : InfoModel
Contact : ContactModel
}
Here, consider we have two pages: Info and Contact. And then we have an update method to update them. Pretty straightforward.
Until we have around 20 pages or 50 components on the same page
For the first time, I felt slowness while typing in a simple HTML input box. Things just dragged like we are typing in a WPF application on a Windows 98 machine.
Let's understand what is happening here. The model is holding information required for Views. Now, even though the Info view is active, it is having information about Contact also. This makes the model quite big. Specifically if there are three or four levels of deep models. Deep models make things easy to understand but quite difficult to update. The deeper and bigger the model is, the slower the update of a property is. Now, in ELM/Elmish architecture, we rely on models extensively for what we can see on a view. Even simple typing into input requires us to update the model. And then there is validation and other stuff going on.
It also presents another weird problem. When we start the application, there is a need to initialize the models. So, for a big application, one needs to set all models at the start. To do that, we need to fire up too many requests to the server. For a sec, we consider that would be OK in the era of cloud, but it can't resolve the issue of dependent types. If some property is dependent on another, then we can't have them at the start, and we can't initialize the model.
No model, No view.
What is the alternative to this fat model?
Slim Union Type - Here is the above code written differently:
open FSharp.Core
open Fable.Core
open System
[<Pojo>]
type Validate = {
IsValid : bool
ErrMsg : string
}
module Validate =
let Success = {
IsValid = true
ErrMsg = ""
}
let Failure (msg : string) = {
IsValid = false
ErrMsg = msg
}
let InitialValidate = {
IsValid = true
ErrMsg = ""
}
[<Pojo>]
type InfoModel = {
FirstName : string
FirstNameErr : Validate
LastName : string
LastNameErr : Validate
DOB : string
DOBErr : Validate
Gender : string
GenderErr : Validate
IsValid : bool
}
[<Pojo>]
type ContactModel = {
Mobile : string
MobileErr : Validate
Home : string
HomeErr : Validate
Office : string
OfficeErr : Validate
Email : string
EmailErr : Validate
Email2 : string
Email2Err : Validate
IsValid : bool
}
type Page = Info = 0 | Contact = 1
type PageModel = Info of InfoModel | Contact of ContactModel
[<Pojo>]
type Model = {
Page : Page
PageModel : PageModel
}
It will hold only the thing that is visible on a Page. And for components, one can always go the old way or based on the component's visibility, they can choose the second option.
I did have a case where I had around 13 - 15 tabs on a page. They all are part of the same page, details about the same thing divided into multiple tabs for better viewing. Every single tab is doing
CRUDoperations using big fat forms and modal pop ups. I did start with the first option as it is the same page and so didn't mind loading the whole model at one shot. But things started to get very slow, so I had to switch to the second option. Here, I didn't require aPageproperty as it is for capturing value from a URL. But converting the model to a Union type indeed helps to improve performance. So, only the visible tab is having values in the model. Once we switch the tab, we are updating the model with another union type option.
Here, instead of a simple Union type, I have used Enum Types for the page. It is again to cut down the generated code. Pattern matching will work even for int as well as string. So, why burden our JavaScript with a string when pattern matching works on int? Even [<stringenum>] is a good alternative in these types of scenarios.
Here is the generated code for the second option:
import { setType } from "fable-core/Symbol";
import _Symbol from "fable-core/Symbol";
import { compareUnions, equals, Any } from "fable-core/Util";
export const ValidateModule = function (__exports) {
const Success = __exports.Success = {
IsValid: true,
ErrMsg: ""
};
const Failure = __exports.Failure = function (msg) {
return {
IsValid: false,
ErrMsg: msg
};
};
const InitialValidate = __exports.InitialValidate = {
IsValid: true,
ErrMsg: ""
};
return __exports;
}({});
export class PageModel {
constructor(tag, data) {
this.tag = tag | 0;
this.data = data;
}
[_Symbol.reflection]() {
return {
type: "Test.PageModel",
interfaces: ["FSharpUnion", "System.IEquatable", "System.IComparable"],
cases: [["Info", Any], ["Contact", Any]]
};
}
Equals(other) {
return this === other || this.tag === other.tag && equals(this.data, other.data);
}
CompareTo(other) {
return compareUnions(this, other) | 0;
}
}
setType("Test.PageModel", PageModel);
Kiss of the JavaScript

Fable can use JavaScript code. JavaScript can do some crazy stuff. If we compose it, then it becomes: Fable can do some crazy stuff. Don't trust me? See for yourself.
open Fable.Import
[<Emit("new Date(parseInt($0.substr(6)))")>]
let JsDateString (x : string) : JS.Date = jsNative
JsDateString "/Date(1522149899822-0000)/"
converts to:
new Date(parseInt("/Date(1522149899822-0000)/".substr(6)));
Here, new Date(parseInt($0.substr(6))) is a dynamically typed JavaScript function to convert /Date(1522149899822-0000)/ strings to a date. Obviously, I can try to do conversion using F#. I did try for half a day. But then I thought: why bother when the solution is already there on Stack Overflow? I should just use it. To no surprise, it works without any issue.
JavaScript does have many libraries, code snippets, and Stack Overflow answers. I can't say that all are good approaches, but sometimes it doesn't hurt to sneak outside.
Again, don't overuse it. One glass of wine may be good for health, but overdoses can hurt you.
The same is true when trying a new library. Here is a snippet for the Flatpickr library:
let flatpickr : Element -> obj option -> unit = importDefault "flatpickr"
It is used to call a date picker on input. So, there is no need for static typing. So, I used it anyway.
For me, the rule of thumb is: try using it as JavaScript. If we like the library, then we should search for a TypeScript definition file and use ts2fable to generate imports for us. That's also in the case of overusing all the options of that library.
CSS Tips and Tricks

In the above long article, I was always trying to avoid libraries or adding extra code. But in this specific section, I will suggest the exact opposite. If you are using the wonderful library called Bulma, then you should definitely try using Fulma. And if you are not using Bulma, then take a hint from it and write a wrapper for your favorite CSS framework.
But do save ourselves from lots and lots of DIVs and typos while writing CSS classes.
It is also showcasing how a library with documentation for front end should be written. The code is heavily optimized (you can find my imprints here and there in some commits too). It is one good project you should look for inspiration.
Shameless Plug

After working with many big and small consulting companies on various projects ranging from dotnet web to Node.js, single page applications to cross-platform mobile applications, I decided to go solo. There were hiccups at the start, but I did survive for almost a year. Not only did I survive, but I also started my own cloud consulting company Fuzzy Cloud.
So, I will be looking for new assignments from the month of April. If anyone is interested in work or training with me, please contact me. I know we are so small compared to other friends from Europe and the USA, but still we do share the same love for F# and functional programming in general. Even if I skip my 10+ years of commercial experience in different domains and tech, I am still a student of Evelina. Always ready for challenging assignments.
I would love to have feedback about this article and will update if there are any additions or changes required.
Frequently Asked Questions
Fable is a frontend framework that compiles F# to JavaScript, offering static typing for web applications. It's particularly valuable for large-scale projects where static typing helps catch errors early and makes refactoring safer, avoiding the painful experience of working with untyped JavaScript codebases.
Organize your application by features using namespaces, with each page divided into three modules: Types (for Model and Msg), State (for init and update logic), and View (for UI components). This structure scales well as your application grows and prevents the file proliferation that occurs when keeping all pages in a flat structure.
Frontend applications run in the browser and cannot be scaled horizontally like backend services. Poor frontend code directly impacts user experience and machine performance, making static typing essential to catch errors before runtime and ensure code quality from the start.
Elmish provides a predictable state management pattern with clear separation between Types (data structures), State (business logic), and View (UI rendering). This architecture makes it easier to understand application flow, test components, and maintain large applications over time.