fickl
F# to JavaScript and TypeScript, faster and more explicit.
A fork of the Fable compiler that adds what JavaScript and TypeScript projects need: native async and generators, TypeScript declarations, typed interop and a switch for every implicit change.
Swap dotnet fable for dotnet fickl; Fable's attributes and functions keep working.
open Fable.Core
open Fickl.Core
type User = { Name: string; Age: int }
[<Global>]
let fetchUser (id: int) : JS.Promise<User> = jsNative
[<AsyncFunction>]
let adultNames () = jsAsync {
let! first = fetchUser 1
and! second = fetchUser 2
return
[ first; second ]
|> List.filter (fun u -> u.Age >= 18)
|> List.map _.Name
}
import { Record } from "@fable-org/fable-library-js/Types.js";
import { record_type, int32_type, string_type } from "@fable-org/fable-library-js/Reflection.js";
import { ensureResolvedAsyncValue } from "@fable-org/fable-library-js/Util.js";
import { ofArray, filter, map } from "@fable-org/fable-library-js/List.js";
export class User extends Record {
constructor(Name, Age) {
super();
this.Name = Name;
this.Age = (Age | 0);
}
}
export function User_$reflection() {
return record_type("Example1.User", [], User, () => [["Name", string_type], ["Age", int32_type]]);
}
export const adultNames = async function () {
const _arg = await Promise.all([fetchUser(1), fetchUser(2)]);
return ensureResolvedAsyncValue(map((_arg_1) => _arg_1.Name, filter((u) => (u.Age >= 18), ofArray([_arg[0], _arg[1]]))));
};
import { Record } from "@fable-org/fable-library-ts/Types.ts";
import type { int32 } from "@fable-org/fable-library-ts/Int32.ts";
import { ensureResolvedAsyncValue, type IComparable, type IEquatable } from "@fable-org/fable-library-ts/Util.ts";
import { record_type, int32_type, string_type, type TypeInfo } from "@fable-org/fable-library-ts/Reflection.ts";
import { type FSharpList, ofArray, filter, map } from "@fable-org/fable-library-ts/List.ts";
export class User extends Record implements IEquatable<User>, IComparable<User> {
readonly Name: string;
readonly Age: int32;
constructor(Name: string, Age: int32) {
super();
this.Name = Name;
this.Age = (Age | 0);
}
}
export function User_$reflection(): TypeInfo {
return record_type("Example1.User", [], User, () => [["Name", string_type], ["Age", int32_type]]);
}
export const adultNames = async function (this: any): Promise<FSharpList<string>> {
const _arg: [User, User] = await Promise.all([fetchUser(1), fetchUser(2)] as [Promise<User>, Promise<User>]);
return ensureResolvedAsyncValue<FSharpList<string>>(map<User, string>((_arg_1: User): string => _arg_1.Name, filter<User>((u: User): boolean => (u.Age >= 18), ofArray([_arg[0], _arg[1]]))));
};
Compiled by this build of Fickl when the site was built.
Why Fickl
Fickl is a lab for the JavaScript and TypeScript side of Fable. Its priorities, in order: the performance and ergonomics of the compiler, explicit control of the code it emits, the performance and ergonomics of JavaScript interop, TypeScript interop, and transparent diagnostics.
A package cache, a compile server and a faster watch loop. Every release is benchmarked against the latest Fable.
jsAsync and promise compile to async functions, jsGenerator to function*, and use works with Symbol.dispose.
--declaration writes a .d.ts next to each JavaScript file, checked against a strict tsconfig.
keyof, Record, Partial, branded strings, type guards, callable interfaces and global augmentations.
Every implicit change has a switch: keep a function curried, keep a binding, keep Some null apart from None.
Fable's attributes and functions keep working. Fickl's own live in Fickl.Core, behind flags or the FICKL_COMPILER define.
What Fickl gets right that Fable does not
Each case in the repository's tests/Differences is compiled with both compilers and run with Node.js on every change to main. A case leaves the list once Fable passes it too. See Differences from Fable for the current list.