F# and React.js via Fable ๐ Feliz
- Fable ๐ฒ is an F# library and dotnet tool that lest you compile F# code to many different languages and framework, most preminently F# can compile to Javascript, Fable stands for F#-(B)abel originally. Since Fable 4, it can compile also to Python, Rust, and Swift.
- Feliz is a React.js adapter/api library for Fable, that lets you code in React with a simplified synthax.
- F# is an amazing functional first (also does OO) programming language with an amazing type inference and type system, inherited from its grandpa OCaml, it runs on the .NET platform like its C# cousin and it is highly interoperable in mixed C# / F# solutions, here some info on F# for web development.
Letโs go ๐ฒ๐ฅ
First thing first, install dotnet-sdk if you donโt have it, i am on mac so.. F# is a dotnet language, so it runs on the .NET runtime and you will need the sdk to make anything usfeul with it.
brew install dotnet
Then we want to install the latest feliz templates, which also make use of the amazing Vite bundler and dev server, instead of the older webpack.
dotnet new -i Feliz.Template
and create a new Feliz app, which is a dotnet app that will be compiled to Javascript using React.js components, using Fable.js ๐
dotnet new feliz -n AwesomeApp
here is how our package.json looks like
{
"private": true,
"scripts": {
"start": "dotnet tool restore && dotnet fable watch src --runFast vite",
"build": "dotnet tool restore && dotnet fable src --run vite build",
"clean": "dotnet fable clean src --yes"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^3.1.0",
"vite": "^4.1.0"
},
"engines": {
"node": ">=18"
}
}
All set, now you can install and run, this will in turn also compile our .fs to .js files and run the vite dev server.
npm i && npm start
Your App Main.fs should look like this one below, if you want to use the router. After each fable compilation, the javascript output is preserved next to our original files as .js, but we donโt need to care much about it.
Something to keep in mind is that the order of file matters in F# to exclude cyclic dependencies (it was a choice for the compiler), so here it is. Always include the files in their dependency order (main or root is always at the bottom).
<ItemGroup>
<None Include="index.html" />
<Compile Include="Extensions.fs" />
<Compile Include="Components.fs" />
<Compile Include="Main.fs" />
</ItemGroup>
You can then invoke the routes in your browser by using the hash (#) based routes (FE-only routes) to navigate to different routes, for example /#hello.
// router
[<ReactComponent>]
static member Router() =
let (currentUrl, updateUrl) = React.useState(Router.currentUrl())
React.router [
router.onUrlChanged updateUrl
router.children [
match currentUrl with
| [ ] ->
Html.div [
Html.h1 "Welcome!"
]
| [ "hello" ] -> Components.HelloWorld(). // #hello
| [ "counter" ] -> Components.Counter() // #counter
| otherwise -> Html.h1 "Not found"
]
]
If you are interested, you can also make use of inline JSX via this article and some modifications to your .fsproj file (or adding the packages via cli)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <!-- 6.0 is LTS atm -->
</PropertyGroup>
<ItemGroup>
<None Include="index.html" />
<Compile Include="Extensions.fs" />
<Compile Include="Components.fs" />
<Compile Include="Main.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Feliz" Version="2.6.0" />
<!-- add compiler plugins -->
<PackageReference Include="Feliz.CompilerPlugins" Version="2.2.0" />
<PackageReference Include="Feliz.Router" Version="4.0.0" />
<!-- add latest fable.core theta (maybe also latest just works in your case) -->
<PackageReference Include="Fable.Core" Version="4.0.0-theta-006" />
</ItemGroup>
</Project>
With this additions, we can create sample .jsx inline components as such, but is also possible to load them from disk to integrate with external react components or libraries!
namespace App
open Feliz
open Feliz.Router
open Fable.Core // this is needed to create some extensions
open Fable.React
[<AutoOpen>]
module JsxHelpers =
let inline toJsx (el: ReactElement) : JSX.Element = unbox el
let inline toReact (el: JSX.Element) : ReactElement = unbox el
type Components =
[<ReactComponent>]
static member HelloWorld() =
JSX.jsx $"""
<>
<h1>HELLO WORLD!</h1>
<div>this is a div from JSX</div>
</>
"""
|> toReact
More info on external components interaction here :
- https://fable.io/blog/2022/2022-10-12-react-jsx.html
- https://zaid-ajaj.github.io/Feliz/#/Feliz/UsingJsx
Have fun with React and Feliz and have a great week!