<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Angel Daniel Munoz Gonzalez</title>
    <description>The latest articles on Forem by Angel Daniel Munoz Gonzalez (@tunaxor).</description>
    <link>https://forem.com/tunaxor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F35800%2F28156be5-48cb-4192-94a4-9c848f92a80a.jpg</url>
      <title>Forem: Angel Daniel Munoz Gonzalez</title>
      <link>https://forem.com/tunaxor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tunaxor"/>
    <language>en</language>
    <item>
      <title>Exploring MonoGame with F#</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Tue, 16 Dec 2025 14:00:00 +0000</pubDate>
      <link>https://forem.com/tunaxor/exploring-monogame-with-f-the-evolution-of-kipo-2ok0</link>
      <guid>https://forem.com/tunaxor/exploring-monogame-with-f-the-evolution-of-kipo-2ok0</guid>
      <description>&lt;p&gt;Hey there!&lt;br&gt;
It has been quite some time since the last blog entry.&lt;/p&gt;

&lt;p&gt;Another December, another #FsAdvent entry in my F# decade long journey!&lt;br&gt;
Thanks to &lt;a href="https://bsky.app/profile/sergeytihon.com" rel="noopener noreferrer"&gt;Sergey Tihon&lt;/a&gt; for another consecutive year of organizing the very lovely &lt;a href="https://sergeytihon.com/2025/11/03/f-advent-calendar-in-english-2025/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;F# Advent calendar&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Rather than bore you with "How I burned out and recovered" stories, I thought it would be more interesting to share my recent journey into game development using F# and MonoGame.&lt;/p&gt;

&lt;p&gt;Today I'll be talking about Kipo&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/Kipo" rel="noopener noreferrer"&gt;
        Kipo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An RPG-like Game prototype written in F# and Monogame
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Kipo&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;An ARPG engine prototype, built as a playground for exploring functional game development patterns in F# and MonoGame.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/AngelMunoz/Kipo/./2026-01-01%2023-33-39.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FAngelMunoz%2FKipo%2F.%2F2026-01-01%252023-33-39.gif" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What is This?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Kipo is an isometric action-RPG engine written entirely in F#. It's a technical sandbox where serious engineering meets game development curiosity. If you've ever wondered what an RPG engine looks like built from the ground up with functional principles—here it is.&lt;/p&gt;
&lt;p&gt;This isn't a finished game. It's a working prototype with real systems: combat, AI, skills, projectiles, visual effects, and more. The architecture is designed for performance and maintainability, but the scope is still evolving.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Technical Highlights&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Data-Oriented Programming&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;State is strictly separated from logic. We prioritize using immutable data structures that flow through pure transformation functions, aiming for code that is easier to reason about, test, and parallelize.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Performance Engineering&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;The engine is built with a focus on minimizing GC pressure. We aim to keep critical paths…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/Kipo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;Kipo is the second prototype I've build with &lt;a href="https://monogame.net/" rel="noopener noreferrer"&gt;MonoGame&lt;/a&gt; and although &lt;a href="https://github.com/AngelMunoz/Kps" rel="noopener noreferrer"&gt;Kps&lt;/a&gt; shares similarities, Kipo took off from where I became blocked with Kps.&lt;/p&gt;

&lt;p&gt;In the ideal world these are some of the overall goals I had for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;F# : because I really love the language, and these days is hard for me to pick something else for my own projects.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MonoGame: Because for some reason it is basically the only option that doesn't fight me tooth and nail to use F#. Neither Unity nor Godot have F# support at all. Sure I could jump through hoops to use F# in these engines but I find that the friction is not worth it at least for me.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RPG in its core: If you ever played &lt;a href="https://en.wikipedia.org/wiki/Trickster_Online" rel="noopener noreferrer"&gt;Trickster Online&lt;/a&gt; or even &lt;a href="https://en.wikipedia.org/wiki/Ragnarok_Online" rel="noopener noreferrer"&gt;Ragnarok Online&lt;/a&gt;, you'll know the feeling I'm looking for. I wanted to apply some of these mechanics in a single-player RPG format.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Isometric View: It looks good, I didn't know what I was getting into.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tiled2D Maps: I don't really have experience with games, but every time I've given it a micro shot, tiled2d has been there and I started to get somewhat familiar with it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3D entities: The scenario and world can be 2D as I think for that kind of work it is easier to have images/sprites, but when it comes to characters, entities, objects and so on, I think 3D is interesting to explore, particularly inspired by Super Mario World Games where the platform looks 2D but the characters are 3D.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ECS-like architecture: Since I'm not an expert in game development, I wanted to explore some of the common patterns but after reading a little bit about ECS, I felt it brought a lot of things to the table that would make my life more complex than I needed it. Picking up some ideas from ECS and adapting core concepts from F# and adaptive data seemed like a good fit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/fsprojects/FSharp.Data.Adaptive" rel="noopener noreferrer"&gt;FSharp.Data.Adaptive&lt;/a&gt;: Adaptive data is one of my favorite approaches to state changes in applications, so I wanted to explore if I could skeak it into a game architecture.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that in mind, I started this project roughly two months ago (counting KPS as well) and I wanted to share some of the journey, phases, and learnings I had along the way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: I must say though that my previous experience writing video games has been close to zero, (a few demos of phaser.js, some monogame experiments no longer than 300 LOC, and at most one or two unity tutorials) so as I was implementing both Kipo and KPS I was learning quite a lot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Foundations &amp;amp; Architecture (Early November)
&lt;/h2&gt;

&lt;p&gt;The project began not with graphics, but with a focus on data + types architecture.&lt;br&gt;
What I've leaned over the years with F# is that a solid set of models and types can simplify life a long way.&lt;/p&gt;
&lt;h3&gt;
  
  
  Re-architecture &amp;amp; Core Systems
&lt;/h3&gt;

&lt;p&gt;While some of the architectural choices were born in Kps, some were entirely born in Kipo.&lt;br&gt;
Most of it though, simply flowed from Kps to Kipo with the corresponding adjustments to the domain. This allowed me to not start strictly from zero and this time, I would focus a more on staying more true to Monogame where possible (GameComponent, DrawableGameComponent) than complete F# moules and functions.&lt;/p&gt;

&lt;p&gt;The decision comes from being able to interop and leverage existing MonoGame Libraries in case I needed to, which ended up being useful when integrating Myra as a GUI library.&lt;/p&gt;

&lt;p&gt;In any case back on the game itself, the main effort at the beginning was to port over the input, keybindings and action systems from KPS. These would allow me to start moving a player and invoking abilities for the early prototype phases.&lt;/p&gt;

&lt;p&gt;Then simple motion in a top-down 2d world was more than enough to get started.&lt;/p&gt;

&lt;p&gt;Kps had a monolithic &lt;code&gt;GameState&lt;/code&gt; that managed every component of entities in a single record in a very monolithic way. In Kipo I decided to split the components into separate maps of components which allowed for more flexibility and granular updates although, that introduced a little bit of coginitive complexity as entity components are no longer grouped together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kps code&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;EntityComponents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Classification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Profession&lt;/span&gt;
  &lt;span class="nc"&gt;BaseStats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BaseAttributes&lt;/span&gt;
  &lt;span class="nc"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Resources&lt;/span&gt;
  &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Position&lt;/span&gt;
  &lt;span class="nc"&gt;Movement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Movement&lt;/span&gt;
  &lt;span class="nc"&gt;AbilityCooldowns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AbilityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;Effects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EffectId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;ActiveEffect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;Factions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Classification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Faction&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;
  &lt;span class="nc"&gt;Abilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AbilityId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;
  &lt;span class="nc"&gt;PartyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PartyId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;voption&lt;/span&gt;
  &lt;span class="nc"&gt;Inventory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InventoryItemInstanceId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;InventoryItem&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;EquippedItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Slot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;InventoryItemInstanceId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: &lt;a href="https://github.com/AngelMunoz/Kps/blob/main/Pomo.Lib/Domain.fs" rel="noopener noreferrer"&gt;Kps Domain.fs&lt;/a&gt; shows the full picture of the Kps domain types.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kipo code&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;World&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Time&lt;/span&gt; &lt;span class="n"&gt;aval&lt;/span&gt;
    &lt;span class="c1"&gt;// Components as adaptive maps&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Velocities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nn"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;Factions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nn"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Faction&lt;/span&gt; &lt;span class="nc"&gt;HashSet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="nc"&gt;AIControllers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nn"&gt;AI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AIController&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This in turn allowed the newer systems to operate on the components they care about and when updates were made, only the relevant state would update, not an entire entity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reactive Data
&lt;/h3&gt;

&lt;p&gt;One of the key technical choices was the integration of &lt;code&gt;FSharp.Data.Adaptive&lt;/code&gt;.&lt;br&gt;
This library allows for reactive programming patterns, where changes to data automatically propagate through the system. This also guaranteed a single source of truth along systems when these requested the most recent snapshot of the game state.&lt;/p&gt;

&lt;p&gt;The built-in cache mechanisms also (hopefully as I didn't measure anything at this point though no performance issues have been noticed at all) helped to avoid unnecessary recomputations during the game loop.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;EffectProcessingSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PomoEnvironment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;GameSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Core&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CoreServices&lt;/span&gt;

  &lt;span class="c1"&gt;// Adaptive computations were set in class private properties&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;timedEvents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;World&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ActiveEffects&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;timedEffects&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;calculateTimedEvents&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;World&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;loopEvents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;World&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ActiveEffects&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;loopEffects&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;calculateLoopEvents&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;World&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;permanentLoopEvents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;World&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ActiveEffects&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;permanentLoopEffects&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;calculatePermanentLoopEvents&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;World&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;allEvents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IndexList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="nn"&gt;AMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unionWith&lt;/span&gt;
      &lt;span class="n"&gt;resolve&lt;/span&gt;
      &lt;span class="n"&gt;timedEvents&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;AMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unionWith&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt; &lt;span class="n"&gt;loopEvents&lt;/span&gt; &lt;span class="n"&gt;permanentLoopEvents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// get only the values, as keys are not relevant here&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;AMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toASetValues&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;publishEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IndexList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LifecycleEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;evts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;evts&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="n"&gt;stateEvent&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt; &lt;span class="n"&gt;stateEvent&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;EffectTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DamageIntent&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt; &lt;span class="n"&gt;dmg&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;EffectTick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResourceIntent&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="nc"&gt;Update&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;allEvents&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ASet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt; &lt;span class="c1"&gt;// request the latest snapshot&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;publishEvents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;having the adaptive value "running" within the class and requesting the latest snapshot during the update loop allows the system to avoid dirty checking and flag-based management with ifs and other flow control mechanisms.&lt;/p&gt;

&lt;p&gt;Most of these computations are pure functions that operate on the state and transform that data to produce something that can be used in this case by other systems via the event bus.&lt;/p&gt;

&lt;p&gt;In this case, all of the effect-related events are calculated from the active effects map, which if no-one is applying or removing effects, this map remains unchanged and no recomputations are made even if we request the latest snapshot every frame.&lt;/p&gt;

&lt;p&gt;Depending of what the systems do, some of them use adaptive values some of them just use pure functions that operate on the latest snapshot of the world and overall this is what most systems within the game look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gameplay Depth (Mid-November)
&lt;/h2&gt;

&lt;p&gt;With these core systems in place, I was able to start iterating on basic gameplay features. Each time a new feature was added. It fit neatly into the established architecture, more domain types, a new system and the game loop just kept running smoothly.&lt;/p&gt;

&lt;p&gt;Then the focus shifted to implementing deep RPG mechanics. F# algebraic data types make it easier to reason about the relation between different concepts and how those can be intertwined, for my case it boiled down to have simple and small types that represent the domain concepts and then build systems that operate on these types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combat, Skills, and Inventory
&lt;/h3&gt;

&lt;p&gt;Porting what I had already done in Kps, was a bit complex as some of the previous code assumed the existance of things that weren't there just yet however, the very core of combat and skills were ported with nearly no changes.&lt;/p&gt;

&lt;p&gt;Non-Surprisingly F# is very good at math 😆, the complex part as usual was to decide how these calculations were going to be performed.&lt;/p&gt;

&lt;p&gt;What are the attackers stats, what are the defender stats. How do defenses (and when) apply and so on.&lt;/p&gt;

&lt;p&gt;Skills were a difficult piece in the sense that, there's a lot of moving parts and trying to be relatively generic while still being flexible enough to express different kinds of skills was a challenge in itself.&lt;/p&gt;

&lt;p&gt;I've been playing Trickster Online at least a decade at this point (thanks private servers), and I never thought how complex skill definitions, effects placed from skills, targeting modes, nuances between single-hit, multi-hit, multi-target, directed-multi-target, area-of-effect, were... something I always took for granted as a player.&lt;/p&gt;

&lt;p&gt;However, I think I was able to settle on a flexible enough skill definition format that allows me to express a wide variety of skills. For example, here's a fireball skill definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fireball"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hurls a seeker fiery ball that explodes upon impact."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Intent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Offensive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Cost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Targeting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TargetEntity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Area"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Point"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Delivery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Projectile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Speed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"CollisionMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IgnoreTerrain"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ElementFormula"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fire"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Formula"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"(MA * 1.0) + (FireA * 1500)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: For more definitions and the difference between them you can check &lt;a href="https://github.com/AngelMunoz/Kipo/blob/main/Pomo.Core/Content/Skills.json" rel="noopener noreferrer"&gt;Skills.json&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Effects were complicated as there's stacking, permanency, effects that get stronger/weaker based on stats, and so on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Targeting&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Self&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;TargetEntity&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;TargetPosition&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;TargetDirection&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;SkillArea&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Circle&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;maxCircleCircleTargets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Cone&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;maxConeTargets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;maxLineTargets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;EffectModifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;StaticMod&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;StatModifier&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g. The more MA, the more HP heals/damages as the result of the effect&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;DynamicMod&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Formula&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MathExpr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Stat&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g. Hellfire residual burn damage based on ability power and element&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;AbilityDamageMod&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
    &lt;span class="n"&gt;abilityDamageValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Formula&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MathExpr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;
    &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Element&lt;/span&gt; &lt;span class="n"&gt;voption&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ResourceChange&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ResourceType&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Formula&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MathExpr&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="nc"&gt;Kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EffectKind&lt;/span&gt;
    &lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Duration&lt;/span&gt; &lt;span class="c1"&gt;// e.g., Instant, Timed, Loop, Permanent&lt;/span&gt;
    &lt;span class="nc"&gt;Modifiers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EffectModifier&lt;/span&gt;&lt;span class="bp"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bonus: Flexible Formulas&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing that I feel like I should mention is how we handle skill damage sources. In a typical RPG, you have damage formulas, stat calculations, and elemental modifiers and a bunch of other things... it can get messy fast.&lt;/p&gt;

&lt;p&gt;I didn't want to hardcode logic like &lt;code&gt;let damage = (attack * 2) - defense&lt;/code&gt; directly into the codebase. I wanted the flexibility to tweak game balance because I feel mods could be a thing somewhere in the far future.&lt;/p&gt;

&lt;p&gt;So, as usual with F#, I started with a type and wrote a small parser to convert the strings into a type-safe tree at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Const&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Var&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;VarId&lt;/span&gt;  &lt;span class="c1"&gt;// e.g. "AttackPower", "FireRes"&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Add&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Mul&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Pow&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;MathExpr&lt;/span&gt;
    &lt;span class="c1"&gt;// ... and so on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows me to write strings like &lt;code&gt;"(MA * 1.0) + (FireA * 1500)"&lt;/code&gt; directly in the skill data. The game parses this at runtime and evaluates it when a fireball hits an enemy entity (or a friendly one if it relates to healing).&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Behaviors
&lt;/h3&gt;

&lt;p&gt;One of the previous parts that I felt were almost good enough in Kps was the AI system.&lt;/p&gt;

&lt;p&gt;Based on controllers that are attached to entities, the core of the AI controlled entities is based on perception, behavior and cues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;CuePriority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;cueType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CueType&lt;/span&gt;
  &lt;span class="n"&gt;minStrength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CueStrength&lt;/span&gt;
  &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ResponseType&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;AIArchetype&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AiArchetypeId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;behaviorType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;BehaviorType&lt;/span&gt; &lt;span class="c1"&gt;// e.g. Patrol, Aggressive&lt;/span&gt;
  &lt;span class="n"&gt;perceptionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PerceptionConfig&lt;/span&gt;
  &lt;span class="n"&gt;cuePriorities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CuePriority&lt;/span&gt;&lt;span class="bp"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;decisionInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeSpan&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: For more details on how the AI system works, check &lt;a href="https://github.com/AngelMunoz/Kipo/blob/main/Pomo.Core/Systems/AISystem.fs" rel="noopener noreferrer"&gt;AI.fs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think there's still a lot of room for improvement here, but the basic systems are in place to allow entities to perceive their surroundings, react to stimuli (cues), and make decisions based on their defined behavior types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Bus
&lt;/h3&gt;

&lt;p&gt;To decouple these growing systems, we refined the internal event system. This allowed gameplay events (like "UnitDamaged" or "SkillCast") to trigger independent reactions across the codebase without systems knowing about each other.&lt;/p&gt;

&lt;p&gt;I have always been a big fan of event-driven code, firing an event and handling somewhere else I feel it is a good way to decouple systems even if that brings some complexity when trying to trace what is going on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RequireQualifiedAccess&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;SystemCommunications&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;EffectApplicationIntent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SourceEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;TargetEntity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Effect&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ProjectileImpacted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ProjectileId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;CasterId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;TargetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;SkillId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SkillId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... other strongly typed events&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Maps and Tiled2D
&lt;/h3&gt;

&lt;p&gt;Tiled2D maps are basically just XML files that describe layers of tiles, objects. Some data can be attached to the map itself or to objects via custom properties.&lt;/p&gt;

&lt;p&gt;And since there's already an XML parser built into .NET parsing these maps is very straightforward.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/AngelMunoz/Kipo/blob/main/Pomo.Core/Domain/Map.fs" rel="noopener noreferrer"&gt;Map.fs&lt;/a&gt; file defines the F# types that mirror the structure of the Tiled XML files, allowing the game engine to load and work with map data programmatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Basic Definitions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Orientation&lt;/code&gt;, &lt;code&gt;StaggerAxis&lt;/code&gt;, &lt;code&gt;RenderOrder&lt;/code&gt;: F# enums corresponding to Tiled's map settings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TileDefinition&lt;/code&gt;, &lt;code&gt;MapTile&lt;/code&gt;: Types to represent individual tiles and their instances on a map layer.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Map Components&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MapLayer&lt;/code&gt;: An F# record representing a Tiled layer, containing its ID, name, dimensions, and a 2D array of optional &lt;code&gt;MapTile&lt;/code&gt;s.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MapObject&lt;/code&gt;: Represents a single object from an object group, encapsulating its ID, name, type (&lt;code&gt;MapObjectType&lt;/code&gt;), position (&lt;code&gt;X&lt;/code&gt;, &lt;code&gt;Y&lt;/code&gt;), size (&lt;code&gt;Width&lt;/code&gt;, &lt;code&gt;Height&lt;/code&gt;), rotation, custom properties, and geometric &lt;code&gt;Points&lt;/code&gt; (for polygons).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MapObjectType&lt;/code&gt;: An F# discriminated union (&lt;code&gt;Wall&lt;/code&gt;, &lt;code&gt;Zone&lt;/code&gt;, &lt;code&gt;Spawn&lt;/code&gt;, &lt;code&gt;Teleport&lt;/code&gt;) to categorize map objects, facilitating type-safe pattern matching in game logic.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PortalData&lt;/code&gt;: Specifically for &lt;code&gt;Teleport&lt;/code&gt; objects, defining the target map and spawn point.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ObjectGroup&lt;/code&gt;: A collection of &lt;code&gt;MapObject&lt;/code&gt;s, matching the Tiled object groups.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tileset&lt;/code&gt;: An F# record holding the metadata and &lt;code&gt;TileDefinition&lt;/code&gt;s for a tileset.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;MapDefinition&lt;/code&gt;&lt;/strong&gt;: This is the top-level type that holds all the parsed information from a &lt;code&gt;.xml&lt;/code&gt; map file, including global properties, tilesets, layers, and object groups. This &lt;code&gt;MapDefinition&lt;/code&gt; is then used by various game systems (rendering, physics, AI) to interact with the game world.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MapObjectType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Wall&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Zone&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Spawn&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Teleport&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MapObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MapObjectType&lt;/span&gt; &lt;span class="n"&gt;voption&lt;/span&gt;
    &lt;span class="nc"&gt;X&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;
    &lt;span class="nc"&gt;Y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nc"&gt;Points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IndexList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;voption&lt;/span&gt; &lt;span class="c1"&gt;// For polygons&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the full parsing code in &lt;a href="https://github.com/AngelMunoz/Kipo/blob/main/Pomo.Core/Systems/MapLoader.fs" rel="noopener noreferrer"&gt;MapLoader.fs&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimization &amp;amp; Refinement (Late November)
&lt;/h2&gt;

&lt;p&gt;At some point when some core systems were in place. Adding more entities, traversing the map suddenly brought some random freezes, which I attributed to garbage collection. From what others have said in their F# experiences, excessive garbage collection can be a common issue due to the immutable nature of F#.&lt;/p&gt;

&lt;p&gt;And while avoiding GC and trying to be performant from the start was also one of the goals, somehow I missed the mark.&lt;/p&gt;

&lt;p&gt;So I revisited some of the core systems to realize that there were some bad assumptions of using adaptive data for things that change very often like positions, spatial grids which are updated every update cycle, so the adaptive graph was being invalidated every frame as the "root" data was changing constantly. That got me thinking "If we're going to invalidate the tree every frame, there's no point in using adaptive data for these parts". I opted for a mixed approach where adaptive data is used for more stable data while more dynamic data while still backed up by adaptive data, snapshots are calculated on demand rather than leveraging full adaptivity and calculations being made in adaptive computations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MovementSnapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;SpatialGrid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GridCell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IndexList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;Rotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntityId&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// In Projections.fs&lt;/span&gt;
&lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="nc"&gt;ComputeMovementSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenarioId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// Forces the adaptive values once per frame to build the snapshot&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;velocities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Velocities&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;AMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Positions&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;AMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;force&lt;/span&gt;
    &lt;span class="c1"&gt;// ... calculates physics logic ...&lt;/span&gt;
    &lt;span class="n"&gt;calculateMovementSnapshot&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="n"&gt;velocities&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resulted in smoother performance and the freezes were gone.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're using adaptive data for whatever reason, I would say that try to keep the adaptive roots/trunk as stable as possible when it comes to the frequency of input data and leave most of the high frequency changes to leaves in the adaptive tree.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Spatial Optimization
&lt;/h3&gt;

&lt;p&gt;A major refactor involved the spatial domain, implementing SAT for precise collision detection. The good news is that even if I'm not skilled in geometry this math is an already solved problem. So after a couple prompts to assistants I had a working implementation.&lt;/p&gt;

&lt;p&gt;There are some edge cases where collision boudaries are not being calculated properly, specially when boundaries overlap, this is something I still need to investigate and fix. I guess AI is not replacing us soon after all 😆. It however, helped me to not divert too much time and effort to learn something that works and unblocks me from more interesting/important work untill I have a chance or motivation to fix this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3D Pivot (Early December - Present)
&lt;/h2&gt;

&lt;p&gt;The most recent and significant shift has been the transition from 2D sprites to a full 3D visual style while keeping the same logic core.&lt;/p&gt;

&lt;p&gt;I imported 3D models (using &lt;a href="https://kaylousberg.itch.io/prototype-bits" rel="noopener noreferrer"&gt;KayKit assets&lt;/a&gt;) and replaced the legacy 2D rendering logic. This also meant building a 3D animation system from scratch to handle model rigging, keyframes, and state-based animations like Idle, Walk, and Attack.&lt;/p&gt;

&lt;p&gt;This is just another area where I have never worked before, but with a few prompts and trial and error I was able to get something basic working and it is sustainable enough to keep building upon!&lt;/p&gt;

&lt;p&gt;I can see myself later on adding more complex animations like spell casting, taking damage, dying, and so on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;RigNode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;ModelAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="nc"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;voption&lt;/span&gt;
  &lt;span class="nc"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;
  &lt;span class="nc"&gt;Pivot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt; &lt;span class="c1"&gt;// Key for fixing rotation issues!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Keyframe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeSpan&lt;/span&gt;
  &lt;span class="nc"&gt;Rotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Quaternion&lt;/span&gt;
  &lt;span class="nc"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Vector3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;AnimationClip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeSpan&lt;/span&gt;
  &lt;span class="nc"&gt;Tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Track&lt;/span&gt;&lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;// Array of keyframes per node&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Driven
&lt;/h3&gt;

&lt;p&gt;One of the important aspects of this game is how some key systems are data-driven.&lt;/p&gt;

&lt;p&gt;For example, AI Archetypes, Skills, Maps, and Items among others, are defined in JSON files which may be moved to a database later on.&lt;/p&gt;

&lt;p&gt;This while allowing me to iterate fast on mechanics or design, also hints at a possible modding system in the future as well as oportunities for more F# tools to be created in order to facilitate content creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Polish: The Particle System (Mid-December)
&lt;/h3&gt;

&lt;p&gt;With combat, skills and 3D rendering in place, one missing piece (among others) are particles! Things flying and arriving look good for a demo but they lack emotion, so the next sensible thing to add is particles which should give the visuals a little bit more of sense of being alive.&lt;/p&gt;

&lt;h4&gt;
  
  
  Modeling Emitter Behavior with DUs
&lt;/h4&gt;

&lt;p&gt;The particle system needed to describe &lt;em&gt;where&lt;/em&gt; particles spawn (shapes) and &lt;em&gt;how&lt;/em&gt; they flow (emission modes). Rather than a flat configuration object with fields like &lt;code&gt;isOutward: bool&lt;/code&gt; or &lt;code&gt;shapeType: string&lt;/code&gt;, these became proper discriminated unions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;EmitterShape&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Sphere&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Cone&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;EmissionMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Uniform&lt;/span&gt;   &lt;span class="c1"&gt;// Fill area evenly&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Outward&lt;/span&gt;   &lt;span class="c1"&gt;// Flow away from origin&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Inward&lt;/span&gt;    &lt;span class="c1"&gt;// Converge toward center&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;EdgeOnly&lt;/span&gt;  &lt;span class="c1"&gt;// Spawn at perimeter only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two dimensions compose orthogonally: a &lt;code&gt;Sphere&lt;/code&gt; + &lt;code&gt;Outward&lt;/code&gt; creates an explosion, while &lt;code&gt;Sphere&lt;/code&gt; + &lt;code&gt;Inward&lt;/code&gt; creates an implosion. The pattern matching makes the logic clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="n"&gt;computeSpawnDistance&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;EmissionMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Uniform&lt;/span&gt;  &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NextDouble&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Outward&lt;/span&gt;  &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NextDouble&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Inward&lt;/span&gt;   &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NextDouble&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;EdgeOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NextDouble&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a new emission mode is straightforward: define the case, then the compiler points to every match expression that needs updating.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cross-System Integration: Skills Shape Particles
&lt;/h4&gt;

&lt;p&gt;One pattern that emerged was letting the combat system's &lt;code&gt;SkillArea&lt;/code&gt; influence particle geometry. When a cone skill like Dragon's Breath fires, its particles should automatically fill that cone — not some hardcoded shape from the particle config.&lt;/p&gt;

&lt;p&gt;The particle system checks for skill-driven overrides at spawn time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;EmitterShape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configRadius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;overrides&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Area&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ValueSome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SkillArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Cone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skillAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skillLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;_))&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;skillAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skillLength&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ValueSome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SkillArea&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AdaptiveCone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skillLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;_))&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;configAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;skillLength&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;configAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;configRadius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;
    &lt;span class="n"&gt;spawnConeShape&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="n"&gt;effectiveMode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the systems decoupled — particles don't know about combat logic, they just receive typed data. Adding a new skill area type automatically prompts consideration of how particles should render it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: The full particle configuration reference can be found in &lt;a href="https://github.com/AngelMunoz/Kipo/blob/main/docs/ParticleConfigReference.md" rel="noopener noreferrer"&gt;ParticleConfigReference.md&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What's the actual F# role here?
&lt;/h3&gt;

&lt;p&gt;F# is usually seen as a niche language, downplayed due it's functional nature and the fact that has to compete in a C# dominated space. Using it for Game development is even more niche, so why bother?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Refactoring confidence. Moving from a single, monolithic GameState to separate component maps (and later switching parts of the renderer to 3D) felt safe because the compiler would catch a lot of the mechanical mistakes. That meant I could iterate fast: change a type, let the compiler point out what else needed fixing, and keep moving.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Domain-first modeling. Instead of inventing a bunch of boilerplate classes, I encoded the game's rules and structures directly as types. Those types became a readable, enforceable design document — tooling (and the type system) did a lot of the heavy lifting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Things like units-of-measure/UMX for IDs and strongly typed events reduced accidental bugs (mixing up identifiers or event payloads). They're not flashy, but they save time when you refactor or add new systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of that combined let me mix high-level functional ideas (the &lt;code&gt;MathExpr&lt;/code&gt; tree for flexible formulas) with low-level, performance-conscious code (structs, spans, inline function control, among others) without feeling like I was fighting the language.&lt;/p&gt;

&lt;p&gt;If you look at the commit history for Kipo you'll see a theme: quick, targeted rewrites and a lot of small, confident changes. I rebuilt the rendering pipeline to support skeletal 3D animation in a day, and when GC hiccups showed up I refactored movement to use frame snapshots.&lt;/p&gt;

&lt;p&gt;That kind of speed comes from a practical place: F# encourages expressive domain modelling and gives you enough type-safety to catch many mistakes early. The payoff is especially noticeable while prototyping — you can experiment, refactor, and change direction without the usual fear of breaking everything.&lt;/p&gt;

&lt;p&gt;As I usually say, F# is a high level rust with an even easier time to get going. You get performance, safety, and expressiveness all in one package.&lt;/p&gt;

&lt;h3&gt;
  
  
  LLMs and what they've contributed.
&lt;/h3&gt;

&lt;p&gt;If you've follow me before, you probably know that I've been using LLMs to help me with my Game Development Journey!&lt;/p&gt;

&lt;p&gt;I know next to nothing from 3D graphics programming, Geometry, Animation, and many of the areas game development touches. so Things like Gemini, Qwen, Claude have been really useful to get me going where I wanted to go.&lt;/p&gt;

&lt;p&gt;Whether you like AI or not, for me it is clear that these tools offer a value, I wouldn't let gemini or other assistants write the entire game for me that's for sure and I am very wary of code they write when I am familiar with the domain. I do inspect code in order to correct patterns that I don't like or that I think are not optimal. But when I am exploring new areas, these assistants help me get started, give me ideas, and unblock me when I get stuck.&lt;/p&gt;

&lt;p&gt;LLMs produce slop? Yes. Humans produce slop? Also yes.&lt;/p&gt;

&lt;p&gt;A challenge when working alone (especially when you don't know particular areas like geometry or 3D rendering) is detecting sloppy code. You can't really code review your own blind spots.&lt;/p&gt;

&lt;p&gt;From my experience with this codebase, the AI-generated code wasn't so much wrong as it was redundant: repetitive patterns, duplicated logic, correct inputs and outputs but with bloated inner workings. Honestly, this reflects what us humans tend to do anyway.&lt;/p&gt;

&lt;p&gt;There are still moments where I need to go back and do a full refactoring pass (specially before merging a PR) so I can ensure the code follows my general guidelines. Not super minuciously detailed, I'm human after all and my time is limited. It still requires deliberate effort to keep the codebase in shape and not let it become slop soup.&lt;/p&gt;

&lt;p&gt;Combined with how expressive and robust F# is, moving fast and safe is possible even when exploring new areas, as long as the domain is well modelled F# code will flow naturally, things will break in expected ways and the compiler will help you get back on track. The most troublesome part is when there's subtle logical bugs or lack of domain knowledge, but that's where human intuition and experience come into play.&lt;br&gt;
Even then AI assistants are good "rubber ducks" so rather than a "just code" tool, I also do a lot of back and forth to validate, refine and challenge my own understanding of the problem at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In just over a couple months, &lt;code&gt;Kipo&lt;/code&gt; has evolved from a basic F# architectural prototype into a functional 3D RPG with complex systems for AI, combat, and inventory. This journey proves that F# is not just viable for game development but can offer a powerful, type-safe, and expressive way to build complex interactive systems.&lt;/p&gt;

&lt;p&gt;If you are interested in game development with F# and MonoGame, I highly encourage you to give it a try. The ecosystem might be smaller than C#'s, but the power of the language makes up for it.&lt;/p&gt;

&lt;p&gt;If you want me to share more of F# + AI personal experiences and personal approaches let me know in the comments or ping me on my socials!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>gamedev</category>
      <category>monogame</category>
      <category>fsadvent</category>
    </item>
    <item>
      <title>Local migrations for embedded SQLite in F#</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Sat, 02 Mar 2024 05:01:58 +0000</pubDate>
      <link>https://forem.com/tunaxor/local-migrations-for-embedded-sqlite-in-f-3e51</link>
      <guid>https://forem.com/tunaxor/local-migrations-for-embedded-sqlite-in-f-3e51</guid>
      <description>&lt;p&gt;Hello there, once again we're back with more F# goodies.&lt;/p&gt;

&lt;p&gt;There's a very minimal chance that you might remember one of my F# OSS projects called &lt;strong&gt;Migrondi&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/Migrondi" rel="noopener noreferrer"&gt;
        Migrondi
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Super simple SQL Migrations Tool for SQLite, PostgreSQL, MySQL and SQL Server
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/AngelMunoz/Migrondi/workflows/.NET%20Core/badge.svg?branch=master"&gt;&lt;img src="https://github.com/AngelMunoz/Migrondi/workflows/.NET%20Core/badge.svg?branch=master" alt=".NET Core"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Currently Working on the next version to also add better support for the VSCode Extension!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Migrondi&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Migrondi is a SQL Migrations tool designed to be simple and execute simple migrations. Write SQL and execute SQL against your database.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No need to install it, use it from &lt;a href="https://marketplace.visualstudio.com/items?itemName=tunaxor-apps.migrondi-vscode" rel="nofollow noopener noreferrer"&gt;VSCode&lt;/a&gt;! &lt;a href="https://github.com/AngelMunoz/migrondi-vscode" rel="noopener noreferrer"&gt;https://github.com/AngelMunoz/migrondi-vscode&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Migrondi Runs on &lt;code&gt;Linux-x64&lt;/code&gt;, &lt;code&gt;Linux-arm64&lt;/code&gt;, &lt;code&gt;Windows-x64&lt;/code&gt;, and &lt;code&gt;MacOS-x64&lt;/code&gt; (intel based)&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;For Non .NET users&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;Grab the binary from the releases page or build from source and put it on your &lt;code&gt;PATH&lt;/code&gt;, that way the command is available globally e.g.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; you can put this at the end of your ~/.bashrc&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; $HOME/Apps/migrondi is a directory where you have downloaded your "Migrondi" binary&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; MIGRONDI_HOME=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-smi"&gt;$HOME&lt;/span&gt;/Apps/migrondi&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; PATH=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-smi"&gt;$PATH&lt;/span&gt;:&lt;span class="pl-smi"&gt;$MIGRONDI_HOME&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;For .NET users&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;you can now install this as a global/local tool as well&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;dotnet tool install --global Migrondi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Init&lt;/h3&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/Migrondi" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It is fair as I wrote it over four years ago, So I'll give you a quick reminder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Migrondi is a SQL Migrations tool designed to be simple and execute simple migrations. Write SQL and execute SQL against your database.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I initially wrote it as a CLI tool, but in my last attempt I tried to expose the core parts as a library, mostly to support programmatic cases as well as to be able to change the backing storage from file system to something else.&lt;/p&gt;

&lt;p&gt;For the most part supports some of the major databases like SQL Server, MySQL, PostgreSQL, and SQLite.&lt;/p&gt;

&lt;p&gt;Having that said... Have you ever written a CLI tool, or any kind of user facing application which stores local data? If you have, you might have faced the problem of managing a local database instance, perhaps a NoSQL embedded alternative or something painful related to text file formats.&lt;/p&gt;

&lt;p&gt;Personally, I used to use &lt;a href="https://www.litedb.org/" rel="noopener noreferrer"&gt;LiteDB&lt;/a&gt; which is a NoSQL version which in v4 paired very nicely with F# thanks to Zaid's lovely &lt;a href="https://github.com/Zaid-Ajaj/LiteDB.FSharp" rel="noopener noreferrer"&gt;LiteDB.FSharp&lt;/a&gt; library. Sadly, when v5 showed up, a lot of the F# niceties were lost given how the API was changed and v5 was not very F# friendly. You can still use it of course but you fall back to more unsafe F# code which is not ideal.&lt;/p&gt;

&lt;p&gt;So the next natural candidate is SQLite, which is a very popular choice for local databases and also a safe bet for F# developers as there's a bunch of F# libraries that support SQL very well. My issue in general with SQL databases is having to manage the schema and migrations prior to get hacking around with your app.&lt;/p&gt;

&lt;p&gt;For applications that have a remote database that's fine, you can use a tool like Migrondi, Flyway, Liquibase, etc. to manage your migrations and you're good to go but, for local databases how do you manage your schema and migrations?.&lt;/p&gt;

&lt;p&gt;I'm sure there are solutions out there that I'm not aware of, but it was a big driver for me to add library support to Migrondi, so I could use it to manage local databases as well.&lt;/p&gt;

&lt;p&gt;So let's see how we can use Migrondi to manage a local SQLite database. in your project!&lt;/p&gt;

&lt;p&gt;For this we'll be looking at an example I already wrote for you!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/Siquelin" rel="noopener noreferrer"&gt;
        Siquelin
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WIP&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This repository is meant to be source material for a future set of blog entries for more F# goodies.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Usage&lt;/h1&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Description:
  A simple time tracking cli example

Usage:
  Siquelin [command] [options]

Options:
  --version       Show version information
  -?, -h, --help  Show help and usage information

Commands:
  log &amp;lt;day&amp;gt;                      Start a new work day
  item &amp;lt;start&amp;gt; &amp;lt;finish&amp;gt; &amp;lt;label&amp;gt;  Add a new shift item
  list-items &amp;lt;day&amp;gt;               List shift items for a day []
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/Siquelin" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Which for the intended purposes, I'd recommend you to set github at the tag &lt;code&gt;01-exploring-a-foreign-codebase&lt;/code&gt; or clone it and checkout to that tag as that will be the code this post will be talking about.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to learn a little bit more on "How to explore a foreign codebase" I'd recommend you to check out this post: &lt;a href="https://dev.to/tunaxor/exploring-a-foreign-f-codebase-3og"&gt;Exploring an F# foreign codebase&lt;/a&gt; which contains a few tips and tricks to get you started with unknown to you F# codebases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our particular case we're only interested in the &lt;code&gt;Types.fs&lt;/code&gt;, &lt;code&gt;Migrations.fs&lt;/code&gt;, &lt;code&gt;Program.fs&lt;/code&gt; and the &lt;code&gt;Commands.Hidden&lt;/code&gt; in &lt;code&gt;Commands.fs&lt;/code&gt; module.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: &lt;code&gt;dotnet add package Migrondi.Core --prerelease&lt;/code&gt; to add the new bits to your project in case you want to try this yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's start with the &lt;code&gt;Migrations.fs&lt;/code&gt; file, which contains a few helper functions with &lt;code&gt;Migrondi.Core&lt;/code&gt; to manage our migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Migrations&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Core&lt;/span&gt;
  &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logging&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="n"&gt;getMigrondi&lt;/span&gt; &lt;span class="n"&gt;rootDir&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MigrondiFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Initialize&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;migrondi&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;runMigrations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IMigrondi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addNewMigration&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IMigrondi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't want to customize Migrondi in any way (meaning that you want to use Migrondi just like you would use the CLI tool), then you can use &lt;code&gt;MigrondiFactory&lt;/code&gt; to create a new instance of Migrondi and then call &lt;code&gt;Initialize&lt;/code&gt; to load the migrations from the root directory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MigrondiFactory&lt;/code&gt; function takes a &lt;code&gt;MigrondiConfig&lt;/code&gt; item and a &lt;code&gt;string&lt;/code&gt; which is the root directory where the migrations are located.&lt;/p&gt;

&lt;p&gt;The run migrations function is not particularly complex, it simply calls &lt;code&gt;MigrationsList()&lt;/code&gt; from an &lt;code&gt;IMigrondi&lt;/code&gt; instance and then filters the pending migrations. If there are any pending migrations, it calls &lt;code&gt;RunUp&lt;/code&gt; to apply them and logs the applied migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;runMigrations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IMigrondi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;hasPending&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MigrationsList&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Pending&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;applied&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MigrationRecord&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hasPending&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunUp&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;applied&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Applied migration {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;addNewMigration&lt;/code&gt; function is also quite short, it mainly calls Migrondi to generate a new migration file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;addNewMigration&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IMigrondi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunNew&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Generated migration {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For our simple application, we don't need anything else, once we're able to access an &lt;code&gt;IMigrondi&lt;/code&gt; instance we can run migrations and add new ones.&lt;/p&gt;

&lt;p&gt;Now, let's take a look at the &lt;code&gt;Types.fs&lt;/code&gt; file, which contains the types that we'll be using to interact with the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ShiftItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;workDayId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;
  &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our CLI app focuses on being able to save "shift items" for a given "work day". and basically for any work day we can have multiple shift items.&lt;/p&gt;

&lt;p&gt;Which should be listable when required.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: Keep in mind that this example is for a use case where the user is generating the data, but if you're generating the data yourself for configuration, or for pre/post processing, you can follow this approach as well!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without diving into much detail, our app has a &lt;code&gt;new-migration&lt;/code&gt; hidden command, it is callable from the CLI and is enabled only in &lt;code&gt;DEBUG&lt;/code&gt; builds.&lt;/p&gt;

&lt;p&gt;When we're developing our app we are able to call the following command to generate a new migration file to manage our SQLite database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run -- new-migration initial-types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a new migration file in the &lt;code&gt;migrations&lt;/code&gt; directory which may look like this &lt;code&gt;./migrations/initial-types_1708665113196.sql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Our file content is about creating the tables for our types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- MIGRONDI:NAME=initial-types_1708665113196.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- MIGRONDI:TIMESTAMP=1708665113196&lt;/span&gt;
&lt;span class="c1"&gt;-- ---------- MIGRONDI:UP ----------&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;work_days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="n"&gt;autoincrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;shift_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="n"&gt;autoincrement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;work_day_id&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;end_time&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="k"&gt;foreign&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;work_day_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;work_days&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- ---------- MIGRONDI:DOWN ----------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file we have two sections&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MIGRONDI:UP&lt;/code&gt; which contains the SQL to create the tables.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MIGRONDI:DOWN&lt;/code&gt; which is empty, but it would contain the SQL to drop the tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The golden rule in Migrondi is to not remove anything from &lt;code&gt;MIGRONDI:UP&lt;/code&gt; and above and not to remove the particular line where &lt;code&gt;MIGRONDI:DOWN&lt;/code&gt; is located.&lt;/p&gt;

&lt;p&gt;The first part is basically metadata for migrondi to know what to do with the file, the content between &lt;code&gt;MIGRONDI:UP&lt;/code&gt; and &lt;code&gt;MIGRONDI:DOWN&lt;/code&gt; is the SQL that Migrondi will execute when running the migration, and below &lt;code&gt;MIGRONDI:DOWN&lt;/code&gt; is the SQL that Migrondi will execute when rolling back the migration.&lt;/p&gt;

&lt;p&gt;Many folks don't like rolling back, so feel free to leave it empty, it is your choice.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Program.fs&lt;/code&gt; theres a single line that is important for us to be able to use Migrondi.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// run this at the start of the app regardless of the commands&lt;/span&gt;
  &lt;span class="c1"&gt;// this will ensure that the database is up to date&lt;/span&gt;
  &lt;span class="nn"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runMigrations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the app starts, it will run the migrations, and then it will continue with the rest of the app.&lt;/p&gt;

&lt;p&gt;And that's it! As long as we tell Migrondi where our migrations are and where is the database, we can use Migrondi to manage our local SQLite database.&lt;/p&gt;

&lt;p&gt;Let's check the &lt;code&gt;Env.fs&lt;/code&gt; file to see what are the particular values we're picking up from the environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;loggerFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;getEnvLocations&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="c1"&gt;// The appData let binding, refers to where are we going to store our database&lt;/span&gt;
  &lt;span class="c1"&gt;// in the user's machine. We're using the LocalApplicationData folder to avoid&lt;/span&gt;
  &lt;span class="c1"&gt;// polluting the user's home directory.&lt;/span&gt;
  &lt;span class="c1"&gt;// ~/.local/share/siquelin in *nix systems&lt;/span&gt;
  &lt;span class="c1"&gt;// C:\Users\&amp;lt;USER&amp;gt;\AppData\Local\siquelin in Windows&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;appData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nn"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetFolderPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;SpecialFolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LocalApplicationData&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="s2"&gt;"siquelin"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// The full path to our database file including it's name.&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dbPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"siquelin.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// appDir is the directory where the app is located, we use this to locate the migrations&lt;/span&gt;
  &lt;span class="c1"&gt;// in dev time we will be invoking these migrations from the project directory&lt;/span&gt;
  &lt;span class="c1"&gt;// so we just pick the current directory&lt;/span&gt;
  &lt;span class="c1"&gt;// in release time we will have our migrations next to our assembly&lt;/span&gt;
  &lt;span class="c1"&gt;// so we want to use that instead of the current directory&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;appDir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;DEBUG&lt;/span&gt;
    &lt;span class="c1"&gt;// At dev time we want to be in the project directory&lt;/span&gt;
    &lt;span class="c1"&gt;// as they will be copied to the output directory when building for release&lt;/span&gt;
    &lt;span class="c1"&gt;// ~/projects/Siquelin&lt;/span&gt;
    &lt;span class="nn"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CurrentDirectory&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c1"&gt;// where is our assembly located&lt;/span&gt;
    &lt;span class="c1"&gt;// migrations will be in the same directory (e.g. {AppContext.BaseDirectory}/migrations/*.sql)&lt;/span&gt;
    &lt;span class="nn"&gt;AppContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BaseDirectory&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="n"&gt;endif&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;appDirectory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;appDir&lt;/span&gt;
    &lt;span class="n"&gt;appDataDirectory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;appData&lt;/span&gt;
    &lt;span class="n"&gt;databasePath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dbPath&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Siquelin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// get the locations for the app as we saw in the previous function&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getEnvLocations&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// create the database directory if it doesn't exist&lt;/span&gt;
  &lt;span class="c1"&gt;// to avoid exceptions when trying to create the database file&lt;/span&gt;
  &lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetDirectoryName&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;databasePath&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateDirectory&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// use the default configuration but override the connection string&lt;/span&gt;
    &lt;span class="nn"&gt;MigrondiConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Data Source={locations.databasePath};"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Get an instance of migrondi, the migrondi instance will use the app directory&lt;/span&gt;
  &lt;span class="c1"&gt;// as the root directory for the migrations, and the connection string to connect to the database&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMigrondi&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appDirectory&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getConnection&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IDbConnection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Sqlite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SqliteConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Workday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;getConnection&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shiftItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;ShiftItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;getConnection&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
    &lt;span class="n"&gt;migrondi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;
    &lt;span class="n"&gt;workdays&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt;
    &lt;span class="n"&gt;shiftItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shiftItems&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here on you can start running the other commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run -- item 10:00 11:00
dotnet run -- item 11:30 12:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then list the items&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run -- list-items
info: Siquelin[0]
      Listing shift items for: Saturday, February 24, 2024
info: Siquelin[0]
      Shift item: 10:00 - 11:00
info: Siquelin[0]
      Shift item: 11:30 - 12:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before deploying this to production, we need to be sure that our migrations will be available for the app to run, so we will a couple of lines in our &lt;code&gt;Siquelin.fsproj&lt;/code&gt; file to include the migrations in the output directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;None&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"./migrations/*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;Always&lt;span class="nt"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will ensure that our migrations are copied to the output directory when we build the app, and live next to the assembly.&lt;/p&gt;

&lt;p&gt;Yay! We have a local database, we're managing it with Migrondi!&lt;/p&gt;

&lt;p&gt;However, the eagle eyed reader might have noticed that we can't identify the shift items, they are just a bunch of times, and our users might want to know which shift item is which.&lt;/p&gt;

&lt;p&gt;The bad news is that we've already shipped this and our users are already logging their shift items... so we can't simply blow away the database and start from scratch the next time we run the app, once again Migrondi comes to the rescue!&lt;/p&gt;

&lt;p&gt;We can add a new migration to add a new column in the existing tables, and then we can update the data to fill in the new column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run -- new-migration add-name-column
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create something like &lt;code&gt;add-name-column_1708811304117.sql&lt;/code&gt;, then we can add the following migration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- MIGRONDI:NAME=add-name-column_1708811304117.sql&lt;/span&gt;
&lt;span class="c1"&gt;-- MIGRONDI:TIMESTAMP=1708811304117&lt;/span&gt;
&lt;span class="c1"&gt;-- ---------- MIGRONDI:UP ----------&lt;/span&gt;

&lt;span class="c1"&gt;-- We're adding new columns to the database to store the name of the item.&lt;/span&gt;
&lt;span class="c1"&gt;-- and attempt to backfill&lt;/span&gt;

&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;work_days&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="n"&gt;item_name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;work_days&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;item_name&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;shift_items&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="n"&gt;item_name&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;shift_items&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;item_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;item_name&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- ---------- MIGRONDI:DOWN ----------&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding that migration, we can update our types&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ShiftItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;workDayId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;
  &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the rest of the code updates.&lt;br&gt;
The next time we run the app, the migrations will be applied and the new columns will be added to the database, and the data will be updated to fill in the new columns.&lt;/p&gt;

&lt;p&gt;Let's move forward to the &lt;a href="https://github.com/AngelMunoz/Siquelin/tree/02-siquelin-upsert-existing" rel="noopener noreferrer"&gt;02-siquelin-upsert-existing&lt;/a&gt; tag in the repository to see the updated implementation.&lt;/p&gt;

&lt;p&gt;In regards to our migration management, nothing changed other than the migration we added, and the types we updated. the rest were application and business logic changes.&lt;/p&gt;

&lt;p&gt;Running the new commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run -- item 17:55 20:15 "watch netflix"
dotnet run -- item 17:00 17:30 "Bathroom"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then listing the items&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run -- list-items
info: Siquelin[0]
      Listing shift items for: Saturday, February 24, 2024
info: Siquelin[0]
      Shift item: 'watch netflix': 17:55 - 20:15
info: Siquelin[0]
      Shift item: 'Bathroom': 17:00 - 17:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if our users typed the wrong command (as it is a breaking change for them because a new required argument as added to the command line interface), the migrations will be applied and the data will be updated to fill in the new columns.&lt;/p&gt;

&lt;p&gt;This is done transparently to the users, and we can be sure that the database is up to date.&lt;/p&gt;

&lt;p&gt;That's how we can use Migrondi to manage a local SQLite database in our F# application. There are of course alternative approaches to this and hopefully Migrondi is up to the task for your use case. If not, I welcome you to open an issue in the repository for your potential contribution or discussions related to what you're trying to do.&lt;/p&gt;

&lt;p&gt;Feedback and user testing is always welcome, so if you have any thoughts or questions, feel free to reach out to me on twitter or open an issue in the repository.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>sqlite</category>
      <category>sample</category>
    </item>
    <item>
      <title>Exploring a foreign F# codebase</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Sat, 24 Feb 2024 16:26:36 +0000</pubDate>
      <link>https://forem.com/tunaxor/exploring-a-foreign-f-codebase-3og</link>
      <guid>https://forem.com/tunaxor/exploring-a-foreign-f-codebase-3og</guid>
      <description>&lt;p&gt;Hello folks, here we are again with more F# content for you!&lt;/p&gt;

&lt;p&gt;I have a few things in mind that I want to write about relative to some of my older projects, but I'm not settled in stone yet.&lt;/p&gt;

&lt;p&gt;This blog post is going to be part of a series called "Dissecting an F# codebase" where I'll try to tell you how to approach new codebases, what to look for and how to get a handle on them. Today's topic is the first one and relatively simple: "This place is inmense, how do I even begin to explore it!?".&lt;/p&gt;

&lt;p&gt;For that we'll be working with &lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/Siquelin" rel="noopener noreferrer"&gt;
        Siquelin
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;WIP&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This repository is meant to be source material for a future set of blog entries for more F# goodies.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Usage&lt;/h1&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Description:
  A simple time tracking cli example

Usage:
  Siquelin [command] [options]

Options:
  --version       Show version information
  -?, -h, --help  Show help and usage information

Commands:
  log &amp;lt;day&amp;gt;                      Start a new work day
  item &amp;lt;start&amp;gt; &amp;lt;finish&amp;gt; &amp;lt;label&amp;gt;  Add a new shift item
  list-items &amp;lt;day&amp;gt;               List shift items for a day []
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/Siquelin" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;This is a small project I wrote a few days ago in order to produce a series of blog posts, some about &lt;a href="https://github.com/AngelMunoz/Migrondi" rel="noopener noreferrer"&gt;Migrondi&lt;/a&gt; and some about this series.&lt;/p&gt;

&lt;p&gt;In case the contents of the project have changed at the time you're reading this, please check the git tag: &lt;a href="https://github.com/AngelMunoz/Siquelin/tree/01-exploring-a-foreign-codebase" rel="noopener noreferrer"&gt;&lt;code&gt;01-exploring-a-foreign-codebase&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding a cool F# project to explore
&lt;/h2&gt;

&lt;p&gt;While you can follow these tips with the stated project above, please feel free to check it out with any project you already have in mind or new ones to see if this will work for you or not.&lt;/p&gt;

&lt;p&gt;The first step when you have a project already in mind, is to simply visit their repository and check out what the &lt;code&gt;README&lt;/code&gt; has in place&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  WIP
&lt;/h2&gt;

&lt;p&gt;This repository is meant to be source material for a future set of blog entries for more F# goodies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Oh great... that's quite useful, isn't it? 😅&lt;/p&gt;

&lt;p&gt;Hmm maybe file structure?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.config
.vscode
migrations
.editorconfig
.gitignore
Commands.fs
Database.fs
Env.fs
Extensions.fs
Migrations.fs
Program.fs
README.md
Siquelin.fsproj
Types.fs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes... but no. GitHub doesn't help very much in this department as it doesn't have any notion of how the files are used. If you come from other languages you also know that Files can be deceiving as they don't tell much about the codebase.&lt;/p&gt;

&lt;p&gt;Well, here's one of the cool things F# offers for you: The often controversial "Top-Down File Ordering" requirement. Many folks when they come to F# from other languages are quite annoyed by this and is seen as a limitation, which perhaps it is! But it also has a very cool side effect: It gives you a very clear idea of how the codebase is structured.&lt;/p&gt;

&lt;p&gt;So rather than doing what we've tried so far, let us check the &lt;code&gt;Siquelin.fsproj&lt;/code&gt; file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: Keep in mind that larger projects may have multiple &lt;code&gt;.fsproj&lt;/code&gt; files. In those cases, you need to determine which one is the main project and go from there.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net8.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Extensions.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Types.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Migrations.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Database.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Env.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Commands.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Program.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Donald"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"10.0.2"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"FSharp.SystemCommandLine"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"0.17.0-beta4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Migrondi.Core"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.0.0-beta-010"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;None&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"./migrations/*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;CopyToOutputDirectory&amp;gt;&lt;/span&gt;Always&lt;span class="nt"&gt;&amp;lt;/CopyToOutputDirectory&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/None&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh! ok, ok cool, this is a bit more useful. From checking this file we can see that the project fills the following bullets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a console project (otherwise it wouldn't have the output type node)&lt;/li&gt;
&lt;li&gt;It targets dotnet 8&lt;/li&gt;
&lt;li&gt;The entry point is &lt;code&gt;Program.fs&lt;/code&gt; (top-down file ordering!)&lt;/li&gt;
&lt;li&gt;It has 3 dependencies: &lt;code&gt;Donald&lt;/code&gt;, &lt;code&gt;FSharp.SystemCommandLine&lt;/code&gt; and &lt;code&gt;Migrondi.Core&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It is copying the migrations folder to the output directory&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: You can try this with the &lt;a href="https://github.com/Zaid-Ajaj/Feliz/blob/master/Feliz/Feliz.fsproj" rel="noopener noreferrer"&gt;Feliz&lt;/a&gt; codebase, which is larger and has multiple &lt;code&gt;.fsproj&lt;/code&gt; files. But as you will find out, the project structure is laid out just like we've seen here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We can now go back to the Top-Down file ordering requirement and quickly determine that &lt;code&gt;Extensions.fs&lt;/code&gt; is the most accessible file in the project as it is at the top and any other file below it will have knowledge from this file. The last file is &lt;code&gt;Program.fs&lt;/code&gt; which must have the entry point for the application given that we've already determined that we're working with a console application.&lt;/p&gt;

&lt;p&gt;Any other file in-between them may tell the structure of the application. But keep in mind that just because a file is ordered above another it doesn't mean it is using it's sibling contents. Sometimes these files don't use each other and may be using the contents of the files above them instead.&lt;/p&gt;

&lt;p&gt;Let's check our entry point first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;FSharp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SystemCommandLine&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Siquelin&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Types&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Migrations&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// run this at the start of the app regardless of the commands&lt;/span&gt;
  &lt;span class="c1"&gt;// this will ensure that the database is up to date&lt;/span&gt;
  &lt;span class="nn"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runMigrations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;rootCommand&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"A simple time tracking cli example"&lt;/span&gt;

    &lt;span class="c1"&gt;// set the main handler to do nothing&lt;/span&gt;
    &lt;span class="n"&gt;setHandler&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;

    &lt;span class="c1"&gt;// add a subcommand to list shifts&lt;/span&gt;

    &lt;span class="n"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logDay&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logItem&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listItemsForDay&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;DEBUG&lt;/span&gt;
    &lt;span class="c1"&gt;// only allow this command in debug mode as it is meant for dev purposes&lt;/span&gt;
    &lt;span class="n"&gt;addCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Commands&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Hidden&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newMigration&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="n"&gt;endif&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh, looks like the author at least gave us some comments to work with and figure out what the thing is doing. It seems like it's a CLI application for time tracking. It also seems like it is running some sort of migrations even before the commands are parsed and executed which is a bit questionable but we don't know the reasons behind that&lt;/p&gt;

&lt;p&gt;So from this file we've at least determined that it is a CLI application which has four commands and one is hidden.&lt;br&gt;
We can pick the next file in the list or we can check the &lt;code&gt;Commands&lt;/code&gt; module to feed our curiosity and coincidentally the &lt;code&gt;Commands&lt;/code&gt; module lives in the &lt;code&gt;Commands.fs&lt;/code&gt; file!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: For larger codebases with more history it is likely that the &lt;code&gt;Program.fs&lt;/code&gt; file will have a lot of orchestration and logic as well. given that it is often where everything clashes and starts, for example the &lt;a href="https://github.com/fable-compiler/Fable/blob/98bf8288b154cbae4ebfc29db79ad9ac163906e1/src/Fable.Cli/Entry.fs" rel="noopener noreferrer"&gt;Fable&lt;/a&gt; Entrypoint is in &lt;code&gt;Entry.fs&lt;/code&gt; and it contains a lot of code. The best you can do always is to start at the bottom of the file and work your way up. Remember: Everything at the bottom uses what has already been defined at the top so there are no circular dependencies or random functions/types at the bottom that can trip you off, everything comes from the top!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the file you're going to check next is big, or contains a lot of modules, I'd recommend you to fold/collapse via your editor the modules and check them one by one.&lt;/p&gt;

&lt;p&gt;If we apply the tip from above then the &lt;code&gt;Commands.fs&lt;/code&gt; file would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;Siquelin&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;FSharp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SystemCommandLine&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Types&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Parsing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Handlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is very helpful, otherwise this file would blast us 200 lines of code to the face directly filling us with a lot of context we don't understand yet which may become quickly confusing!&lt;/p&gt;

&lt;p&gt;Following from our previous experience, we knew there was a &lt;code&gt;Commands.logDay&lt;/code&gt; function, so we can check the &lt;code&gt;Commands&lt;/code&gt; module and see what it does.&lt;br&gt;
Once again following the same tip, we can fold/collapse the module and check the &lt;code&gt;logDay&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logDay&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logItem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listItemsForDay&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Hidden&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh, so it seems like the &lt;code&gt;Commands&lt;/code&gt; module is just a collection of functions that are being used in the &lt;code&gt;Program.fs&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;As a word of caution, these functions seem to be asking for an &lt;code&gt;Env.AppEnv&lt;/code&gt; type, it can be easy to start looking into what this &lt;code&gt;AppEnv&lt;/code&gt; is and quickly get derailed looking into more, and more, and more code! My personal recommendation is to first keep checking the function is doing and &lt;em&gt;how&lt;/em&gt; it is using this so called &lt;code&gt;AppEnv&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logDay&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;argument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Argument&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"The day to log, in the format of 'yyyy-MM-dd'"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"log"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"Start a new work day"&lt;/span&gt;

        &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argument&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;setHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;Parsing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dayParser&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failwith&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Handlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workdays&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;cmd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like we're creating a command with a description and an argument, and then we're setting a handler that is using the &lt;code&gt;Parsing.dayParser&lt;/code&gt; and &lt;code&gt;Handlers.logDay&lt;/code&gt; functions.&lt;/p&gt;

&lt;p&gt;From here, it looks like the first parsing function is going to be composed with the &lt;code&gt;Handlers.logDay&lt;/code&gt; function thanks to the &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; operator.&lt;br&gt;
We don't know the shape of the &lt;code&gt;Handlers.logDay&lt;/code&gt; function yet but now at least we know that the so called &lt;code&gt;AppEnv&lt;/code&gt; is some sort of dependency container as it has a logger and a workdays property.&lt;/p&gt;

&lt;p&gt;The following step would be to check the &lt;code&gt;Handlers&lt;/code&gt; module and see what the &lt;code&gt;logDay&lt;/code&gt; function is doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Handlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logDay&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logItem&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workDays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shiftItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listItemsForDay&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shiftItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh, similar to the &lt;code&gt;Commands&lt;/code&gt; module, it seems like the &lt;code&gt;Handlers&lt;/code&gt; module is just a collection of things and by the looks of it these are functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logDay&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"Logging a new work day: {day}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToLongDateString&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;workdays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! So it looks like the &lt;code&gt;logDay&lt;/code&gt; function is just logging a message and then calling the &lt;code&gt;create&lt;/code&gt; method on the &lt;code&gt;workdays&lt;/code&gt; service.&lt;/p&gt;

&lt;p&gt;Notice how we've been able to follow the code without doing any &lt;code&gt;Ctrl/Cmd+F&lt;/code&gt; or Go to definition or a similar action. the code so far has been very linear and hasn't done any kind of weird indirections or anything like that.&lt;/p&gt;

&lt;p&gt;Let's skip one of the tips above and check the &lt;code&gt;WorkdayService&lt;/code&gt; and see what the &lt;code&gt;create&lt;/code&gt; method is doing via go to definition.&lt;/p&gt;

&lt;p&gt;It took us straight into the &lt;code&gt;Types.fs&lt;/code&gt; file and we found this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Types&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ShiftItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;workDayId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
  &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;
  &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logging&lt;/span&gt;
  &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Core&lt;/span&gt;

  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;SiquelinDataLocations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;appDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;appDataDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;databasePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemQueryError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;WorkDayNotFound&lt;/span&gt;

  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;TimeOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemQueryError&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ShiftItem&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemQueryError&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;AppEnv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SiquelinDataLocations&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;
    &lt;span class="n"&gt;migrondi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IMigrondi&lt;/span&gt;
    &lt;span class="n"&gt;workdays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt;
    &lt;span class="n"&gt;shiftItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ShiftItemService&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like a file defining some types and a module with even more types... Aha there it is!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;WorkDay&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh great just an interface definition 🫠... where is this being created? Maybe if I click in &lt;code&gt;See usages&lt;/code&gt; I can find it!&lt;br&gt;
Well... yes, you may find it that way, but do you want to do that?&lt;/p&gt;

&lt;p&gt;You just followed up to check up a type in a function parameter and you're now exposed to this other information which may be a distracting factor if you're trying to understand how it works, it might have been useful and give you other contexts which is fine the more seasoned F# developer you are. However, if you're still not comfortable enough with F# this might be introducing you to more noise and overload you making you think: "Hmm I better check out this later there's a ton of stuff there...".&lt;/p&gt;

&lt;p&gt;We've felt for this trap so... how do we get out of here?&lt;br&gt;
Let's go back to the &lt;code&gt;Handlers&lt;/code&gt; module and check the &lt;code&gt;logDay&lt;/code&gt; function again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logDay&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkdayService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateOnly&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now know that our &lt;code&gt;logDay&lt;/code&gt; function is asking for a &lt;code&gt;WorkdayService&lt;/code&gt; and a &lt;code&gt;ILogger&lt;/code&gt; and thanks to our "go to definition" click, we also know that the &lt;code&gt;WorkdayService&lt;/code&gt; is being part of the &lt;code&gt;AppEnv&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;But wait, we already knew that, didn't we? We knew that the &lt;code&gt;AppEnv&lt;/code&gt; type was being used in the &lt;code&gt;Program.fs&lt;/code&gt; to create the commands and within the &lt;code&gt;Commands&lt;/code&gt; module we also found out that the &lt;code&gt;AppEnv&lt;/code&gt; type had a &lt;code&gt;workdays&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Let's check the &lt;code&gt;Program.fs&lt;/code&gt; file once again but let's check the &lt;code&gt;Env&lt;/code&gt; module this time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getEnv&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh, great looks like there's a function in the &lt;code&gt;Env&lt;/code&gt; module that is creating the &lt;code&gt;AppEnv&lt;/code&gt; type for us. Let's check it out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logging&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Migrondi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Core&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Env&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Siquelin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Migrations&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Data&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;loggerFactory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;getEnvLocations&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh, this file is laid out differently than the others, while sure it has an &lt;code&gt;Env&lt;/code&gt; module, it looks like the whole file is a single module instead. It also looks like it is creating the &lt;code&gt;AppEnv&lt;/code&gt; type for us and it is using a &lt;code&gt;loggerFactory&lt;/code&gt; and a &lt;code&gt;getEnvLocations&lt;/code&gt; function to do so.&lt;/p&gt;

&lt;p&gt;Let's expand the &lt;code&gt;getEnv&lt;/code&gt; function and see what it is doing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getEnv&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AppEnv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Siquelin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getEnvLocations&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// create the database directory if it doesn't exist&lt;/span&gt;
  &lt;span class="c1"&gt;// to avoid exceptions when trying to create the database file&lt;/span&gt;
  &lt;span class="nn"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetDirectoryName&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;databasePath&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Directory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateDirectory&lt;/span&gt;
  &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;MigrondiConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Data Source={locations.databasePath};"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getMigrondi&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appDirectory&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getConnection&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IDbConnection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Sqlite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SqliteConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Workday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;getConnection&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shiftItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;ShiftItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;getConnection&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;locations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;locations&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;
    &lt;span class="n"&gt;migrondi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;migrondi&lt;/span&gt;
    &lt;span class="n"&gt;workdays&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workdays&lt;/span&gt;
    &lt;span class="n"&gt;shiftItems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shiftItems&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Welp, at least we now know where this &lt;code&gt;workdays&lt;/code&gt; implementation is coming from. Looks like it is being created from a &lt;code&gt;Database.Workday.factory&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Sheesh... we're out of the trap! we're back to the normal flow where we can check things from the bottom to the top and not get derailed by other things.&lt;/p&gt;

&lt;p&gt;But how would the non-trap version would have been?&lt;/p&gt;

&lt;p&gt;For that we would have taken just the same steps but in reverse.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'm in the &lt;code&gt;logDay&lt;/code&gt; function who is supplying these parameters?&lt;/li&gt;
&lt;li&gt;I'm in the &lt;code&gt;Commands.logDay&lt;/code&gt; function this is supplying me with the &lt;code&gt;env&lt;/code&gt; parameter, where is this being supplied?&lt;/li&gt;
&lt;li&gt;I'm in the &lt;code&gt;Program.fs&lt;/code&gt; file and I see that the &lt;code&gt;env&lt;/code&gt; is being created in the &lt;code&gt;Env&lt;/code&gt; module, let's check that out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then we would have been in the &lt;code&gt;Env&lt;/code&gt; module and continue our bottom to top exploration with folded members/modules. This would have prevented us from getting cognitive overload from information we don't necessarily need at this moment. Keep in mind that while those are two approaches to the same problem (figuring out how does &lt;code&gt;WorkdayService&lt;/code&gt; looks and it is implemented), I personally believe that one method is better for seasoned F# developers and the other is better for newcomers. Feel free to mix and match these techniques so far to your liking and see what works best for you.&lt;/p&gt;

&lt;p&gt;What do we know so far?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have a CLI application&lt;/li&gt;
&lt;li&gt;It has 4 commands, one of them is hidden&lt;/li&gt;
&lt;li&gt;It is running migrations before the commands are parsed and executed&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Commands&lt;/code&gt; module is using an &lt;code&gt;AppEnv&lt;/code&gt; type&lt;/li&gt;
&lt;li&gt;The cli commands are being created in the &lt;code&gt;Commands&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Commands&lt;/code&gt; module is using a &lt;code&gt;Parsing&lt;/code&gt; and &lt;code&gt;Handlers&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Handlers&lt;/code&gt; module is using a &lt;code&gt;WorkdayService&lt;/code&gt; and a &lt;code&gt;ShiftItemService&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;WorkdayService&lt;/code&gt; and &lt;code&gt;ShiftItemService&lt;/code&gt; are being created in the &lt;code&gt;Env&lt;/code&gt; module&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Env&lt;/code&gt; module is creating the &lt;code&gt;AppEnv&lt;/code&gt; type&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;AppEnv&lt;/code&gt; type is being used in as a dependency container which is created in the &lt;code&gt;Program.fs&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope that at this point you're spotting the discovering pattern and can start using it to explore foreign F# code bases. Perhaps that will ease a bit of stress when you're trying to understand a new codebase and potentially contribute to it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the desired &lt;code&gt;.fsproj&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Check the dependencies&lt;/li&gt;
&lt;li&gt;Check the top-down file ordering&lt;/li&gt;
&lt;li&gt;Check the last file in the list&lt;/li&gt;
&lt;li&gt;If the code is big, fold/collapse the contents and check them from the bottom to the top&lt;/li&gt;
&lt;li&gt;Visit the next module/file above what you're currently checking and repeat the process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is my personal way of exploring a new codebase, and I hope it helps you as well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: For applications, this is a good way to start, but for libraries it might slightly different. Libraries don't have "entry points" in the same way applications do, so while you might still want to check the &lt;code&gt;.fsproj&lt;/code&gt; file and the top-down file ordering The files in there might be sibling files and not sharing code between them, or there might be a set of indirections that you need to follow to understand how the library works. But I still find the bottom to top approach to be useful in those cases.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll stop here for the moment as this is just one entry in the series. Next time we'll talk about partial application and how it is used for dependency injection in the &lt;code&gt;Siquelin&lt;/code&gt; project which probably at this point you've already spotted it.&lt;/p&gt;

&lt;p&gt;In case you have any questions or comments, feel free to reach out to me on Twitter, Threads, The Fediverse or GitHub.&lt;br&gt;
I hope you've enjoyed this entry and I hope to see you in the next one!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>exploration</category>
      <category>software</category>
    </item>
    <item>
      <title>Revisiting WASM for F#</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Sat, 16 Dec 2023 14:36:04 +0000</pubDate>
      <link>https://forem.com/tunaxor/revisiting-wasm-for-f-38c1</link>
      <guid>https://forem.com/tunaxor/revisiting-wasm-for-f-38c1</guid>
      <description>&lt;p&gt;Hey there folks! It has been a while!&lt;/p&gt;

&lt;p&gt;As you may be aware with my content at this point I do a bunch of F# on my free time, on my not so &lt;em&gt;free time&lt;/em&gt; I do web development.&lt;/p&gt;

&lt;p&gt;I am a big fan of going with web components + plain (build-less) javascript whenever possible, so it is not surprising that I often favor things like the &lt;a href="https://fable.io/" rel="noopener noreferrer"&gt;Fable Compiler&lt;/a&gt;, where I can target my F# code directly to javascript and be as close to the native JS experience as possible, both for interop concerns and for ecosystem integration.&lt;/p&gt;

&lt;p&gt;And while JS is still the best course of action today to do front-end development, I think with the release of &lt;a href="https://dev.to%20raw%20`#dropthedot`%20endraw%20"&gt;dotnet&lt;/a&gt; 8 we're bound to re-visit how is Blazor doing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't know what &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; is the Tl;Dr would be that it is a framework for building dotnet apps for WASM and running dotnet code in the browser like F#, be sure to visit Microsoft docs for more information though!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Blazor itself!
&lt;/h3&gt;

&lt;p&gt;Before we go down into the F# code, I'd like to offer some of the new and shiny things that may be relevant for you when you evaluate if Blazor is good for your or your organization.&lt;/p&gt;

&lt;p&gt;It is no secret that the talented folks at Microsoft can produce and improve very well crafted software when executives don't get in their way and I think Blazor is one of those pieces. On each release since dotnet 6 things have improved from trimming, AoT, runtime size, and, performance among others, they have also introduced different ways to render wasm in your dotnet apps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-rendering - Server Only no interactivity&lt;/li&gt;
&lt;li&gt;Server Interactivity - Server Only, interactivity via websockets&lt;/li&gt;
&lt;li&gt;Client WASM - Client only loads the whole app + runtime in the browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Auto Interactive&lt;/code&gt; - best of the 3 worlds, pre-renders, provides interaction while wasm loads, once wasm loads it stays there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was going to write a slightly longer part on this topic but to be honest &lt;a href="https://dusted.codes/dotnet-blazor" rel="noopener noreferrer"&gt;Dustin wrote a much better and comprehensive post about it&lt;/a&gt;, so you should check him out.&lt;/p&gt;

&lt;p&gt;I agree with most of the conten on his post (specially when he says "I've never been the person who blindly champions every Microsoft technology without critical thought") except on two sides&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Auto Interactive&lt;/code&gt; mode is more akin to meta-frameworks like &lt;code&gt;Nextjs&lt;/code&gt;, &lt;code&gt;Remix&lt;/code&gt;, &lt;code&gt;Qwik&lt;/code&gt;, and not so with &lt;code&gt;SPA&lt;/code&gt; frameworks like &lt;code&gt;Angular&lt;/code&gt;, &lt;code&gt;React&lt;/code&gt; or &lt;code&gt;Vue&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that only client wasm should be compared to the later, and the former should be compared to blazor as a whole not just one segment of it, from that lens blazor isn't really that far behind and can actually provide a really performant and close to today JS frameworks, though Dev experience is still behind as there is no reliable hot reload (specially for F# where there's no hot reliad at all).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"The parallels with Silverlight are hard to ignore"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would also say that &lt;strong&gt;&lt;em&gt;IF&lt;/em&gt;&lt;/strong&gt; blazor worked on a browser plugin like silverlight did, today that's not the case it is built on the &lt;a href="https://webassembly.org/" rel="noopener noreferrer"&gt;webassembly&lt;/a&gt; standard which and it is being adopted in the browsers which means once it gets on the web, it is unlikely to ever go out again. Even if Microsoft themselves leave Blazor today, it can still work, the burden of creating a fork and keeping blazor alive will certainly be big but someone will be able to do that, just like the &lt;a href="https://opensilver.net/" rel="noopener noreferrer"&gt;open silver&lt;/a&gt; folks revived silverlight via wasm tech without any particular Microsoft involvement.&lt;/p&gt;

&lt;p&gt;That being said, let's talk about the stuff we're here for.&lt;/p&gt;

&lt;h3&gt;
  
  
  The F# bits
&lt;/h3&gt;

&lt;p&gt;Over the past month I went down to try two of the most prominent libraries that build on top of Blazor&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fsbolero.io/" rel="noopener noreferrer"&gt;Bolero&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://slaveoftime.github.io/Fun.Blazor.Docs/" rel="noopener noreferrer"&gt;Fun.Blazor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last time we saw them was at the frontend review I wrote a couple of years ago&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/tunaxor" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F35800%2F28156be5-48cb-4192-94a4-9c848f92a80a.jpg" alt="tunaxor"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tunaxor/exploring-the-f-frontend-landscape-13aa" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Exploring The F# Frontend Landscape&lt;/h2&gt;
      &lt;h3&gt;Angel Daniel Munoz Gonzalez ・ May 24 '22&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#fsharp&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webassembly&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;But it is also nothing too extreme.&lt;/p&gt;

&lt;p&gt;Essentially both options remain virtually the same in the sense that those simple example I showed there still work.&lt;/p&gt;

&lt;p&gt;If it is not too cynical I think that might already be a point for wasm as you you may know how easy is to JS codebases not work after a couple of years pass by 😆. Well yeah maybe to cynic but, it is indeed a strong point for wasm codebases and specially dotnet ones, if the code worked before it is almost guaranteed that it will be working in a few years without any modifications at all.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: The examples and use cases I'll show below require interactivity, either with WASM only mode or any other of the previously mentioned rendering modes in Blazor, so keep that in mind.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Bolero
&lt;/h4&gt;

&lt;p&gt;Let's dive into Bolero. This framework doesn't shy away from it's functional flavored style it goes straight into the MVU paradigm and it is the most common way you'll see code around, our previous example was the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;initModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="s2"&gt;"+"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// TheProgramComponent class implements the bits&lt;/span&gt;
    &lt;span class="c1"&gt;// that interop with blazor to make elmish work&lt;/span&gt;
    &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;ProgramComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="c1"&gt;// here we start our elmish loop and let it do it's thing&lt;/span&gt;
        &lt;span class="nn"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkSimple&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;initModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The elmish loop as usual provides predictability and a traceable code flow, however one of the main criticisms of MVU is the fact that once the code starts growing larger the update function becomes massive and starts losing the appeal.&lt;/p&gt;

&lt;p&gt;While there's arguments from both sides if that's a bad thing at all, Bolero offers a way to abstract certain parts of your code in &lt;code&gt;Elmish components&lt;/code&gt; which can be used to further de-couple the main update events with inner events in certain parts of your screen.&lt;/p&gt;

&lt;p&gt;Let's say for example that we want a modal-like component that can be&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entirely Dismissed&lt;/li&gt;
&lt;li&gt;Cencelled&lt;/li&gt;
&lt;li&gt;Accepted with value
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ModalError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Cancelled&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Dismissed&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ModalInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyModal&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;ElmishComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ModalInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ModalError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modalHeader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;
      &lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// For simplicity we'll let the parent control the visibility of this modal&lt;/span&gt;
        &lt;span class="c1"&gt;// so we'll set it to true rather than a dynamic value.&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``open``&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
        &lt;span class="c1"&gt;// You could certainly handle this locally within the component.&lt;/span&gt;
        &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;modalHeader&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="nc"&gt;Dismissed&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="s2"&gt;"❌"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autofocus&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
          &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="nc"&gt;Cancelled&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
          &lt;span class="s2"&gt;"Cancel"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
          &lt;span class="s2"&gt;"Ok"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With styling aside, the general structure of our modal would look like that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: As a general rule (regardless Blazor or Bolero) when you design components you want to pass the information into the components via props/parameters and any modifications hoisted to the parent via events/callbacks this enables unidirectional flow that makes it less prone to have bugs in your code and also de-couples the component of knowing what to do with the information once it "has changed".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In your parent you would use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// assuming we've defined already our message and our model before.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;translations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getTranslations&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;
  &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// content off your view&lt;/span&gt;

    &lt;span class="c1"&gt;// somewhere dispatch a message to open the modal&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;OpenModal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="s2"&gt;"Open Modal"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;showModal&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;|&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;ecomp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyModal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;_,&lt;/span&gt; &lt;span class="o"&gt;_&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"modalTitle"&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"modalMessage"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
          &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;CanContinue&lt;/span&gt;
          &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;ShowAlternativeFlow&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see this is a fairly simple way to keep constant with the MVU pattern but also allowing for internal messages to not leak into the main elmish loop.&lt;/p&gt;

&lt;p&gt;This is not new though and it is well documented in their website so you should check them out when you have a chance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: If you're interested in more Bolero specific examples for frontend use cases, let me know I'd be happy to write about those of that can help folks out there.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if you include a few updates for Elmish V4 like &lt;a href="https://elmish.github.io/elmish/docs/subscription.html#subscription-reusability" rel="noopener noreferrer"&gt;reusable subscriptions&lt;/a&gt; then the MVU pattern becomes easier to manage.&lt;/p&gt;

&lt;p&gt;Bolero clearly goes for the more functional side of frontend development and it works nice. There are still a few extra features I haven't talked about like remoting, routing but bolero is a solid choice if you want to bring your existing skills to the frontend development without getting lost into quest to learn how to do modern web development in the JS world.&lt;/p&gt;

&lt;h4&gt;
  
  
  Fun.Blazor
&lt;/h4&gt;

&lt;p&gt;This framework also supports MVU via its [Fun.Blazor.Elmish] package but let's take a look at what's in the box.&lt;/p&gt;

&lt;p&gt;If you have keep up with the frontend development landscape in JS-land then you may know that today's sauce is using "hooks" or "signals".&lt;br&gt;
Hooks are a react invention to react problems, and while they aren't really needed in many frameworks today (because they don't have react problems) they provide good developer experience to handle unidirectional flow and local state management and that made them quite popular even outside react.&lt;/p&gt;

&lt;p&gt;On the signals front, folks have gone full circle in their quest for wheel re-invention and currently we landed back into observables as the primitive for reactive state.&lt;br&gt;
Thankfully this time we're not entirely back at square 0 with signals, as one of the major proponents &lt;a href="https://twitter.com/RyanCarniato" rel="noopener noreferrer"&gt;Ryan Carniato&lt;/a&gt; is a very skilled person and more importantly he &lt;code&gt;remembers history&lt;/code&gt;, something the frontend folks tend to not do when they're iterating and stomping accedentally in concepts already tried in the past.&lt;/p&gt;

&lt;p&gt;This iteration of signals is very much appreciated and for the developers it looks almost like if they were using hooks.&lt;/p&gt;

&lt;p&gt;With that context, I'd like to show you [FSharp.Data.Adaptive] which is an abstraction for reactive data that works similarly to excel cells, and pretty much fits the shape of signals in the frontend&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyCounter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;FunBlazorComponent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Render&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="s2"&gt;"Counter: "&lt;/span&gt;

        &lt;span class="n"&gt;adaptiview&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
          &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"{counter}"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above our local state is handled by &lt;em&gt;changeable&lt;/em&gt; values, which will drive any other computations and the Adaptive model will take care of caching and checking for value changes that affect how often our views render, in the case above only the &lt;code&gt;adaptiview()&lt;/code&gt; node will be re-rendered any time the state changes, the rest of the contents remain static which contrasts with the MVU way to always re-render regardless of state changes.&lt;/p&gt;

&lt;p&gt;If we go back to the modal example from above it would look somewhat like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ModalError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Cancelled&lt;/span&gt;
  &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Dismissed&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ModalInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Modals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;MyModal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modalInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ModalInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ModalError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modalHeader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modalInfo&lt;/span&gt;
    &lt;span class="n"&gt;dialog&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// For simplicity we'll let the parent control the visibility of this modal&lt;/span&gt;
      &lt;span class="c1"&gt;// so we'll set it to true rather than a dynamic value.&lt;/span&gt;
      &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``open``&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
      &lt;span class="c1"&gt;// You could certainly handle this locally within the component.&lt;/span&gt;
      &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;modalHeader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;onAction&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="nc"&gt;Dismissed&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="s2"&gt;"❌"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;autofocus&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;onAction&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="nc"&gt;Cancelled&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="s2"&gt;"Cancel"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;onAction&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="s2"&gt;"Ok"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;FunBlazorComponent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Render&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// your view's content&lt;/span&gt;

      &lt;span class="c1"&gt;// trigger the dialog&lt;/span&gt;
      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;modalOpen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="o"&gt;}))&lt;/span&gt;
        &lt;span class="s2"&gt;"Open Modal"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;adaptiview&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WithSetter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;modalOpen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;modalInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modalInfo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;modalOpen&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
          &lt;span class="nn"&gt;Modals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MyModal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;modalInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
              &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
              &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;modalOpen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;modalOpen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
                &lt;span class="n"&gt;showAlternativeFlows&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it certainly makes it simpler to write and to reason about what is updating what, however it can introduce complexity when coordinating other parts/features of the view you're currently in, in our MVU example we just dispatched another message, and here we're calling other functions, which they may be callbacks or part of the view's current function.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: Similarly to the MVU message, if you are able to pas information as parameters/props and hoist state to the parent via events/callbacks then this will be simpler to reason about.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Interop with JS
&lt;/h3&gt;

&lt;p&gt;While both Bolero and Fun.Blazor provide means for your F# code to shine without the pain that may come with JS tooling (specially if you don't work with that in a day to day basis) until WASM gets DOM or Browser API access you still have to fallback to JS when that's the case. This is when we step back from the F# framework side and lean on the Blazor layer.&lt;/p&gt;

&lt;p&gt;For both Bolero an fun Blazor you should be able to use dependency injection by standard means.&lt;/p&gt;

&lt;p&gt;First let's create a common interface and a simple factory for our service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ILocalStorage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="n"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="n"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;LocalStorage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IServiceProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IJSRuntime&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ILocalStorage&lt;/span&gt;  &lt;span class="k"&gt;with&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="n"&gt;getItem&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s2"&gt;"localStorage.getItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObj&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="n"&gt;setItem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"localStorage.setItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[|&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;|])&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that factory, we can then register that in our &lt;code&gt;Startup.fs&lt;/code&gt; file or wherever we are currently registering our DI services&lt;/p&gt;

&lt;p&gt;To get a reference in a bolero component, it is fairly straight forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;jsRuntime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IJSRuntime&lt;/span&gt;
  &lt;span class="n"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILocalStorage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="c1"&gt;// in cases you already have registered service you can pass that to the update function&lt;/span&gt;
    &lt;span class="c1"&gt;// similar to constructor injection, but in this case it is partial application&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;FromParameter&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perform&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getItem&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="nc"&gt;SetContentInModel&lt;/span&gt;

    &lt;span class="c1"&gt;// For one of shots, you can simply invoke JS interop effect directly in the elmish loop&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;UsingBoleroHelpers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;OfJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perform&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jsRuntime&lt;/span&gt; &lt;span class="s2"&gt;"localStorage.getItem"&lt;/span&gt; &lt;span class="p"&gt;[|&lt;/span&gt; &lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;|]&lt;/span&gt; &lt;span class="nc"&gt;SetContentInModel&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// TheProgramComponent class implements the bits&lt;/span&gt;
    &lt;span class="c1"&gt;// that interop with blazor to make elmish work&lt;/span&gt;
    &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;ProgramComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt; &lt;span class="c1"&gt;// with get, set is important as the DI takes place on public properties&lt;/span&gt;
    &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nc"&gt;LocalStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILocalStorage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Unchecked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;_&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;localStorage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LocalStorage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;jsRuntime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JsRuntime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="c1"&gt;// partially apply the function dependencies&lt;/span&gt;
          &lt;span class="nn"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt;

        &lt;span class="c1"&gt;// here we start our elmish loop and let it do it's thing&lt;/span&gt;
        &lt;span class="nn"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkSimple&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;initModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Fun.Blazor the situation is very similar&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyComponent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;FunBlazorComponent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"some-key"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt; &lt;span class="o"&gt;{|&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;|}&lt;/span&gt;

  &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt; &lt;span class="c1"&gt;// with get, set is important as the DI takes place on public properties&lt;/span&gt;
  &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nc"&gt;LocalStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILocalStorage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Unchecked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultOf&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;_&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Render&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;LocalStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{|&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;|})&lt;/span&gt;
        &lt;span class="o"&gt;})&lt;/span&gt;
        &lt;span class="s2"&gt;"Invoke Function"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both Bolero and Fun.Blazor allow you to interop with javascript seamlessly, it however depends on you how you'd like to structure your programs and follow patterns, one way is simpler but can lead to more complex code eventually while the other is very straight forward but can be cumbersome once it gets up to certain height.&lt;/p&gt;

&lt;p&gt;Continuing with the topic at hand interop happens at the blazor layer, the parameters you pass in the &lt;code&gt;InvokeAsync&lt;/code&gt; function in &lt;code&gt;IJSRuntime&lt;/code&gt; must be JSON serializable, this serialization is not customizable as far as I know (there's better info at the MS docs in case I get that wrong). so you have to be careful what you're sending, for F# most types will work but discriminated unions will not, as that support has not been added yet to &lt;code&gt;System.Text.Json&lt;/code&gt; (which powers all of interop layer)&lt;/p&gt;

&lt;p&gt;In the previous interop example, we used global functions, and while nothing stops you from also adding your own namespace in the global window e.g.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MyNamespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;MySubSection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="c1"&gt;// call it like window.MyNamespace.MySubSection.doWork(a, b, c);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It promotes global polution and also you leave lazy loading out of the window, a better approach is to work straight with Javascript modules so, how about importing your own authored JS files? let's think about a module like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /js/my-script.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./lib/dependencies/dependency.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use our factory function again to create a service for that =&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;IMyService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;IAsyncDisposable&lt;/span&gt;
  &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="n"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;MyService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IServiceProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IJSRuntime&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// use a lazy value here to call the import only untill we really need it&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IJSObjectReference&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s2"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/js/my-script.js"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IMyService&lt;/span&gt;  &lt;span class="k"&gt;with&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="n"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// will fetch the script on the first call&lt;/span&gt;
          &lt;span class="c1"&gt;// if this gets called again it will use the result from the already settled value task&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;

          &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s2"&gt;"doWork"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[|&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;|])&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="nc"&gt;DisposeAsync&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dispose&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ValueTask&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that, you have lazy loading for services, and simple JS interop for both Fun.Blazor and Bolero you can then inject this service as shown above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts!
&lt;/h2&gt;

&lt;p&gt;There's a bunch more to talk about (from each of the frameworks and blazor itself!) but I'd rather leave that for other post entries.&lt;br&gt;
Having in mind that this section is purely my personal thoughts...&lt;/p&gt;

&lt;p&gt;I'd say blazor (with either bolero/fun.blazor) has moved the needle favorable a little bit more.&lt;/p&gt;

&lt;p&gt;When I wrote the original piece about the Frontend landscape I felt WASM was simply not worth it at the time except on very specific cases, today with the new Auto Interactive rendering mode blazor offers plus the advances in the F# counterparts I think we're getting into "Let's GOOOO" territory.&lt;/p&gt;

&lt;p&gt;Assuming you can deploy aspnet servers freely, then if you were considering nuxt/next/remix or those kinds of metaframerowks, then Blazor might have become an option for you and depending on the talent pool you have around it might be worth it to have lesser context switches and enjoy the benefits of F# and full dotnet in the browser.&lt;/p&gt;

&lt;p&gt;Assuming WASM only mode then... Things haven't changed that much but they have changed a bit enough. While I don't have numbers loading times and trimmed app size have improved quite a lot so WASM apps are closer to your standard "Enterprise Angular" application (if you've seen those you know what I mean), so loading times and bundle sizes might not be that relevant for you anymore. Except in cases where time to interaction means $$$, then stick to pre-render and server first approaches.&lt;/p&gt;

&lt;p&gt;For places like intranet applications or enterprise'y large apps then I'd consider it even more today for sure you could write those in angular/react today but if you still end up working with 10 thousands of lines of code, I think Blazor can benefit better from F#, its language features. and dotnet ecosystem specially in that "Full Stack" scenario where your core library gets shared entirely, not just a subset that may or may not work in the browser but the real thing.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>webassembly</category>
      <category>fsadvent</category>
    </item>
    <item>
      <title>Oh no, I typed 'dotnet new web' with F#!</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Thu, 20 Jul 2023 18:00:00 +0000</pubDate>
      <link>https://forem.com/tunaxor/oh-no-i-typed-dotnet-new-web-with-f-4bin</link>
      <guid>https://forem.com/tunaxor/oh-no-i-typed-dotnet-new-web-with-f-4bin</guid>
      <description>&lt;p&gt;Oh Jesus... not this again... please don't tell me that you just typed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dotnet new web -lang F# -o MyProject&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What!?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A friend of yours was insisting that you use F# for your next project, and you just wanted to get it over with?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let me guess... &lt;em&gt;he didn't even tell you that there are better™ web frameworks for F# like &lt;a href="https://giraffe.wiki" rel="noopener noreferrer"&gt;Giraffe&lt;/a&gt;, &lt;a href="https://saturnframework.org" rel="noopener noreferrer"&gt;Saturn&lt;/a&gt;, and &lt;a href="https://www.falcoframework.com/" rel="noopener noreferrer"&gt;Falco&lt;/a&gt; among others?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You just followed the instructions at the Microsoft's documentation?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No worries that happens more often than you think.&lt;/p&gt;




&lt;p&gt;If that sounds like too dramatic for you, let me tell you I've seen it before online, I've heard it in person and even in my own head (when I went through the "F# would be better without M$" phase).&lt;/p&gt;

&lt;p&gt;When we insist to our friends to try out F# sometimes we actually don't expect them to try F# (because of reasons) and we don't give a path or indication to how to get started with the &lt;em&gt;"True F# experience™"&lt;/em&gt; other than "You should try F# it's an awesome language".&lt;/p&gt;

&lt;p&gt;In the F# online communities there are people who will tell you to avoid Microsoft at all costs, others will tell you that there are F# tailored solutions by the community and others won't care at all, it's your code not theirs.&lt;/p&gt;

&lt;p&gt;But if for some particular reason you stumbled into the microsoft docs and you followed the instructions to create a new web project with F# you may have typed that command above and you may be wondering what to do next.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By the way, you can find source code for this post in the following repository: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/dotnet-new-web-fsharp" rel="noopener noreferrer"&gt;
        dotnet-new-web-fsharp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Oh no, I typed 'dotnet new web' with F#!&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;You were told to use F# but noone gave you a path to get started right?&lt;/p&gt;
&lt;p&gt;Well I have a &lt;a href="https://github.com/AngelMunoz/dotnet-new-web-fsharp" rel="noopener noreferrer"&gt;blog post entry&lt;/a&gt; just for that case! and this repository is the source code that goes hand by hand with that post.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Run&lt;/h3&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dotnet run&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And start sending requests with postman/curl/whatever to&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://localhost:5000/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://localhost:5000/uploads&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://localhost:5000/api/uploads/user-avatar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That should show things working on nicely&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/dotnet-new-web-fsharp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  dotnet new web...
&lt;/h3&gt;

&lt;p&gt;When you run that command you'll get a directory structure like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MyProject
├── appsettings.Development.json
├── appsettings.json
├── Program.fs
├── MyProject.fsproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what is known as a &lt;em&gt;Minimal API&lt;/em&gt; in dotnet it is an aspnet feature that introduces a simpler way to get started without too much ceremony for C# projects that also happens to work with F#.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Program.fs&lt;/code&gt; file is the entry point of your application and should look somewhat like the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Hosting&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Run&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// Exit code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has the standard main function with a web application builder, an example to define a single route and start the application, it is basically a &lt;em&gt;hello world&lt;/em&gt; web application.&lt;/p&gt;

&lt;p&gt;Buf you will notice straight away there are some weird things around, like the &lt;code&gt;Func&amp;lt;string&amp;gt;(fun () -&amp;gt; "Hello World!")&lt;/code&gt; part why not just pass a function directly there? also what's the &lt;code&gt;|&amp;gt; ignore&lt;/code&gt; thing doing? why are we ignoring that though?&lt;/p&gt;

&lt;p&gt;It turns out that in C# the compiler and tooling is built in a way that &lt;code&gt;Func&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Action&amp;lt;T&amp;gt;&lt;/code&gt;, and friends can be written transparently (e.g. &lt;code&gt;(string name) =&amp;gt; Results.Ok(name)&lt;/code&gt;) there, F# functions (&lt;code&gt;FSharpFunc&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;fun (name: string) -&amp;gt; Results.Ok(name)&lt;/code&gt;) on the other hand while syntactically similar they are not a 1-1 replacement for those types in the dotnet runtime and there has to be a translation the F# idiom to the C# idiom.&lt;/p&gt;

&lt;p&gt;In our sample above MapGet is part of a route builder meaning that it always returns an instance of &lt;code&gt;RouteHandlerBuilder&lt;/code&gt; in C# there's no warning for discarded values so it doesn't show anything, in F# to avoid that warning we explicitly ignore the value.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: In the case of the &lt;code&gt;|&amp;gt; ignore&amp;lt;'T&amp;gt;&lt;/code&gt; (you don't see the &lt;code&gt;'T&lt;/code&gt; because the compiler provides type inference) this is a feature that doesn't let you simply discard return &amp;gt; values from an expression, you have to explicitly ignore them or live with a warning. Take into account the following JavaScript code&lt;/p&gt;


&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The previous dev who worked here&lt;/span&gt;
  &lt;span class="c1"&gt;// Mutated the object, it didn't creae a new one&lt;/span&gt;
  &lt;span class="c1"&gt;// Not even god remembers why, he left the company 2 years ago&lt;/span&gt;
  &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;propName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// create a person with a name&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Frank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// add the age property to the person use the same reference&lt;/span&gt;
&lt;span class="nf"&gt;addProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// someone else decided to apply object destructuring&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;addProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;job&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Developer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Now that the original dev is not around, we're not sure what was the true intent of the function was it to mutate the object only? was it intended to create copies and use the resulting value? no idea but If you're a relatively seasoned JS dev, you know mutating an object within a function may lead to unexpected code paths yet &amp;gt; the editor/tooling won't complain about it, it is usually you or a co-worker that finds out because they got a jira ticket to fix something out.&lt;/p&gt;

&lt;p&gt;For F# this means that it will produce a warning in the build logs as well as the editor that there's a function call that returns a value that is not being used (the first function call in the previous example) it won't complain about the second function call because you're actually using the function's return value and since most objects are immutable in F# that kind of code that mutates an instance and returns it probably wouldn't compile unless it is a classic dotnet object.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Ok, but is that actually bad?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might be wondering if those annoyances might limit how your F# code behaves or what can be done with it and the reality is that no that's not really a big issue in my opinion it is not bad it is just annoying, as you have to type more characters and it introduces more noise to the code, you certainly notice that C# gets more love but that's not a big deal, you can still write your code and it will work just fine.&lt;/p&gt;

&lt;p&gt;At this point you might start wondering if there's an easier with less friction path to work with F# and asp.net, the answer is: &lt;strong&gt;&lt;em&gt;Sure!&lt;/em&gt;&lt;/strong&gt; there are actually other options you can check out like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.falcoframework.com/" rel="noopener noreferrer"&gt;Falco&lt;/a&gt; - A functional web framework for F# that sits on top of aspnet.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://giraffe.wiki" rel="noopener noreferrer"&gt;Giraffe&lt;/a&gt; - A functional web framework for F# that sits on top of aspnet.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://saturnframework.org" rel="noopener noreferrer"&gt;Saturn&lt;/a&gt; - A functional web take on MVC built on top of giraffe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;both Falco and Giraffe are very similar but they offer different tools to work with they are not a 1-1 equivalent at the userland code level they are both worth looking at for you to evaluate.&lt;/p&gt;

&lt;p&gt;Saturn is built on top of the giraffe abstractions for routes and function composition with a few helpers that provide a MVC-like smooth yet functional abstraction, it is more opinionated but also worth looking at.&lt;/p&gt;




&lt;p&gt;Before we surrender to the F# idiomatic gods, I find myself in the position to tell you that you can still use the minimal api features and build your web application and that you can actually make it simpler to integrate things like swagger and open api documentation which is currently something the F# tailored solutions may not be as simple to add.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Let's add the required namespaces&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tasks&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DependencyInjection&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Hosting&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;

&lt;span class="c1"&gt;// Rather than using functions inlined in the MapGet call we can define them&lt;/span&gt;
&lt;span class="c1"&gt;// as private functions in a dedicated module&lt;/span&gt;
&lt;span class="c1"&gt;// we use RequireQualifiedAccess to force us to use the module name&lt;/span&gt;
&lt;span class="c1"&gt;// when calling the functions&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RequireQualifiedAccess&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;MinimalHandlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// dummy type to be able to resolve the logger&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;UploadAvatar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
          &lt;span class="k"&gt;interface&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;// F# functions are public by default so if we want some sort of encapsulation here&lt;/span&gt;
    &lt;span class="c1"&gt;// (which is not necessary) we have to add the "private" keyword after "let"&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;indexHandler&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;

    &lt;span class="c1"&gt;// here's a sample of a handler for file uploads&lt;/span&gt;
    &lt;span class="c1"&gt;// which in reallity is what we care about when we write our endpoints&lt;/span&gt;
    &lt;span class="c1"&gt;// the boilerplate and other things can be left out in a more localized place&lt;/span&gt;
    &lt;span class="c1"&gt;// here we can focus specifically on the request and response&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;uploadAvatar&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadAvatar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt; &lt;span class="s2"&gt;"uploadAvatar Got Called"&lt;/span&gt;

            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadFormAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RequestAborted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;userAvatar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetFile&lt;/span&gt; &lt;span class="s2"&gt;"user-avatar"&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObj&lt;/span&gt;

            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;userAvatar&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"image/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvariantCultureIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                    &lt;span class="c1"&gt;// do what you want with the file&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoContent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;
                    &lt;span class="c1"&gt;// if is not an image then we don't want it, tell the client it is a bad request&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The file must be an image"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="c1"&gt;// no file means no avatar, and we will tell the client it is a bad request&lt;/span&gt;
            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The request must contain a file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Now this is our public function, it takes a web application from the aspnet's&lt;/span&gt;
    &lt;span class="c1"&gt;// builder and registers the routes we want&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;
            &lt;span class="c1"&gt;// remember that (fun () -&amp;gt; "Hello World!") thing?&lt;/span&gt;
            &lt;span class="c1"&gt;// well we can now just pass the function directly&lt;/span&gt;
            &lt;span class="c1"&gt;// which adds a little bit of indirection but it makes it easier to read&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MapGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;indexHandler&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
             &lt;span class="c1"&gt;// Before we "ignore" the endpoint builder, we can also enjoy aspnet's features&lt;/span&gt;
             &lt;span class="c1"&gt;// which are very useful if you want swagger/open api documentation built for you&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Status200OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/uploads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadAvatar&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadAvatar&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Accepts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Status204NoContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ProducesProblem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// Here's where we register our handlers&lt;/span&gt;
    &lt;span class="nn"&gt;MinimalHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
    &lt;span class="c1"&gt;// if we have more modules we can repeat this pattern&lt;/span&gt;
    &lt;span class="c1"&gt;// and our app is suddenly less annoying than what it got started with&lt;/span&gt;
    &lt;span class="c1"&gt;// examples could be:&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// AuthHandlers.register app&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
    &lt;span class="c1"&gt;// ProductHandlers.register app&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Run&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// Exit code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we follow the pattern of keeping our endpoints in a module and registering them at the main function we can keep our code organized with minimal effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;But... How do we scale?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing I've heard before if you come from languages like Javascript/Typescript where express apps or Python's Flask apps blow up to hell due to the "micro-framework" focus of using just functions and routes&lt;/p&gt;

&lt;p&gt;Some believe that by using only function handlers you set up yourself for spaghetti code with an untelligible mess.&lt;/p&gt;

&lt;p&gt;This in my opinion is more of an architectural level problem rather than one at the application/framework level, you can still use the same patterns you use in other languages to keep your code organized such as dependency injection. In asp.net DI is built into the framework so you can use it out of the box. In the example above we actually injected a logger into our handler and the only thing we had to do was to add the correct signature plus the type annotation in the route registration.&lt;/p&gt;

&lt;p&gt;If we add more services to our application, we can rely in the function parameters rather than closures to access services that may live in a module.&lt;/p&gt;

&lt;p&gt;That being said, if you're still not convinced, we can also add &lt;em&gt;controller&lt;/em&gt; endpoints which might be more reminiscent to controllers in frameworks like ruby on rails, laravel, django, etc which are often associated to "bigger" applications.&lt;/p&gt;

&lt;p&gt;To do that we'll have to make some changes to our single file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First we need to add the namespace&lt;/span&gt;
&lt;span class="c1"&gt;// otherwise controllers are not found&lt;/span&gt;
&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;PlainWeb&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tasks&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DependencyInjection&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Hosting&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logging&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mvc&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;

&lt;span class="c1"&gt;// We have to use the ApiController + Route attributes&lt;/span&gt;
&lt;span class="c1"&gt;// to enable aspnets conventions for controllers&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"[controller]"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// DI in this case is applied at the controller's constructor level&lt;/span&gt;
&lt;span class="c1"&gt;// but... at the same time we're using a closure-like access to the handler's dependencies c:&lt;/span&gt;
&lt;span class="c1"&gt;// which was not present in the minimal example.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;UploadsController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadsController&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;ControllerBase&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// This attribute is used to specify the route otherwise it is taken from the method name&lt;/span&gt;
    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user-avatar"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
      &lt;span class="c1"&gt;// these are similar to the minimal api builder methods, they are used to specify&lt;/span&gt;
      &lt;span class="c1"&gt;// the content type of the request and response&lt;/span&gt;
    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Consumes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProducesResponseType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Status204NoContent&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProducesResponseType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
    &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UserAvatar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IFormFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"UserAvatar Got Called"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;// File instance is obtained by the name of the file in the form data&lt;/span&gt;
            &lt;span class="c1"&gt;// in this case our file was named "avatar" in the client request&lt;/span&gt;
            &lt;span class="c1"&gt;// we also need to use Option.ofObj to convert the potential null value to an option&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;avatar&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;avatar&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObj&lt;/span&gt;

            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;avatar&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The request must contain a file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;avatar&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"image/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InvariantCultureIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                    &lt;span class="c1"&gt;// do what you want with the file&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoContent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"The file must be an image"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Guess what!?&lt;/span&gt;
&lt;span class="c1"&gt;// We don't need to get rid of our minimal handlers!&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RequireQualifiedAccess&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;MinimalHandlers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c"&gt;(* ... *)&lt;/span&gt;

&lt;span class="c1"&gt;// namespaces cannot contain values (let bindings)&lt;/span&gt;
&lt;span class="c1"&gt;// we have to use a module wrapper to contain our main function&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// add the required services for the controllers to work&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddControllers&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Build&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// as an example, we can keep our minimal api handlers&lt;/span&gt;
        &lt;span class="c1"&gt;// plus the controller route handlers!&lt;/span&gt;
        &lt;span class="nn"&gt;MinimalHandlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
        &lt;span class="c1"&gt;// ProductHandlers.register app&lt;/span&gt;
        &lt;span class="c1"&gt;// ProductHandlers.register app&lt;/span&gt;

        &lt;span class="c1"&gt;// Ensure that the controller routing middleware is added&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MapControllers&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;

        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Run&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

        &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// Exit code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we also have convention based controllers that build in years of battle tested asp.net framework patterns, and minimal apis which are somewhat a &lt;em&gt;newer&lt;/em&gt; and more lightweight concept that internally builds on top of the same conventions for regular asp.net apps.&lt;/p&gt;

&lt;p&gt;In my personal and biased opinion, controllers are a little bit more verbose and feel heavier than minimal apis, so I tend to favor the later but they are also more familiar to people coming from other languages&lt;/p&gt;

&lt;p&gt;In the end it is a matter of preference and how comfortable are you with the code you're writing.&lt;/p&gt;

&lt;h3&gt;
  
  
  So... what's next?
&lt;/h3&gt;

&lt;p&gt;Getting started with your project of course! you can start adding more routes, or switching to the F# frameworks we've already mentioned or... hear me out...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Just Keep using standard asp.net!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you got to this point and felt comfortable using plain asp.net then there's nothing wrong with that, perhaps even better as you may be able to provide feedback to the asp.net team on how to improve the experience for F# developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing Thoughts
&lt;/h3&gt;

&lt;p&gt;A last word of advice, while you're learning this new F# thing with web servers you'll find that there's a ton of C# tailored documentation for things like Controllers and minimal APIs while the F# content in this regard is scarce. The main reason this happens is that most of the F# comunity doesn't want to deal with the friction of using C# idioms in F# code so they build their own solutions. Thus you're likely going to find more information by switching to an F# tailored solution.&lt;/p&gt;

&lt;p&gt;I'd suggest that before you fully commit to something, make a couple of toy projects to test the waters and follow your gut, which one felt better and go from there! eventually you'll end up with something that you like and that works for you with the added knowledge of asp.net under the hood.&lt;/p&gt;

&lt;p&gt;This is also not a definitive guide and I hope that you don't get discouraged from trying F# if for some reason you stumble upon the &lt;strong&gt;&lt;em&gt;Official&lt;/em&gt;&lt;/strong&gt; documentation and you end up doing something that may or may not be what your friends/twitter/reddit/discord folks were telling you about.&lt;/p&gt;

&lt;p&gt;It is likely that when you try these minimal api things out you start feeling the friction between plain aspnet and F# so you will likely reach for a more F# tailored solution.&lt;/p&gt;

&lt;p&gt;You know where to find me!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://misskey.cloud/@angelmunoz" rel="noopener noreferrer"&gt;@angelmunoz@misskey.cloud&lt;/a&gt; - Fediverse presence (like mastodon).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://threads.net/@angel_d_munoz" rel="noopener noreferrer"&gt;@angel_d_munoz@threads.net&lt;/a&gt; - We're getting started here :P .&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>fsharp</category>
      <category>aspnet</category>
      <category>aspdotnet</category>
    </item>
    <item>
      <title>F# File uploads with Saturn and Falco</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Wed, 19 Jul 2023 00:59:03 +0000</pubDate>
      <link>https://forem.com/tunaxor/f-file-uploads-with-saturn-and-falco-2ffh</link>
      <guid>https://forem.com/tunaxor/f-file-uploads-with-saturn-and-falco-2ffh</guid>
      <description>&lt;p&gt;Hello there folks!&lt;/p&gt;

&lt;p&gt;It has been quite a while (once again hah!) while I've been busy working in a few of my own F# projects the truth is that I didn't have much to write about in F# after my last "Simple things in F#" series.&lt;/p&gt;

&lt;p&gt;Today I don't have a new series to start with but rather a simple example which may or may not grow in another blog series. For the moment we'll talk about how to do File uploads to an F# backend powered by &lt;a href="https://www.falcoframework.com/" rel="noopener noreferrer"&gt;Falco&lt;/a&gt; and &lt;a href="https://saturnframework.org" rel="noopener noreferrer"&gt;Saturn&lt;/a&gt; so let's get started!&lt;/p&gt;

&lt;p&gt;The samples for this post can be found at this repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/fsharp-file-uploads" rel="noopener noreferrer"&gt;
        fsharp-file-uploads
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;File Upload Sample&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;This is a small sample that shows how to upload a file from an HTML Form to an F# backend.&lt;/p&gt;
&lt;p&gt;The backends here are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.falcoframework.com/" rel="nofollow noopener noreferrer"&gt;Falco&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://saturnframework.org/" rel="nofollow noopener noreferrer"&gt;Saturn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While they're mainly the same as they both are built on top of ASP.NET it is nice to have a focused sample that works as a reference in case you need it.&lt;/p&gt;
&lt;p&gt;You can check the post for this content now &lt;a href="https://dev.to/tunaxor/f-file-uploads-with-saturn-and-falco-2ffh" rel="nofollow"&gt;https://dev.to/tunaxor/f-file-uploads-with-saturn-and-falco-2ffh&lt;/a&gt; :)&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/fsharp-file-uploads" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;





&lt;p&gt;So the situation is the following&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You just found F# or saw a tweet/toot/thread related to F# and decided to give it a go, you've been told there are F# web frameworks like &lt;a href="https://www.falcoframework.com/" rel="noopener noreferrer"&gt;falco&lt;/a&gt; and &lt;a href="https://saturnframework.org" rel="noopener noreferrer"&gt;saturn&lt;/a&gt; but you haven't seen a lot of samples out there, it would be nice if there's a focused sample you can take a look at.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well let me tell you that I've been there as well! Well... in my case it was with php, then with node, then with C# and at some point F# as well hah! But I digress, Let's start with our client side where we'll be sending files to our back-end.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML Forms
&lt;/h3&gt;

&lt;p&gt;At this point in time, HTML is present everywhere so this is where we will start, given the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Send a File to the Server&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Standard HTML Post --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"standard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Standard Html Upload&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- don't forget the name, it is quite important! --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"my-uploaded-file"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- trigger the submit event with this button/input --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Upload"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;progress&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"standard-progress"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"opacity: 0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/progress&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Once the server answers our request we'll set the responses below --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"response-target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error-target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Let's also sprinkle vanilla JS here to make it a little bit dynamic --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="c1"&gt;// Let us wait for the DOM to load, so we can find our HTML nodes&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// let's gather the elements we'll be working with&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#standard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#response-target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorTarget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#error-target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#standard-progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// avoid page reloads we'll send our request manually&lt;/span&gt;
          &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="c1"&gt;// these days building a form data is so simple&lt;/span&gt;
          &lt;span class="c1"&gt;// just pass the form reference to the constructor and&lt;/span&gt;
          &lt;span class="c1"&gt;// all inputs related to the form will be added automatically&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="c1"&gt;// We'll use a standard fetch here for simplicity&lt;/span&gt;
          &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/uploads&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
              &lt;span class="c1"&gt;// if the status code is not ok, then we'll send the text to the catch handler&lt;/span&gt;
              &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// remember text is a promise, so we'll need to extract that there&lt;/span&gt;
                  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// if all goes well then we'll swap our target's html&lt;/span&gt;
              &lt;span class="c1"&gt;// with the server's response&lt;/span&gt;
              &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// you can reject promises with anything not just Errors so we'll check&lt;/span&gt;
              &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;errorTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="c1"&gt;// if it was not an error then it is likely we're here due to our&lt;/span&gt;
              &lt;span class="c1"&gt;// handler above&lt;/span&gt;
              &lt;span class="nx"&gt;errorTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="c1"&gt;// hide the progress in any case&lt;/span&gt;
              &lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our client side HTML is as simple as we can get it, just a form and a few lines of JavaScript to avoid a page reload. The form submission will hit the &lt;code&gt;/uploads&lt;/code&gt; endpoint in our server, this index file will be server by our server as well so no need to point to the full address (also to leave CORS out of the situation for the moment).&lt;/p&gt;

&lt;h3&gt;
  
  
  Saturn Back-end
&lt;/h3&gt;

&lt;p&gt;Fortunately our sample is so focused that it can be implemented in a file with around... 50-60 LoC&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Saturn&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Giraffe&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Giraffe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ViewEngine&lt;/span&gt;

&lt;span class="c1"&gt;// This is a templating function to produce HTML content that we'll use later on&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;responseTemplate&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// Giraffe.ViewEngine uses a tagElement  attribute list  tagElement list&lt;/span&gt;
    &lt;span class="n"&gt;article&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"color: %s{color}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;// encodedText is a function that produces a string with encoded html,&lt;/span&gt;
            &lt;span class="c1"&gt;// this avoids sending raw html to the client from the server&lt;/span&gt;
            &lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;encodedText&lt;/span&gt; &lt;span class="s2"&gt;"File Contents below:"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;encodedText&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// the index handler is quite simple, just send the html file which is located by&lt;/span&gt;
&lt;span class="c1"&gt;// the relative path to the current working directory of the server&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;htmlFile&lt;/span&gt; &lt;span class="s2"&gt;"./index.html"&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;

&lt;span class="c1"&gt;// This is our File Uploads Handler&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Saturn/Giraffe can also use aspnet's features directly&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Form&lt;/span&gt;
        &lt;span class="c1"&gt;// From the ASPNET's form abstraction we try to get a single file with the name&lt;/span&gt;
        &lt;span class="c1"&gt;// we provided, we also convert it to an option to handle potential null values&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;extractedFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetFile&lt;/span&gt; &lt;span class="s2"&gt;"my-uploaded-file"&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObj&lt;/span&gt;
        &lt;span class="c1"&gt;// if we wanted to extract multiple files we'd use something like form.Files.GetFiles "input's name attribute"&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;extractedFile&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// if the file is present in the request, then we can do anything we want here&lt;/span&gt;
            &lt;span class="c1"&gt;// from validating size, extension, content type, etc., etc.&lt;/span&gt;

            &lt;span class="c1"&gt;// For our use case we'll create a disposable stream reader to get the text content of the file&lt;/span&gt;
            &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OpenReadStream&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// in our simple use case we'll just read the content into a single string&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadToEndAsync&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// we'll write the file to disk just as a sample&lt;/span&gt;
            &lt;span class="c1"&gt;// we could upload it to S3, Google Buckets, Azure Storage as well&lt;/span&gt;
            &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nn"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAllTextAsync&lt;/span&gt;&lt;span class="o"&gt;($&lt;/span&gt;&lt;span class="s2"&gt;"./{Guid.NewGuid()}.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;// We received a file and we've "processed it" successfully&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseTemplate&lt;/span&gt; &lt;span class="s2"&gt;"green"&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
            &lt;span class="c1"&gt;// send our HTML content to the client and that's it&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;htmlView&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// The file was not found in the request well return an error message&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseTemplate&lt;/span&gt; &lt;span class="s2"&gt;"tomato"&lt;/span&gt; &lt;span class="s2"&gt;"The file was not provided"&lt;/span&gt;
            &lt;span class="c1"&gt;// and also set our status code to 400 to signal it was a client error&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setStatusCode&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;htmlView&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// lastly register our routes in the router&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;appRouter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;
        &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/index.html"&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;
        &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s2"&gt;"/uploads"&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// create a server&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;use_router&lt;/span&gt; &lt;span class="n"&gt;appRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// run it baby!&lt;/span&gt;
&lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Nothing too crazy I hope!&lt;/p&gt;

&lt;p&gt;Let's put that into an F# project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create the project&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-lang&lt;/span&gt; F# &lt;span class="nt"&gt;-o&lt;/span&gt; saturn-sample
&lt;span class="nb"&gt;cd &lt;/span&gt;saturn-sample
&lt;span class="c"&gt;# add the Saturn package&lt;/span&gt;
dotnet add package Saturn
&lt;span class="c"&gt;# create the empty index file&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now copy and paste the contents of the previously provided client side HTML into the &lt;code&gt;index.html&lt;/code&gt; file, then copy F# source code and replace the contents of the &lt;code&gt;Program.fs&lt;/code&gt; file.&lt;br&gt;
and start your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to visit &lt;code&gt;http://localhost:5000/&lt;/code&gt; and start testing the file upload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Falco Back-End
&lt;/h3&gt;

&lt;p&gt;Fortunately, the falco sample is not much different and also fits in a single file! we'll be using the same &lt;code&gt;index.html&lt;/code&gt; that we shown above so we'll go straight to the F# source code for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tasks&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Falco&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Falco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Routing&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Falco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HostBuilder&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Falco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Markup&lt;/span&gt;

&lt;span class="c1"&gt;// Similarly to the saturn sample&lt;/span&gt;
&lt;span class="c1"&gt;// we'll provide a templating function&lt;/span&gt;
&lt;span class="c1"&gt;// this one uses Falco.Markup though rather than Giraffe.ViewEngine&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;responseTemplate&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="nn"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nn"&gt;Attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"color: %s{color}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nn"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nn"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h3&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nn"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="s2"&gt;"File Contents below:"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="nn"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nn"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// equally to the previous sample we'll read the index file and return an html response&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the only difference here is that we are able to pass the request aborted cancellation token&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadAllTextAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"./index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RequestAborted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofHtmlString&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Falco can also use aspnet's features directly&lt;/span&gt;
        &lt;span class="c1"&gt;// but offers an F# API for ease of use&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streamForm&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;extractedFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="c1"&gt;// extract the file safely from the&lt;/span&gt;
            &lt;span class="c1"&gt;// IFormFileCollection in the http context&lt;/span&gt;
            &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Files&lt;/span&gt;
            &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="c1"&gt;// try to extract the uploaded file named after the "name" attribute in html&lt;/span&gt;
                &lt;span class="c1"&gt;// GetFile returns null if no file is present, so we safely convert it into an optional value&lt;/span&gt;
                &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetFile&lt;/span&gt; &lt;span class="s2"&gt;"my-uploaded-file"&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;extractedFile&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// if the file is present in the request, then we can do anything we want here&lt;/span&gt;
            &lt;span class="c1"&gt;// from validating size, extension, content type, etc., etc.&lt;/span&gt;

            &lt;span class="c1"&gt;// For our use case we'll create a disposable stream reader to get the text content of the file&lt;/span&gt;
            &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OpenReadStream&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// in our simple use case we'll just read the content into a single string&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadToEndAsync&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// we'll write the file to disk just as a sample&lt;/span&gt;
            &lt;span class="c1"&gt;// we could upload it to S3, Google Buckets, Azure Storage as well&lt;/span&gt;
            &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nn"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAllTextAsync&lt;/span&gt;&lt;span class="o"&gt;($&lt;/span&gt;&lt;span class="s2"&gt;"./{Guid.NewGuid()}.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;// We received a file and we've "processed it" successfully&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseTemplate&lt;/span&gt; &lt;span class="s2"&gt;"green"&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
            &lt;span class="c1"&gt;// send our HTML content to the client and that's it&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofHtml&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// The file was not found in the request return something&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseTemplate&lt;/span&gt; &lt;span class="s2"&gt;"tomato"&lt;/span&gt; &lt;span class="s2"&gt;"The file was not provided"&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withStatusCode&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofHtml&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// declare the host and the endpoints&lt;/span&gt;
&lt;span class="n"&gt;webHost&lt;/span&gt; &lt;span class="o"&gt;[||]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;endpoints&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="c1"&gt;// handle the index routes as well&lt;/span&gt;
          &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;
          &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/index.html"&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;
          &lt;span class="c1"&gt;// our file endpoint handler as well&lt;/span&gt;
          &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s2"&gt;"/uploads"&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, let's put that into an F# project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# create the project&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-lang&lt;/span&gt; F# &lt;span class="nt"&gt;-o&lt;/span&gt; falco-sample
&lt;span class="nb"&gt;cd &lt;/span&gt;falco-sample
&lt;span class="c"&gt;# add the Falco package&lt;/span&gt;
dotnet add package Falco
&lt;span class="c"&gt;# create the empty index file&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now copy and paste the contents of the previously provided client side HTML into the &lt;code&gt;index.html&lt;/code&gt; file, then copy F# source code and replace the contents of the &lt;code&gt;Program.fs&lt;/code&gt; file.&lt;br&gt;
and start your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be able to visit &lt;code&gt;http://localhost:5000/&lt;/code&gt; and start testing the file upload.&lt;/p&gt;




&lt;p&gt;Both backends in F# are virtually the same a few differences here and there might make it more appealing for some than others but the premise is the same, using F# should make it easy and safe regardless of your preferred style and we can see this in using the F# Optional type to avoid null reference exceptions which can bite hard in languages where null is the day to day bread.&lt;/p&gt;

&lt;p&gt;Please note the usage of the &lt;code&gt;use&lt;/code&gt; keyword in the samples, for disposable references (the ones that implement the &lt;code&gt;IDisposable&lt;/code&gt; interface) F# can make the disposition automatically at the end of the scope when this keyword is used, otherwise you would need to manually call &lt;em&gt;reference&lt;/em&gt;&lt;code&gt;.Dispose()&lt;/code&gt; which can be easily forgotten so... avoid that and just let F# handle it for you!&lt;/p&gt;

&lt;p&gt;I must mention that when you're dealing with file uploads you should avoid reading the contents into a string, this is for performance reasons mainly because rarely you actually need to inspect the contents of the file you usually pass it on to another file upload service like I mentioned in the sample, aws, gcloud, azure, you name it letting the code process a stream is better as you don't allocate the string in memory for un-needed reasons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus HTMX
&lt;/h3&gt;

&lt;p&gt;You may have heard of this &lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;HTMX&lt;/a&gt; thing... that supposedly makes it easier to deal with client side HTML + any existing back-end.&lt;/p&gt;

&lt;p&gt;Well, let me tell you that it works wonders with F# as well!&lt;/p&gt;

&lt;p&gt;to use HTMX we'll change our HTML a little bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Send a File to the Server&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Include HTMX in your website --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/htmx.org@1.9.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- HTMX uses hx-* attributes to indicate the behavior of the element that will send 
        an http request, in this case
        we're telling htmx to post a form-data request to the /uploads endpoint
        and when the server sends a response replace the inner html of the response target
        with the server's response
     --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
      &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"htmx-form"&lt;/span&gt;
      &lt;span class="na"&gt;hx-encoding=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&lt;/span&gt;
      &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/uploads"&lt;/span&gt;
      &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#response-target"&lt;/span&gt;
      &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"innerHtml"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;HTMX Upload&lt;span class="nt"&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"my-uploaded-file"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Upload&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;progress&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"htmx-progress"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/progress&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"response-target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error-target"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/section&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Closely to vanilla JS, but with a smoother API to work with --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="c1"&gt;// given the element (specified by the selector) listen to the progress event in the&lt;/span&gt;
      &lt;span class="c1"&gt;// backing hxr request&lt;/span&gt;
      &lt;span class="c1"&gt;// crafting an xhr request manually is quite lengthy so that's why we used fetch in the plain&lt;/span&gt;
      &lt;span class="c1"&gt;// html example, htmx thankfully hides that for us and lets us focus on the omportant bits&lt;/span&gt;
      &lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#htmx-form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:xhr:progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;htmx&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#htmx-progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="c1"&gt;// if the backend sends an error we'll replace the contents of the error target instead&lt;/span&gt;
      &lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:responseError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#error-target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can replace the contents of the &lt;code&gt;index.html&lt;/code&gt; file with that and it should work just as great, with the difference that we need to write a little bit of JS ourselves and we also get file upload progress as well!&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing thoughts
&lt;/h3&gt;

&lt;p&gt;Hopefully this shows that F# is not an alien language and it is more similar than you think, no weird maths, no weird point free code. This is very close to what you would write in other languages like typescript given that they provide a framework similar to aspnet!&lt;/p&gt;

&lt;p&gt;Does this help you at all?&lt;br&gt;
Do you want more content like this?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Until the next time, you know where to find me!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://misskey.cloud/@angelmunoz" rel="noopener noreferrer"&gt;@angelmunoz@misskey.cloud&lt;/a&gt; - Fediverse presence (like mastodon).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://threads.net/@angel_d_munoz" rel="noopener noreferrer"&gt;@angel_d_munoz@threads.net&lt;/a&gt; - We're getting started here :P .&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/angel_d_munoz" rel="noopener noreferrer"&gt;@angel_d_munoz&lt;/a&gt; - We'll take the beatings until the ship sinks or if we get bored.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

</description>
      <category>dotnet</category>
      <category>fsharp</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Mixing Google Cloud and F#</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Fri, 16 Dec 2022 11:00:00 +0000</pubDate>
      <link>https://forem.com/tunaxor/mixing-gcloud-and-f-515c</link>
      <guid>https://forem.com/tunaxor/mixing-gcloud-and-f-515c</guid>
      <description>&lt;blockquote&gt;
&lt;h3&gt;
  
  
  This post is part of the &lt;a href="https://sergeytihon.com/fsadvent/" rel="noopener noreferrer"&gt;#FsAdvent&lt;/a&gt; Calendar Thanks to &lt;a href="https://sergeytihon.com" rel="noopener noreferrer"&gt;Sergey Tihon&lt;/a&gt; for once again, organizing such an amazing event that brings many F# folks talking about the whole experience of using F# in a wide variety of use cases
&lt;/h3&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello there folks, it has been a while!&lt;/p&gt;

&lt;p&gt;I'm sorry for the lack of content and the lack of consistency (with both perla and the short F# video series I've been working with) but that's what happens when you only write for fun!&lt;/p&gt;

&lt;p&gt;Today's topic is &lt;a href="https://cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Platform&lt;/a&gt; and F#...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait what? Is that even possible? I thought that F# is just for some niche scenarios and hobby apps... Well even if that was the case (it is not) it is a Microsoft language, so it should be an Azure only thing right?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nope, it can run on &lt;a href="https://aws.amazon.com/blogs/developer/f-tooling-support-for-aws-lambda/" rel="noopener noreferrer"&gt;Amazon Web Services&lt;/a&gt; as well!&lt;/p&gt;

&lt;p&gt;I digress, while GCP might not be the first option to pop up in your head when it comes to cloud and serverless (specially when we talk about &lt;a href="https://get.dot.net" rel="noopener noreferrer"&gt;dotnet&lt;/a&gt;), today that is a reality.&lt;/p&gt;

&lt;p&gt;There are several libraries in dotnet for &lt;a href="https://github.com/googleapis/google-cloud-dotnet" rel="noopener noreferrer"&gt;google apis&lt;/a&gt; which cover most of the GCP usage, but we will focus in &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework-dotnet" rel="noopener noreferrer"&gt;Google Cloud Functions&lt;/a&gt; for dotnet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Disclaimer
&lt;/h4&gt;

&lt;p&gt;This blog entry is directed to those folks who are slightly familiar with gcloud, the more experience you have with it the better, but it should be fairly straight forward to follow (keeping in mind that anything that relates to the cloud is always a not so simple topic).&lt;br&gt;
Also, this is not a tutorial or a "how to" kind of article but it should give you some pointers to allow you get at least a function online and running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Before we start
&lt;/h2&gt;

&lt;p&gt;While this is not a tutorial let's try to make a couple of functions just to get our feet wet&lt;/p&gt;

&lt;p&gt;We a few things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Google Cloud Project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o413kp6buzprhxk15yu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7o413kp6buzprhxk15yu.png" alt="Project Selection" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can create a new one if you don't have it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixtef6zlrgndji891ze1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixtef6zlrgndji891ze1.png" alt="Create a new project" width="683" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The project id will be very important as it will be used in the google cloud APIs and dotnet libraries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;The GCloud CLI - &lt;a href="https://cloud.google.com/sdk/gcloud/" rel="noopener noreferrer"&gt;https://cloud.google.com/sdk/gcloud/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The dotnet templates for the functions framework - &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework-dotnet" rel="noopener noreferrer"&gt;https://github.com/GoogleCloudPlatform/functions-framework-dotnet&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dotnet new -i Google.Cloud.Functions.Templates&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fun is for Functions
&lt;/h2&gt;

&lt;p&gt;Let's go for the simplest one un-authenticated, http functions let's get started&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS C:\Users\scyth\repos&amp;gt; dotnet new gcf-http -o MyFunction
The template "Google Cloud Functions HttpFunction" was created successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new function template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Function.fs
MyFunction.fsproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Function.fs&lt;/code&gt; file looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;MyFunction&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Framework&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHttpFunction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Logic for your function goes here.&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;param name="context"&amp;gt;The HTTP context, containing the request and the response.&amp;lt;/param&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;A task representing the asynchronous operation.&amp;lt;/returns&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HandleAsync&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAsync&lt;/span&gt; &lt;span class="s2"&gt;"Hello, Functions Framework."&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AwaitTask&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StartAsTask&lt;/span&gt; &lt;span class="p"&gt;:&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cloud functions run &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet" rel="noopener noreferrer"&gt;aspnet core&lt;/a&gt; under the hood, but since it is a controlled environment it was designed in a way that the only concern you should worry about is running your function so all of the usual boilerplate related to adding services, middleware or enabling features is hidden away from you which for most simple scenarios this is what you will need and in the case of F# where dependency injection is more related to simply use functions it might be enough.&lt;/p&gt;

&lt;p&gt;let's talk about the handler, surely it looks quite... weird right? well yes, it is kind of weird because with an &lt;code&gt;async {}&lt;/code&gt; CE requires that you convert tasks to async, and that's a whole complex topic which may even have encyclopedic material to discuss with, in the meantime we won't care too much about it and we can change our handler to the following form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HandleAsync&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAsync&lt;/span&gt; &lt;span class="s2"&gt;"Hello, Functions Framework."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This is not an endorsement to switch all of the existing code in any other codebase from &lt;code&gt;async {}&lt;/code&gt; to &lt;code&gt;task {}&lt;/code&gt; both CEs have their usages and for this purpose since we're going to interface with many task based libraries (like GCloud dotnet APIs or aspnet's task based functions) and it makes sense to stay on the task side of things, for the most part &lt;code&gt;async {}&lt;/code&gt; will be enough for most cases and it is still the &lt;a href="https://learn.microsoft.com/en-us/dotnet/fsharp/tutorials/async#core-concepts" rel="noopener noreferrer"&gt;recommended default&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That being said, let's continue with the function, the context parameter is an &lt;code&gt;HttpContext&lt;/code&gt; object which means you have access to the &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Response&lt;/code&gt; to it, you can easily extract information like the request's path, query string, it's body and other useful stuff. let's change our function to accept some json content and return some json as well.&lt;/p&gt;

&lt;p&gt;To avoid cluttering our function body too much we will add a &lt;code&gt;Json&lt;/code&gt; module that will handle deserialization via &lt;code&gt;System.Text.Json&lt;/code&gt; although, feel free to use whatever json deserialization strategy you may want to go with, like &lt;code&gt;Thoth.Json.Net&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;MyFunction&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Framework&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IO&lt;/span&gt;
  &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Json&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tryDeserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DeserializeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
      &lt;span class="k"&gt;with&lt;/span&gt;
      &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHttpFunction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;

    &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HandleAsync&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryDeserialize&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;{|&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="o"&gt;|}&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;
          &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Sum of the numbers is {total}"&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span class="o"&gt;({|&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;|})&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"There were no numbers :( {err}"&lt;/span&gt;
          &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span class="o"&gt;({|&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, we now have a deserialized body and depending on what happened we can decide whether we can continue or not or if we want to tell the consumer that they didn't do what it was expected, the sky's the limit here, let's try it!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PS C:\Users\scyth\repos\MyFunction&amp;gt; dotnet run
2022-11-26T06:11:45.606Z [Google.Cloud.Functions.Hosting.EntryPoint] [info] Serving function MyFunction.Function
2022-11-26T06:11:45.720Z [Microsoft.Hosting.Lifetime] [info] Now listening on: http://127.0.0.1:8080
2022-11-26T06:11:45.721Z [Microsoft.Hosting.Lifetime] [info] Application started. Press Ctrl+C to shut down.
2022-11-26T06:11:45.722Z [Microsoft.Hosting.Lifetime] [info] Hosting environment: Production
2022-11-26T06:11:45.722Z [Microsoft.Hosting.Lifetime] [info] Content root path: C:\Users\scyth\repos\MyFunction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first few lines are what we would normally expect when we run an asp net app locally via &lt;code&gt;dotnet run&lt;/code&gt; just logs and the URL where the server is listening, I will do a bad request and a good request via postman, but feel free to use your favorite tool for this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[info] Request starting HTTP/1.1 GET http://127.0.0.1:8080/ - -
Scopes: [{ RequestId: 0HMMFEKIDG1IQ:00000002, RequestPath: / }]
There were no numbers :( The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
[info] Request finished HTTP/1.1 GET http://127.0.0.1:8080/ - - - 400 - application/json;+charset=utf-8 239.0244ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the case above I didn't send any json body in the request and it shows our logs as well as asp.net ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scopes: [{ RequestId: 0HMMFEKIDG1IQ:00000002, RequestPath: / }]
[info] Request starting HTTP/1.1 GET http://127.0.0.1:8080/ application/json;+charset=utf-8 32
Scopes: [{ RequestId: 0HMMFEKIDG1IQ:00000003, RequestPath: / }]
Sum of the numbers is 45
[info] Request finished HTTP/1.1 GET http://127.0.0.1:8080/ application/json;+charset=utf-8 32 - 200 - application/json;+charset=utf-8 30.8089ms
Scopes: [{ RequestId: 0HMMFEKIDG1IQ:00000003, RequestPath: / }]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a little bit of date cleaning you can see in the logs that logs are there which is nice but, hold on a minute... they don't look as nice as the rest of the logs, perhaps that could be a problem with google cloud monitoring tools&lt;/p&gt;

&lt;p&gt;Well one of the good things of asp.net is that it has a ton of stuff out fo the box and it can be included via dependency injection, which in this case we can use our &lt;code&gt;Function&lt;/code&gt; constructor to bring things like an &lt;code&gt;ILogger&amp;lt;Function&amp;gt;&lt;/code&gt;, &lt;code&gt;IConfiguration&lt;/code&gt;, and other services that come out of the box from aspnet&lt;br&gt;
let's change our code a little bit&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nc"&gt;MyFunction&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Cloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Functions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Framework&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;AspNetCore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Logging&lt;/span&gt; &lt;span class="c1"&gt;// This line got added&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// ... omit for brevity ...&lt;/span&gt;

&lt;span class="c1"&gt;// Notice&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IHttpFunction&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;

    &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HandleAsync&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryDeserialize&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;{|&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="o"&gt;|}&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;
          &lt;span class="c1"&gt;// switch from printfn to logger&lt;/span&gt;
          &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Sum of the numbers is {total}"&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span class="o"&gt;({|&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;|})&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="c1"&gt;// switch from printfn to logger&lt;/span&gt;
          &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LogInformation&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"There were no numbers :( {err}"&lt;/span&gt;
          &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span class="o"&gt;({|&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|})&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our log entries will look more like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-11-26T06:22:08.838Z [MyFunction.Function] [info] Sum of the numbers is 45
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2022-11-26T06:24:18.960Z [MyFunction.Function] [info] There were no numbers :( The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will be very helpful to diagnose things once you deploy your function to the cloud, as it is simple to run and debug locally but it is not so simple once things are living in the cloud, logs are very important thing to get right and help yourself with future issues where your code may not be behaving as you would expect&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: There are further customizations you can do to the startup sequence if you want for example add services via the asp.net's dependency injection mechanisms, you can view how &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework-dotnet/blob/9c21f17e944a0516d15eea83d1c4e62b266d7e17/docs/customization.md" rel="noopener noreferrer"&gt;in this document&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great! Now we have our local function that we run, and we know it works time to deploy!&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;Deploying via the CLI is I believe relatively simple, it takes a single command albeit, with a bunch of arguments but nothing more than that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gcloud functions deploy my-function-name-in-gcp-console \
  --region us-west1 \
  --runtime dotnet6 \
  --trigger-http \
  --allow-unauthenticated \
  --entry-point MyFunction.Function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Please ensure that you have initialized the gcloud cli (&lt;code&gt;gcloud init&lt;/code&gt;) and that you have selected the project you created earlier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gcloud functions deploy my-function-name-in-gcp-console&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first argument is the name of your function, this name will be the one that will show in the google cloud functions console and used as an identifier for your function (each new deploy will increment a version of that function rather than deploy a new function to a different endpoint)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--region us-west1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where is this function getting deployed in the cloud? In my case, us-west1 is the assigned region.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--runtime dotnet6&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Select the runtime to run this function with, since dotnet6 is the most recent LTS release at the time of writing, it makes sense to select it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--trigger-http&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let GCP know that you want this function to be invoked via HTTP (rather than an Event like those from storage, firestore, or pub/sub)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--allow-unauthenticated&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case you are not using anything related to IAM access within the gcloud network so we'll keep it simple and allow anyone to hit our endpoint&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--entry-point MyFunction.Function&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the "full namespace path" to the class that implements the &lt;code&gt;IHttpFunction&lt;/code&gt; interface it doesn't have to me named &lt;code&gt;Function&lt;/code&gt; but for our purposes it can stay like that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--gen2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not listed here but I will mention it anyways because the following screenshot is using gcloud functions gen2 so your screen might be different than mine, but gen2 functions are still in preview so take it with a grain of salt.&lt;/p&gt;

&lt;p&gt;If all went well you should see a screen like this once you selected your function in the gcloud console&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xaphgi1e932967bgdet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xaphgi1e932967bgdet.png" alt="Test View for GCloud Function" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can monitor logs and also see what the function answers from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Great! hopefully the process of creating a new google cloud function, developing one and finally deploying it, I think in a best case scenario you are likely doing the full process in around 30 minutes just because the gcloud cli takes a lot of time to install 🤣.&lt;/p&gt;

&lt;p&gt;But things in real life are not always that simple when it comes to projects, so are there other things worth having in mind? Yes let's see&lt;/p&gt;

&lt;h3&gt;
  
  
  NuGet dependencies
&lt;/h3&gt;

&lt;p&gt;Since GCloud functions run in a controlled environment, they use conventions for deployments and other stuff if you are using a package manager like &lt;code&gt;paket&lt;/code&gt; you may need to fall back to NuGet because the deployment process can't be customized as far as I know, so keep that in mind.&lt;/p&gt;

&lt;p&gt;Other than that you can use &lt;code&gt;dotnet add package My.Dependency --version 1.10.0&lt;/code&gt; normally and dependencies install just right.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project References
&lt;/h3&gt;

&lt;p&gt;Referencing other projects like a shared library that has core types, validations and other business rules sometimes is a must because this code could be used by different projects in an organization this can prove problematic when you need to deploy something because your project structure now contains more than one project. Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;README.md
src/
  Lib/
    ...
    Types.fs
    Lib.fsproj
  ProjectA/
    ...
    ProjectA.fsproj
  FunctionProject/
    Function.fs
    FunctionProject.fsproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea now would be to deploy from src rather than from &lt;code&gt;src/FunctionProject&lt;/code&gt; but if you do that just like that your project won't work as it won't be able to find &lt;code&gt;Lib/Lib.fsproj&lt;/code&gt; which is not great, you have to change your deployment command and add the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gcloud functions deploy my-function-name \
... other arguments ...
--set-build-env-vars=GOOGLE_BUILDABLE=FunctionProject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--set-build-env-vars=GOOGLE_BUILDABLE=FunctionProject&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you set this env var &lt;code&gt;GOOGLE_BUILDABLE&lt;/code&gt; and point it to the directory of the project you want to build (that has project references) if will ensure to grab the sources if the current directory and build that project in the cloud to ensure it has what it needs to build correctly (You can find more about it in &lt;a href="https://github.com/GoogleCloudPlatform/functions-framework-dotnet/blob/9c21f17e944a0516d15eea83d1c4e62b266d7e17/docs/examples.md#multiprojectfunction-and-multiprojectdependency" rel="noopener noreferrer"&gt;this document&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  .gcloudignore and .gitignore
&lt;/h3&gt;

&lt;p&gt;Don't forget to include both of these files otherwise you will end up uploading the whole sources + build artifacts to gcloud which is dead space you don't want to pay for!&lt;br&gt;
An example of &lt;code&gt;.gcloudignore&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# let gcloiud ignore this file 😅
.gcloudignore

# If you would like to upload your .git directory, .gitignore file or files
# from your .gitignore file, remove the corresponding line
# below:
.git
.gitignore

node_modules
# include gitignore contents at this point
#!include:.gitignore

# Ensure the production appsettings is there
!appsettings.Production.json
# ignore other appsetting files

appsettings.*.json
# ... the rest of your ignores for gcloud ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;.gitignore&lt;/code&gt; file can be created using &lt;code&gt;dotnet new gitignore&lt;/code&gt; and both files complement each other (they must be side by side).&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with GCLoud APIs with Auth
&lt;/h3&gt;

&lt;p&gt;Some APIs like firestore, require you to have a set of credentials available in the environment, for the SDK to make authenticated requests to the GCP API, we can simplify by generating the default project credentials&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gcloud auth application-default login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a set of credentials based on the default project your gcloud CLI is currently running&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux, macOS: &lt;code&gt;$HOME/.config/gcloud/application_default_credentials.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;%APPDATA%\gcloud\application_default_credentials.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: Please ensure that your default project is not a &lt;strong&gt;&lt;em&gt;PRODUCTION&lt;/em&gt;&lt;/strong&gt; one! otherwise you may end up developing with the wrong data.&lt;br&gt;
&lt;a href="https://cloud.google.com/docs/authentication/application-default-credentials" rel="noopener noreferrer"&gt;More info about this in the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The good
&lt;/h2&gt;

&lt;p&gt;I think the dotnet team in google cloud has nailed the experience for the most part, it is simple it follows what one would expect from dotnet-land libraries adding a whole set of functions that talk to each other via gcloud pub/sub + http functions can be a powerful combo for serverless, and the free tier is very generous.&lt;/p&gt;

&lt;p&gt;F# is not the only supported language of course you can also write functions in C# and VB, and guess what? they all have the same examples in the dotnet cloud functions framework yes including F# and VB isn't that crazy!?&lt;br&gt;
...Once again third-party companies show that you can support template for languages other than &lt;code&gt;C#&lt;/code&gt; and that is a very welcome thing from me I love to see that you get to decide what do tool do you want to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bad
&lt;/h2&gt;

&lt;p&gt;The documentation is sometimes a little too vague, you have to jump from section to section in hopes to find what you're looking for and just to be able to find it but most likely with C# samples, if you're a VB or an F# dev, then you have to have that mental overhead of translating that code from C# to VB or F# which is not desirable by any means, however I found out that the most important bits of information were in the GitHub markdown files rather than in the gcloud documentation. Arguably, this is because dotnet is basically a recent addition and probably most of the work and efforts were directed into provide a smooth developer experience and filling the gaps here and there when it comes to the docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ugly
&lt;/h2&gt;

&lt;p&gt;This is not a problem of functions &lt;em&gt;per-se&lt;/em&gt; more like a gcloud libraries problem, most of the libraries do have certain preference for C# based designs, that means that in cases like &lt;code&gt;firestore&lt;/code&gt; you will have to create classes rather than records, that or if you're using just primitives, records with &lt;code&gt;CLIMutable&lt;/code&gt; attribute... You can't win them all I guess regardless, I think that's something we can live with as F# class and other OOP features support is great as well, it simply isn't that common but is not the end of the world!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo project
&lt;/h2&gt;

&lt;p&gt;I made a sample project that you can use as a reference and explore that goes a little further than the hello world project style (not too far though)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/AngelMunoz/FsFediverseArchive" rel="noopener noreferrer"&gt;https://github.com/AngelMunoz/FsFediverseArchive&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this project you will be able to see a few things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Shared library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that contains types, and Thoth.Json encoders/decoders to have a unified API for json serialization/deserialization&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Http Function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that works as a webhook for one of the social media websites I'm part of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Event function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that gets triggered when a &lt;a href="https://cloud.google.com/pubsub/" rel="noopener noreferrer"&gt;Pub/Sub&lt;/a&gt; topic is fired (from the webhook function)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Http Function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;that shows the public notes and replies I've made or received, these notes were saved in firestore in the event function&lt;/p&gt;

&lt;p&gt;While it's not the most complex project you'll ever see&lt;/p&gt;

&lt;p&gt;You can see the last function in action in this website: &lt;a href="https://fediverse.tunaxor.me" rel="noopener noreferrer"&gt;https://fediverse.tunaxor.me&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Once again F# proves that it can be a powerful tool that is not necessarily tied to run specifically with other Microsoft products like Windows or Azure. If you are in a situation that for some reason you have to run serverless in google cloud, don't fret! F# is there to help you if you need it!&lt;/p&gt;

&lt;p&gt;Also making the FsFediverseArchive project made me realize that cloud, serverless and projects of a similar theme are very complex in nature, when your things are local everything works just fine and dandy, but when things get to the cloud... That is not always the case there is confusion, missing logs, code behaving weird and no obvious root cause... But all in all, things can be done and there's certain level of testing and integration with emulators and such tooling that can help so the road may be dark, but there's almost always a light at the end that guides us.&lt;/p&gt;

&lt;p&gt;So, what do you think? Please give it a shot and let me know if the process is as simple and as smooth as I felt it was when I did it for the first time, it would be nice to hear where are the gotchas and issues so we can help each other out there :)&lt;/p&gt;

&lt;p&gt;You know where to find me!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://misskey.cloud/@angelmunoz" rel="noopener noreferrer"&gt;@angelmunoz@misskey.cloud&lt;/a&gt; - Fediverse presence (like mastodon).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://twitter.com/angel_d_munoz" rel="noopener noreferrer"&gt;@angel_d_munoz&lt;/a&gt; - We'll take the beatings until the ship sinks or if we get bored.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>googlecloud</category>
      <category>fsadvent</category>
    </item>
    <item>
      <title>Targeting Node, Bun and Deno With F#</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Wed, 13 Jul 2022 17:14:12 +0000</pubDate>
      <link>https://forem.com/tunaxor/targeting-node-bun-and-deno-with-f-1fb5</link>
      <guid>https://forem.com/tunaxor/targeting-node-bun-and-deno-with-f-1fb5</guid>
      <description>&lt;p&gt;Hello folks, here we are once again with more F#, this time we'll be talking about how can we use the &lt;a href="https://fable.io" rel="noopener noreferrer"&gt;fable compiler&lt;/a&gt; to target &lt;a href="https://bun.sh" rel="noopener noreferrer"&gt;bun.sh&lt;/a&gt; and &lt;a href="https://deno.land" rel="noopener noreferrer"&gt;deno.land&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you may (or not) know by now if you have read some of my older posts fable lets you compile your F# code into modern web standards JavaScript this has a lot of advantages for modern runtimes like bun/deno which accept ES modules by default that means you don't need to further process your compiled JS code if not required it should just work!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is node, deno, and bun?
&lt;/h2&gt;

&lt;p&gt;Over the past decade the JavaScript ecosystem grew exponentially and innovated in many areas that were missing for JavaScript, it allowed the language to modernize and to enable tooling for web applications as well as servers, people found out that sometimes it makes sense to be able to share the code that runs in the browser with the one that runs on the server, node, deno and bun precisely allow you to do that, they are JavaScript runtimes built on top of web browser engines like V8 (chromium) and WebKit (safari) although the server code is different from the client, there is always logic that can be shared between both parties be it validation, workflow execution and other cool stuff.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://nodejs.org" rel="noopener noreferrer"&gt;nodejs&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Until today... it is still the most used runtime to deploy server or desktop JavaScript it builds itself on top of chromium's V8 engine to power JavaScript code in a runtime similar yet different to the browser.&lt;/p&gt;

&lt;p&gt;When node was getting started the JavaScript landscape was vastly different but node provided some niceties over browser JavaScript at the time, most notably for me the notion of modules, the format called commonjs caught the attention of many people who wanted to prove how applications were built there were other module systems at the time, amd, umd, system, etc but no one had a definitive solution, browserify was then built, webpack came to the scene, and a lot of tooling after (including Typescript, Babel, ES2015, and other niceties) here we are today, the node ecosystem is a beast on its own and with the support to ESModules the ecosystem is finally in the transition to a more web standards code which can allow better source code sharing among the browser and node itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://deno.land" rel="noopener noreferrer"&gt;deno.land&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As per the words taken from deno's landing page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deno is a simple, modern and secure runtime for JavaScript, TypeScript, and WebAssembly that uses V8 and is built in Rust.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides web platform functionality and adopts web platform standards.&lt;/li&gt;
&lt;li&gt;Secure by default. No file, network, or environment access, unless explicitly enabled.&lt;/li&gt;
&lt;li&gt;Supports TypeScript out of the box.&lt;/li&gt;
&lt;li&gt;Ships only a single executable file.&lt;/li&gt;
&lt;li&gt;Has built-in development tooling like a dependency inspector (deno info) and a code formatter (deno fmt).&lt;/li&gt;
&lt;li&gt;Has a set of reviewed (audited) standard modules that are guaranteed to work with Deno: &lt;a href="https://deno.land/std" rel="noopener noreferrer"&gt;deno.land/std&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deno (which is built by the same person who initially built node) is basically another take to node but with different philosophies in some areas, some of the most notable and already mentioned are typescript support out of the box, it also uses V8 and is built with rust. Unlike node, deno doesn't have package manager, rather than that deno leverages web standards where it can and in this case it uses URL imports in ESModules to import files and import maps to keep bare modules intact, this pairs nicely with CDNs like jspm, jsdelivr, skypack and deno's cdn as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://bun.sh" rel="noopener noreferrer"&gt;Bun.sh&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Bun is the new player in the game and oh boi... what a player it is!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Bun is a fast all-in-one JavaScript runtime&lt;br&gt;
Bundle, transpile, install and run JavaScript &amp;amp; TypeScript projects — all in Bun.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bun is a new JavaScript runtime with a native bundler, transpiler, task runner and npm client built-in.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bun aims to be compatible with node where it can, as well as being web standards driven (like deno) but it also takes lessons from the JavaScript ecosystem and tries to provide performant and efficient tooling it's like if you combined rollup/esbuild/npm/pnpm/yarn all in one.&lt;/p&gt;

&lt;p&gt;One important bit is that Bun implements the node resolution algorithm which helps a lot bringing the existing node ecosystem into bun basically almost out of the box, in fact one of its advertising features is that you can run &lt;a href="https://github.com/oven-sh/bun#bun-create" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; projects within bun without a hassle.&lt;/p&gt;

&lt;p&gt;Also unlike deno and node, Bun preferred to use WebKit instead of V8 which seems to be faster in bun's benchmarks and well it is a very interesting prospect when you can tell folks "&lt;strong&gt;&lt;em&gt;Hey! do tou want to make your node faster? Just run it in bun!&lt;/em&gt;&lt;/strong&gt;"&lt;/p&gt;

&lt;h4&gt;
  
  
  Will node usage decline?
&lt;/h4&gt;

&lt;p&gt;Now the creation of bun, and deno doesn't mean that node is going to die anytime soon, the idea alone is laughable. While these projects aim to solve similar problems, It depends how each project's developer audience uses them, that will make these projects favor more, less or different use cases.&lt;/p&gt;

&lt;p&gt;Think about it for the moment, just think how many frameworks are out there yet most of then co-exist naturally and help each other out to improve, thankfully creating a JS runtime isn't as easy as writing yet another framework 🤣.&lt;/p&gt;

&lt;p&gt;For us Developers though it adds more choices on the table, and that's good competition drives innovation. Given how each runtime relies more on web standards these innovations may end up in the standards and benefit everyone at the same time.&lt;/p&gt;

&lt;p&gt;It also opens the possibility that code you write may be as agnostic as possible and run without modifications in different runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting back to fsharp
&lt;/h2&gt;

&lt;p&gt;Now what does this mean for the F# folks?&lt;/p&gt;

&lt;p&gt;Depending on how you use F# it might not mean anything at all or it might mean leveraging the type safety and the power of F# to write safe code that will perform well in a multitude of runtimes be it lambda functions, web workers like cloudflare's, or simply leverage the excellent F# tooling to improve your codebase and take advantage of the well supported compilation JavaScript target.&lt;/p&gt;

&lt;p&gt;We will use a simple console application for this case.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: keep in mind that you should install node, deno, or bun depending which one you want to target I'll show the three runtimes but all of them are optional!&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-lang&lt;/span&gt; F# &lt;span class="nt"&gt;-o&lt;/span&gt; fs-sample &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;fs-sample
dotnet new tool-manifest
dotnet tool &lt;span class="nb"&gt;install &lt;/span&gt;fable

&lt;span class="c"&gt;# Let's built the app right away just to test it&lt;/span&gt;

dotnet fable &lt;span class="nt"&gt;-o&lt;/span&gt; dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These commands should create and build, and compile JavaScript from the F# console application&lt;br&gt;
inside the &lt;code&gt;dist/Program.js&lt;/code&gt; file you will find a similar output to this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fable_modules/fable-library.3.7.16/String.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello from F#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;You can run this file in the standard means of your runtime&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Node&lt;/strong&gt;: &lt;code&gt;node dist/Program.js&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bun&lt;/strong&gt;: &lt;code&gt;bun dist/Program.js&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deno&lt;/strong&gt;: &lt;code&gt;deno run dist/Program.js&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: node requires a package.json file with the property &lt;code&gt;"type": "module"&lt;/code&gt; to run without issues&lt;/p&gt;

&lt;p&gt;To add that just run npm init -y and add said property&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At this point I can tell you:&lt;/p&gt;

&lt;p&gt;"&lt;strong&gt;&lt;em&gt;That's it, that's all you need to target JavaScript runtimes with F#&lt;/em&gt;&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;Hopefully this is a reminder that Fable just outputs JavaScript , you can use the plain JavaScript as is in the runtimes that support ES2015 (and a few newer features) without the need for extra tooling like bundlers, and transpilers or similar tooling and as I've said before on other posts "&lt;em&gt;Wherever Web Standards JavaScript runs, F# code will run as well&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;There's a cool feature from fable when you use an &lt;code&gt;[&amp;lt;EntryPoint&amp;gt;]&lt;/code&gt; attribute, let's change the &lt;code&gt;Program.fs&lt;/code&gt; code to the following&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"%A"&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;after running once again &lt;code&gt;dotnet fable -o dist&lt;/code&gt; the compiled output looks like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./fable_modules/fable-library.3.7.16/String.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;toConsole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%A&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))(&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;You can run this file in the standard means of your runtime&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Node&lt;/strong&gt;: &lt;code&gt;node dist/Program.js -- -a --b=c&lt;/code&gt; outputs: &lt;code&gt;--,-a,--b=c&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bun&lt;/strong&gt;: &lt;code&gt;bun dist/Program.js -- -a --b=c&lt;/code&gt; outputs: &lt;code&gt;-a,-b=c&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deno&lt;/strong&gt;: &lt;code&gt;deno run dist/Program.js -- -a --b=c&lt;/code&gt; outputs:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deno doesn't output anything at all, and that's because Deno doesn't use &lt;code&gt;process.argv&lt;/code&gt; like node and bun but rather &lt;code&gt;Deno.args&lt;/code&gt; so that's one of the few differences you will find, also bun requires to escape the arguments via &lt;code&gt;--&lt;/code&gt; otherwise it tries to parse them as if they were bun's cli arguments.&lt;/p&gt;

&lt;p&gt;This entry point function might be useful for you depending what are you targeting and if you are looking forward to use the program's cli arguments.&lt;/p&gt;
&lt;h3&gt;
  
  
  Packages
&lt;/h3&gt;

&lt;p&gt;For Node and Bun the package story is the same, just run npm/pnpm/yarn/bun install and once packages are downloaded just run things with bun, although keep in mind that if you're calling a CLI tool that internally calls Node, it won't run in bun but node.&lt;/p&gt;

&lt;p&gt;for Deno the story is slightly different, you can use an import map like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"urlpattern-polyfill"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.skypack.dev/pin/urlpattern-polyfill@v5.0.3-5dMKTgPBkStj8a3hiMD2/mode=imports,min/optimized/urlpattern-polyfill.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://deno.land/std@0.147.0/http/server.ts"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;which in turn allows you to do this in deno&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;urlpattern-polyfill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serve&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;while these are not "packages" like the node/bun ones, they behave in the same way, deno applies cache techniques to allow offline usage as well so you don't depend on internet to import your dependencies at runtime.&lt;/p&gt;

&lt;p&gt;Does that import map thing feel familiar? well maybe I spoke about that a few months ago when I wrote about a project of mine (&lt;a href="https://github.com/AngelMunoz/Perla" rel="noopener noreferrer"&gt;Perla&lt;/a&gt;) which uses import maps to allow you to write Single Page Applications without node installed!&lt;/p&gt;
&lt;h2&gt;
  
  
  Fable.Node Fable.Bun, Fable.Deno
&lt;/h2&gt;

&lt;p&gt;What about specific APIs for node, deno and bun?&lt;/p&gt;

&lt;p&gt;Well you're in luck if you want to target node because &lt;a href="https://github.com/fable-compiler/fable-node" rel="noopener noreferrer"&gt;Fable.Node&lt;/a&gt; has been out for a while and since node is the most popular runtime in this list you'll even find bindings to projects like express via the &lt;a href="https://github.com/glutinum-org/Glutinum" rel="noopener noreferrer"&gt;Glutinum&lt;/a&gt; project which are high quality bindings with test suites to ensure things don't just break!&lt;/p&gt;

&lt;p&gt;If you want the newer runtimes though... you'll have to wait for me to release the bindings for &lt;a href="https://github.com/AngelMunoz/fable-bun" rel="noopener noreferrer"&gt;fable.bun&lt;/a&gt; and &lt;a href="https://github.com/AngelMunoz/fable-bun" rel="noopener noreferrer"&gt;fable.deno&lt;/a&gt; that will allow you to target Bun and Deno's APIs&lt;/p&gt;

&lt;p&gt;Now let's move to something more exciting than just a console&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter the Bix Experiment
&lt;/h2&gt;

&lt;p&gt;With Both Bun and Deno out I really wanted to see if I could make something to test them out both runtimes offer HTTP servers that work with &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Response&lt;/code&gt; which were introduced with the Fetch API in the browsers a few years ago&lt;/p&gt;

&lt;p&gt;I have always wanted to make a JavaScript framework just to be part of the meme and as well to contribute back what the internet has given me for free over the years, this is where &lt;strong&gt;Bix&lt;/strong&gt; comes in&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bix&lt;/strong&gt; is a micro-framework designed with F# in mind and that runs on both Deno and Bun!&lt;br&gt;
In theory it also should even run in a service worker! (intercepting fetch requests) although I haven't tested that yet.&lt;br&gt;
It offers a general purpose handler that coupled with a set of route definitions it can bring a &lt;a href="https://giraffe.wiki" rel="noopener noreferrer"&gt;Giraffe&lt;/a&gt;/&lt;a href="https://saturnframework.org" rel="noopener noreferrer"&gt;Saturn&lt;/a&gt; like framework to life in JavaScript runtimes which is incredibly awesome! useful? maybe not 😅, but awesome for me indeed. Let's see some code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Bix&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Bix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Types&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Bix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Handlers&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Bix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Router&lt;/span&gt;

&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Bix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bun&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;checkCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HttpHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bearer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"Authorization"&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObj&lt;/span&gt;
        &lt;span class="c1"&gt;// dummy handler&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;bearer&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setStatusCode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sendText&lt;/span&gt; &lt;span class="s2"&gt;"Not Authorized"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Empty&lt;/span&gt;
    &lt;span class="c1"&gt;// helper functions to define routes&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sendText&lt;/span&gt; &lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/posts/:slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;promise&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// promise based handlers are supported&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PathParams&lt;/span&gt; &lt;span class="s2"&gt;"slug"&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="c1"&gt;// database from somewhere&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;renderPost&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="c1"&gt;// views from somewhere&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;sendHtml&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{|&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Bix Server!"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Now&lt;/span&gt; &lt;span class="o"&gt;|}&lt;/span&gt;
        &lt;span class="n"&gt;sendJson&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// route composition a'la suave/giraffe is supported&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/protected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checkCredentials&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sendText&lt;/span&gt; &lt;span class="s2"&gt;"I'm protected!"&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Empty&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withRouter&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withDevelopment&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withPort&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;development&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="s2"&gt;"Development"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="s2"&gt;"Production"&lt;/span&gt;

&lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"{mode} Server started at {server.hostname}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For Deno it isn't much different&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// open the Bix.Deno module&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Bix&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Deno&lt;/span&gt;

&lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Empty&lt;/span&gt;
&lt;span class="c1"&gt;// you can use the same routes without changes!&lt;/span&gt;
&lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withRouter&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;
&lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withDevelopment&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withPort&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
&lt;span class="c1"&gt;// the run function returns a promise in deno due how the std HTTP server works&lt;/span&gt;
&lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Bix provides some basic http handlers like returning json responses, set status codes, send html, and even send html files.&lt;/p&gt;

&lt;p&gt;The most amazing (at least for me) about this is that... 90% - 95% of micro-framework code is shared code between both runtimes, the only thing that really changes is the &lt;code&gt;run&lt;/code&gt; and the internal &lt;code&gt;Request&lt;/code&gt; handler function which need to be different because of how the servers are started in both runtimes and that they are different in some areas, so we need to abstract some of these details away in order to make the rest of the framework re-usable between platforms.&lt;/p&gt;

&lt;p&gt;If there's a &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt; http server for node, be sure that it can be supported as well&lt;/p&gt;

&lt;p&gt;If this peeks your interest then visit the project&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/AngelMunoz" rel="noopener noreferrer"&gt;
        AngelMunoz
      &lt;/a&gt; / &lt;a href="https://github.com/AngelMunoz/fable-bun" rel="noopener noreferrer"&gt;
        fable-bun
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Fable bindings for Bun.sh
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
&lt;a href="https://bun.sh" rel="nofollow noopener noreferrer"&gt;Bun.sh&lt;/a&gt; + Fable&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;This is a small repository that adds &lt;a href="https://fable.io" rel="nofollow noopener noreferrer"&gt;Fable&lt;/a&gt; bindings for &lt;a href="https://bun.sh" rel="nofollow noopener noreferrer"&gt;Bun.sh&lt;/a&gt;, these are very minimal and only add a few convenience methods for Bun's request/response impl consumption, but the rest of the Bun API should be included eventually, feel free to contribute any bindings that are missing for you!&lt;/p&gt;


  
    &lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Where are the other samples?&lt;/h3&gt;

&lt;/div&gt;
  
  They have been moved to their corresponding repositories, feel free to check them out
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Fable.Deno&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Please visit &lt;a href="https://github.com/AngelMunoz/fable-deno" rel="noopener noreferrer"&gt;fable-deno&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Fable.URLPattern&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Please visit &lt;a href="https://github.com/AngelMunoz/fable-urlpattern" rel="noopener noreferrer"&gt;fable-urlpattern&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Bix&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;Please visit &lt;a href="https://github.com/AngelMunoz/Bix" rel="noopener noreferrer"&gt;Bix&lt;/a&gt;&lt;/p&gt;


&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/AngelMunoz/fable-bun" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;There are slightly more complete samples there (including server side rendered endpoint using Feliz.ViewEngine) and give it a go, I'll try to start releasing the first previews over the next days/week but Feedback is super important here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Fable is a very powerful tool to make F# code, style and conciseness available almost everywhere via JavaScript (and soon other languages), I'm truly excited to see how bun, deno and node will grow together and improve to become really good assets in the software developer toolbelt.&lt;/p&gt;

&lt;p&gt;Creating a framework was also fun, I can finally call myself a JavaScript developer now that I've built my own framework 😅 &lt;code&gt;/s&lt;/code&gt; if you want to know more about how Bix internals work and how is everything abstracted to &lt;em&gt;just work&lt;/em&gt; in both deno and bun, feel free to let me know in the comments below or on twitter!&lt;/p&gt;

&lt;p&gt;I'd be glad to write another piece specifically for that purpose&lt;/p&gt;

</description>
      <category>node</category>
      <category>bunjs</category>
      <category>deno</category>
      <category>fsharp</category>
    </item>
    <item>
      <title>Exploring The F# Frontend Landscape</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Tue, 24 May 2022 01:04:17 +0000</pubDate>
      <link>https://forem.com/tunaxor/exploring-the-f-frontend-landscape-13aa</link>
      <guid>https://forem.com/tunaxor/exploring-the-f-frontend-landscape-13aa</guid>
      <description>&lt;p&gt;Hello everyone it's been a while!&lt;br&gt;
Today we will talk about what is the current frontend landscape of Frontend development for the F# ecosystem. Over the last few years Fable has become quite capable and more bindings have been released.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Disclaimer&lt;/em&gt;&lt;/strong&gt;: &lt;em&gt;&lt;strong&gt;The F# Ecosystem Is Stable&lt;/strong&gt;&lt;/em&gt; you don't need to jump ship to the next thing or anything like that! Just because there are options doesn't mean you need to ditch out what you know and learn something new. Think of this as as restaurant menu: There are options but ultimately it is your choice which one you pick and you can even decide "I don't want to eat here" it's completely fine. No kittens will die and the world won't stop so if you see a lot of options I'd suggest you to not feel pressured to choose!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When it comes to the Frontend landscape for F# we have three main roads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fable&lt;/li&gt;
&lt;li&gt;WebSharper&lt;/li&gt;
&lt;li&gt;Web Assembly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These have different approaches to the frontend landscape but ultimately do the same thing &lt;strong&gt;&lt;em&gt;Websites&lt;/em&gt;&lt;/strong&gt; some of those options allow you to go for server side rendering and others allow you to go for single page applications.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fable
&lt;/h2&gt;

&lt;p&gt;In this Section:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;High Profile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feliz&lt;/li&gt;
&lt;li&gt;Fable.Lit&lt;/li&gt;
&lt;li&gt;Sutil&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low Profile&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fable.Svelte&lt;/li&gt;
&lt;li&gt;Feliz.Snabdom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fable Next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feliz.Solid&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fable is an F# to JavaScript compiler (like Typescript compiles to JavaScript) it is near its fourth major release and has a very very strong ecosystem based around &lt;a href="https://reactjs.org" rel="noopener noreferrer"&gt;React.js&lt;/a&gt; although, in recent times other options have become available.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Fable and who benefits most from it?
&lt;/h3&gt;

&lt;p&gt;Fable is meant for those developers that need to work with the JavaScript ecosystem or that want to benefit from it, it would be unwise to negate the number of good libraries and existing solutions that have been born in it.&lt;/p&gt;

&lt;p&gt;Fable rather than negate that JavaScript exists, builds on top of it and gives you the most flexible option when it comes to frontend development in F#.&lt;br&gt;
You keep using the safety of F# (most of the time) and when necessary you can fall back to JavaScript interoperation (via emit, imports or the dynamic operator) or even JavaScript itself to fill the missing gaps that could be there.&lt;/p&gt;

&lt;p&gt;The bad thing is that the JavaScript ecosystem is so vast and has grown so much over the years it might be possible that if you want X library there won't be bindings for it. After all the F# developer numbers are way too low compared so we don't have the programmer power to be on equal grounds.&lt;/p&gt;

&lt;p&gt;Writing bindings isn't complex but it can take quite some time from your development efforts if the libraries you're targeting are too big, this cost is only paid once though, when the bindings are complete is just a matter of maintaining the bindings up to date (which isn't too big of a chore).&lt;/p&gt;

&lt;p&gt;That being said! Let's dive into Fable's options:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/Zaid-Ajaj/Feliz" rel="noopener noreferrer"&gt;Feliz&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is by far the most popular library in the F# frontend ecosystem, This library took existing lessons from Fable React and improved its DSL (Domain Specific Language) to be more concise and less verbose than existing alternatives. It also took a different approach when it came to React applications, it steered slightly away from what was very popular at the time the Elmish architecture (also known as MVU) and provided an API that is as close as possible to React itself.&lt;/p&gt;

&lt;p&gt;Feliz introduced hooks which helped to simplify state management in some cases, as well as reduce the verbosity MVU can get when applications grow, since it was compatible with previous fable-react bindings it wasn't a big of an effort to migrate to Feliz.&lt;/p&gt;

&lt;p&gt;Your typical Feliz UI component looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marginRight&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marginLeft&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its DSL is based on a list of properties for each kind of HTML tag, you can build reusable pieces of UI by just writing functions and other components given React's nature it is clear why Feliz is the most used, it simply fits with the F# mind, data and functions!&lt;/p&gt;

&lt;p&gt;Feliz supports MVU via the &lt;code&gt;Feliz.UseElmish&lt;/code&gt; package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReactComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useElmish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[|&lt;/span&gt; &lt;span class="o"&gt;|])&lt;/span&gt;
    &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
            &lt;span class="n"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// somewhere else&lt;/span&gt;
&lt;span class="nn"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getElementById&lt;/span&gt; &lt;span class="s2"&gt;"feliz-app"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're looking to dive into Frontend F# then Feliz is a solid choice you will learn what most of the F# FE devs use and it is arguably the best choice today within the Fable realm.&lt;/p&gt;

&lt;p&gt;The downsides of Feliz are the downsides of using React, since Feliz is a 1-1 binding over React you get the same problems React devs have, weird rules for hooks, easy to mistakenly provoke re-renders, and effects are still not entirely figured out in the react ecosystem and you have to keep manually what things need to re-render your UI. React uses Virtual DOM which was a performant way to render UI's in the early 2010's it is not the case anymore where browsers have caught up in performance and it turns out that in performance critical situations the VDOM diffing from React is just overhead rather than an advantage. For your average website, this shouldn't be a concern though but it is worth mentioning it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/fable-compiler/Fable.Lit" rel="noopener noreferrer"&gt;Fable.Lit&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is my personal favorite one when it comes to Fable options, &lt;a href="https://github.com/fable-compiler/Fable.Lit" rel="noopener noreferrer"&gt;Fable.Lit&lt;/a&gt; builds on top of &lt;a href="https://lit.dev" rel="noopener noreferrer"&gt;lit.dev&lt;/a&gt; which is a web component library built on web standards. It brings performant straightforward and inter-framework compatible components to the F# FE landscape since Lit works with DOM elements themselves rather than abstractions you can manipulate component instances like if you were doing vanilla JavaScript except that you can use the F# safety for that.&lt;/p&gt;

&lt;p&gt;In Fable.Lit rather than building an F# DSL (we tried) we use a string-based alternative which is closed to the HTML you know and love, this also helps a lot when you have to consume web components like those from &lt;a href="https://shoelace.style" rel="noopener noreferrer"&gt;shoelace.style&lt;/a&gt;, &lt;a href="https://fast.design" rel="noopener noreferrer"&gt;fast.design&lt;/a&gt;, &lt;a href="https://opensource.adobe.com/spectrum-web-components/" rel="noopener noreferrer"&gt;adobe spectrum&lt;/a&gt; components, and more, this will be a very important and big point over the next few years now that web components have taken off finally with major companies like Microsoft, Salesforce, Github, Adobe and more are using them.&lt;/p&gt;

&lt;p&gt;Here's two ways you can use Fable.Lit Components&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HookComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;functionCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useState&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt;
        &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""
        &amp;lt;!-- @&amp;lt;event name&amp;gt; means attach a handler to this event --&amp;gt;
        &amp;lt;sl-button outline variant="&lt;/span&gt;&lt;span class="n"&gt;neutral&lt;/span&gt;&lt;span class="s2"&gt;" @click={fun _ -&amp;gt; setValue value + 1}&amp;gt;Increment&amp;lt;/sl-button&amp;gt;
        &amp;lt;sl-button outline variant="&lt;/span&gt;&lt;span class="n"&gt;neutral&lt;/span&gt;&lt;span class="s2"&gt;" @click={fun _ -&amp;gt; setValue value - 1}&amp;gt;Decrement&amp;lt;/sl-button&amp;gt;
        &amp;lt;sl-badge&amp;gt;Count: {value}&amp;lt;/sl-badge&amp;gt;
        """&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LitElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my-custom-element"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;MyCustomElement&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;LitElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{|&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attribute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"initial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|}&lt;/span&gt;
            &lt;span class="c1"&gt;// defaults to true if not set&lt;/span&gt;
            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useShadowDom&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useState&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;

    &lt;span class="n"&gt;html&lt;/span&gt;
        &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""
        &amp;lt;sl-button outline variant="&lt;/span&gt;&lt;span class="n"&gt;neutral&lt;/span&gt;&lt;span class="s2"&gt;" @click={fun _ -&amp;gt; setValue value + 1}&amp;gt;Increment&amp;lt;/sl-button&amp;gt;
        &amp;lt;sl-button outline variant="&lt;/span&gt;&lt;span class="n"&gt;neutral&lt;/span&gt;&lt;span class="s2"&gt;" @click={fun _ -&amp;gt; setValue value - 1}&amp;gt;Decrement&amp;lt;/sl-button&amp;gt;
        &amp;lt;sl-badge&amp;gt;Count: {value}&amp;lt;/sl-badge&amp;gt;
        """&lt;/span&gt;

&lt;span class="c1"&gt;// using both somewhere&lt;/span&gt;
&lt;span class="n"&gt;html&lt;/span&gt;
    &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""
    Function Component:
    {functionCounter 20}
    &amp;lt;br&amp;gt;
    &amp;lt;!-- .initial means bind to "&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="s2"&gt;" property --&amp;gt;
    &amp;lt;my-custom-element .initial={10}&amp;gt;&amp;lt;/my-custom-element&amp;gt;
    """&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First of all if you are wondering "ugh strings", "that doesn't give any highlight", "the holes are not typed" I have a few words for that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Interpolated strings aren't as flexible as JS tagged templates so in .NET we fallback to using just objects and we lose type safety&lt;/li&gt;
&lt;li&gt;We actually have two extensions to give you the ability to highlight these F# strings

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=alfonsogarciacaro.vscode-template-fsharp-highlight" rel="noopener noreferrer"&gt;Highlight HTML/SQL templates in F#&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=daniel-hardt.html-for-fsharp-lit-template" rel="noopener noreferrer"&gt;Html for F# (Lit Template)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;For Rider and other editors no-one has tried to build a plugin as far as I know&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the thing (and the main reason I like it):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did we have to write bindings for &lt;code&gt;sl-button&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Would we need to write bindings for any other custom element/web component?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer for both is &lt;strong&gt;&lt;em&gt;No&lt;/em&gt;&lt;/strong&gt;, we still have write bindings for the JS parts of the libraries we might use but when it comes to custom elements and other standard HTML elements we don't need to do anything, that includes it's attributes/properties.&lt;/p&gt;

&lt;p&gt;The tradeoff is precisely that we gain access to a vast array of libraries out there but we lose some type safety when you describe your UIs.&lt;/p&gt;

&lt;p&gt;And before I forget it, Fable.Lit also supports the Elmish architecture&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HookComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useElmish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""
        &amp;lt;h1&amp;gt;{model.Count}&amp;lt;/h1&amp;gt;
        &amp;lt;button @click={fun _ -&amp;gt; dispatch Increment}&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button @click={fun _ -&amp;gt; dispatch Decrement}&amp;gt;Decrement&amp;lt;/button&amp;gt;
    """&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lit itself is a pretty safe bet is a solid choice and built on web standards so it's very likely to have a really long life (it first came out around 2013-2014 as polymer if you ever head of that) and has adjusted and improved together with web browsers&lt;/p&gt;

&lt;p&gt;Fable.Lit on the other hand is fairly new and the bindings may still have some areas where we can improve but the technology underneath is already production ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/davedawkins/Sutil" rel="noopener noreferrer"&gt;Sutil&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;When I first learned about Sutil I fell in love with it, it brought a lot of concepts from &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; to the F# frontend landscape and while it's development has been slower than most it has some really interesting choices which can fit some minds better than the other alternatives.&lt;/p&gt;

&lt;p&gt;Sutil is the first pure F# framework for the frontend that we have, it doesn't have bindings to any framework because it's just F#.&lt;/p&gt;

&lt;p&gt;Sutil uses a Feliz variation of a DSL called &lt;a href="https://dev.towe'll%20talk%20about%20it%20later%20on"&gt;Feliz.Engine&lt;/a&gt; so you get the F# type safety you know and love with reactive UI elements.&lt;br&gt;
Sutil functions run only once and then everything is static unless you choose to be reactive via stores. This helps in regards performance and updates are only applied where things change.&lt;/p&gt;

&lt;p&gt;In a similar fashion to Fable.Li, Sutil works with plain DOM elements which make it compatible with Web Components as well, it also provides features to write web components with it!&lt;/p&gt;

&lt;p&gt;If you really like the programming model of svelte or rxjs (observables) then Sutil is something to look after, it also has built-in animations, chrome dev tools, and other nice features.&lt;/p&gt;

&lt;p&gt;Here's how Sutil looks like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// functions can be separated from UI logic&lt;/span&gt;
&lt;span class="c1"&gt;// with some thought we can make these very reusable&lt;/span&gt;
&lt;span class="c1"&gt;// and even UI agnostic&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IStore&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modify&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IStore&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modify&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c1"&gt;// make this element reactive&lt;/span&gt;
        &lt;span class="nn"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Counter: {count}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;// using stablished HTML elements&lt;/span&gt;
            &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="c1"&gt;// interoperation with custom elements&lt;/span&gt;
            &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"sl-button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nn"&gt;Attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"variation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"neutral"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
            &lt;span class="o"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As mentioned before whenever we mount/call &lt;code&gt;view&lt;/code&gt; it will render once and when stores/observables emit a new value only the reactive parts will update this allows for fine grained updates&lt;/p&gt;

&lt;p&gt;Sutil Also offers MVU support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;},&lt;/span&gt; &lt;span class="nn"&gt;Cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;none&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makeElmishSimple&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;
    &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;disposeOnUnmount&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nn"&gt;Bind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;|&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Counter = {n}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"+"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="o"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Part of the disadvantages in Sutil is the slow updates although David recently mentioned that he will be working on it, more maintainers would be welcome.&lt;br&gt;
Also it has been in beta for a while so it might not be so ready for prime time.&lt;br&gt;
More testing from real users would be nice because at least on my relatively limited testing it feels just as solid as any of the previous choices.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/alfonsogarciacaro/Feliz.Engine" rel="noopener noreferrer"&gt;Feliz.Engine&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Once the high profile projects have been mentioned one project that is worth mentioning and one you can use if you plan to ever bring another framework to the F# space is Feliz.Engine&lt;/p&gt;

&lt;p&gt;Feliz.Engine is a library that defines in a &lt;em&gt;standard&lt;/em&gt; way F# DSLs for Elements, Attributes and Styles. It was born out of the Original Feliz DSL but modified slightly to fit a more general use case.&lt;/p&gt;

&lt;p&gt;Sutil, Feliz.Solid and Feliz.Snabdom use Feliz.Engine under the hood you could also use it to bring others to the fold!&lt;/p&gt;

&lt;p&gt;This project deserves a mention just for it's potential to bring more to the ecosystem (and to not confuse it with Feliz itself)&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/fable-compiler/Fable.Store" rel="noopener noreferrer"&gt;Fable.Svelte&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;These bindings were a way to make F# work with &lt;code&gt;.svelte&lt;/code&gt; files. I don't have much to say about this other than it exists and you could take a look if you want but it's usage is fairly low.&lt;/p&gt;

&lt;p&gt;For this one I don't think is a good choice for your next serious project, maybe for experiments here and there but given it's low usage there might be some bugs not found just yet in the way, Sutil would be a better choice if you like Svelte-like way of doing UIs&lt;/p&gt;

&lt;p&gt;Svelte of course is rock solid as a choice and is one of the most popular JS frameworks out there, the problem lies on how mature are the bindings and how battle tested they are&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/alfonsogarciacaro/Feliz.Snabbdom" rel="noopener noreferrer"&gt;Feliz.Snabdom&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Using Feliz.Engine comes Feliz.Snabdom as well snabdom has a virtual dom implementation but deals with DOM elements rather than abstract over them (like react) this gives you more wiggle room to interoperate with third party components. it provides life cycle hooks, lazy loaded elements and other features.&lt;/p&gt;

&lt;p&gt;I'm not fan of virtual dom myself so I haven't really tried this much more than just a few examples at the same time I'm not sure of the maturity of the bindings, although snabdom has been out for years and has been used by thousands of devs the concerns lie in how portable the code is + how mature the bindings could be.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fable Next!
&lt;/h2&gt;

&lt;p&gt;These new options are coming hot from the oven and paint a bright future for Fable integrations on the Frontend ecosystem!&lt;/p&gt;

&lt;p&gt;Fable 4 (snake island) is going to bring &lt;code&gt;JSX&lt;/code&gt; compilation, meaning that frameworks that use JSX as their DSL and building blocks will have an even easier time integrating, this support will come to Feliz.Engine as well this means a couple of things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable API for the F# side (Feliz.Engine)&lt;/li&gt;
&lt;li&gt;Broad Target of UI frameworks by just configuring the packages you want to use (be it solidjs, vue jsx, inferno, preact etc)&lt;/li&gt;
&lt;li&gt;Easier migration paths between F# &amp;lt;-&amp;gt; JS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given how JSX is still a compilation step you can always fallback to manual JSX and continue from/to JSX if needed.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a href="https://github.com/alfonsogarciacaro/Feliz.Solid" rel="noopener noreferrer"&gt;Feliz.Solid&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;This is an exciting one, [solid.js] has been going up in popularity these days because it is what react could have been&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;True and Predictable reactivity&lt;/li&gt;
&lt;li&gt;No manual dependency tracking&lt;/li&gt;
&lt;li&gt;Observable support&lt;/li&gt;
&lt;li&gt;No Virtual DOM&lt;/li&gt;
&lt;li&gt;Fast and Efficient&lt;/li&gt;
&lt;li&gt;Small footprint Library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if the react model is &lt;em&gt;your thing&lt;/em&gt; and you want to avoid many of the react footguns then this is something to keep an eye for&lt;/p&gt;

&lt;p&gt;Solid code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;JSX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Solid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;createSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;doubled&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;quadrupled&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doubled&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"{count()} * 2 = {doubled()}"&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"{doubled()} * 2 = {quadrupled()}"&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
        &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nn"&gt;Attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt; &lt;span class="s2"&gt;"button"&lt;/span&gt;
            &lt;span class="nn"&gt;Ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nn"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Click me!"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see it is very similar to Sutil or Feliz.Snabdom and that's because it is using Feliz.Engine as well! while these don't interoperate between themselves easy because each library defines what the DSL actually emits: DOM Elements, Virtual DOM elements they do use the same DSL so learning one it basically teaches you the others as well!&lt;/p&gt;

&lt;p&gt;The main disadvantage of this is that is of course new, it only works on Fable 4 (Alpha at the time of writing) and thus should not be considered for your next serious project. Once Fable 4 is released for real then it could be something to really consider and contribute to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fable + JSX
&lt;/h3&gt;

&lt;p&gt;If you're thinking "my favorite framework is not in the list" do not worry, writing bindings for Fable has become easier over the years, specially when you take Feliz.Engine into account Fable 4 will bring JSX as well meaning that it could be even simpler to integrate to the JavaScript ecosystem.&lt;/p&gt;

&lt;p&gt;Perhaps you like vue, but supporting vue files is too much, perhaps the framework you are using at work supports JSX this has the potential to bring a lot with minimal changes, like &lt;a href="https://twitter.com/alfonsogcnunez/status/1528379845394440193" rel="noopener noreferrer"&gt;Alfonso said&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's like programming against an interface (JSX) instead of an implementation (the compiled JS)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Although he also said there are nuances and likely differences in how each framework and its tooling treats JSX so Feliz.Engine is close to universal but not &lt;em&gt;that&lt;/em&gt; universal.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://websharper.com/" rel="noopener noreferrer"&gt;WebSharper&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Web sharper has been out for quite some time and has an interesting F# first approach to the UI, WebSharper aims to fulfill the full-stack F# promise hiding away some of the JS details but has some pretty good capabilities when it needs to interoperate with javascript.&lt;/p&gt;

&lt;p&gt;Rather than a set of multiple libraries and frameworks WebSharper is a one stop shop all style of framework&lt;/p&gt;

&lt;p&gt;A simple Web Sharper Application looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Website&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SinglePage&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nn"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will tell WebSharper to generate some JavaScript and run it directly on the body of your application.&lt;/p&gt;

&lt;p&gt;While WebSharper has a ViewModel strategy it also offers MVU support, for example a counter can look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaScript&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Update&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Render&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Dispatch&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sprintf&lt;/span&gt; &lt;span class="s2"&gt;" %i "&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;V&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
            &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"+"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreateSimple&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nc"&gt;Update&lt;/span&gt; &lt;span class="nc"&gt;Render&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Run&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RunById&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or if you prefer HTML&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- this is inside the HTML page you're serving --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;ws-onclick=&lt;/span&gt;&lt;span class="s"&gt;"OnDecrement"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;${Counter}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;ws-onclick=&lt;/span&gt;&lt;span class="s"&gt;"OnIncrement"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;+&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"Content/Counter.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!--[BODY]--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaScript&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// The templates are loaded from the DOM, so you just can edit index.html&lt;/span&gt;
    &lt;span class="c1"&gt;// and refresh your browser, no need to recompile unless you add or remove holes.&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MySPA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;Snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ClientLoad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromDocument&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;vmodel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Create&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;vmodel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;

            &lt;span class="n"&gt;vmodel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;

        &lt;span class="nc"&gt;MySPA&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnIncrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnDecrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="nn"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;V&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;vmodel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;V&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bind&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;vmodel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://try.websharper.com/" rel="noopener noreferrer"&gt;There's a whole website with demos you can try!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WebSharper also offers a reactive model that can be used in exchange of the MVU architecture, the last example could also be simplified to the next example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaScript&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// The templates are loaded from the DOM, so you just can edit index.html&lt;/span&gt;
    &lt;span class="c1"&gt;// and refresh your browser, no need to recompile unless you add or remove holes.&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MySPA&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;Snippet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;ClientLoad&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FromDocument&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Create&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;MySPA&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnIncrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnDecrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;V&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;V&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bind&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reactive style is similar to the new &lt;a href="https://vuejs.org/guide/introduction.html#composition-api" rel="noopener noreferrer"&gt;Vue's Composition API&lt;/a&gt; so regardless of your choices WebSharper has you covered.&lt;/p&gt;

&lt;p&gt;That being said, on my twitter bubble WebSharper is not one of the most popular ones and I'm not entirely aware why my guess is that it tries to hide JavaScript as much as possible to try to stay in F# and that could create some sort of vendor lock in and friction in some cases, this doesn't mean it is a bad choice though if you don't want to build on top of the JavaScript ecosystem that much it looks like solid technology to pick up specially because it offers paid support so this can be a good fit for teams rather than individuals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to learn more about WebSharper let me know, so I can explore more and dedicate a couple of blog posts to it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Assembly
&lt;/h2&gt;

&lt;p&gt;In this Section:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Bolero&lt;/li&gt;
&lt;li&gt;Fun.Blazor&lt;/li&gt;
&lt;li&gt;Avalonia.FuncUI&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Web Assembly is the &lt;em&gt;newest&lt;/em&gt; player in the game and one that is being/will be a true game changer with it comes to web development, just as a taste of it's power you can now &lt;a href="https://web.dev/ps-on-the-web/" rel="noopener noreferrer"&gt;use photoshop natively in the browser&lt;/a&gt; now, what does that mean for you as a .NET developer?&lt;/p&gt;

&lt;p&gt;It means that you can run F# code (or C# if that's your jam) natively on the browser, no intermediary JavaScript that you have to touch and you keep the safety of F#&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Web Assembly and who benefits most from it?
&lt;/h3&gt;

&lt;p&gt;Web assembly (WASM) is meant for users who want to run native code in the browser, this has a few implications, WebAssembly does not have so far access to DOM and neither a garbage collector, so for WASM apps to work with .NET you need to load a .NET runtime + your application's code. That means that any time your website is visited you have to wait a few seconds while your web app loads the runtime + your code.&lt;br&gt;
Any time that any of these technologies have to they need to share information with the JS world and this can be costly, while you as an App developer don't have to do it manually you still need to be wary of the costs of serialization/deserialization that are made any time you share information with the JS world, be it in form of big UI trees, large amounts/rows of data and similar situations.&lt;/p&gt;

&lt;p&gt;That being said, if you can afford these drawbacks then you will be able to enjoy F# safety in all of its glory, no more weird JavaScript emits, or trying to bind an interface to a JavaScript object and hopefully that holds true at runtime. It's just the real deal.&lt;/p&gt;

&lt;p&gt;You can leverage the .NET ecosystem when it comes to libraries with all of their patterns and knowledge you already may have. This also means that since you are using .NET you can share logic and data 100% with the server, after all .NET6 libraries (unless they are using OS specific APIs) work on ASP.NET core for the server and WASM, this means no shared folders with tweaked paths and &lt;code&gt;#if FABLE&lt;/code&gt; or similar directives it's just the assembly being shared as is.&lt;/p&gt;

&lt;p&gt;While you don't need to interact with JavaScript at all, you can do so if you must there are ways to interoperate with functions declared in the global scope or even in JavaScript modules.&lt;/p&gt;

&lt;p&gt;So you're not entirely isolated you can interact with the outside world if needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DISCLAIMER&lt;/strong&gt;: Most of these alternatives rely on &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; which is the product solution offered by Microsoft to tap into WebAssembly with C# as usual F# is not in the roadmap but the community always jumps in to save the day and offer F# devs the experiences they deserve.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  &lt;a href="https://fsbolero.io/" rel="noopener noreferrer"&gt;Bolero&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Bolero is the most stable and mature solution for F# web assembly, it is brought to you by the same devs behind web sharper, in a way it could be the next step in their path to make web applications with F#. Bolero offers most of the functionalities of web sharper but native this time, HTML Templates via a Type Provider, Client Routing with Discriminated unions, F# DSL, MVU.&lt;/p&gt;

&lt;p&gt;Your typical Bolero view looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;initModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="s2"&gt;"+"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkSimple&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;initModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;ProgramComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;program&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is pretty similar to the other MVU samples we've seen so far bolero also offers interoperation with Blazor concepts like external components from third party libraries, remoting (RPC client-server style of communication) and of course Pure F#.&lt;/p&gt;

&lt;p&gt;If you liked the approach that WebSharper gave you and you're looking for the next step then bolero will be just what you want you can translate concepts and knowledge from there. The sad part for me is that it doesn't have a reactive style when it comes to handling state MVU is great but on larger applications it just doesn't cut it for me, the nice thing of the bad is that you can create multiple elmish components in your application and use parameters so you don't have a single master Elmish update function and rather each component have it's own state.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://slaveoftime.github.io/Fun.Blazor.Docs/" rel="noopener noreferrer"&gt;Fun.Blazor&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is another abstraction built on top of Blazor, it is the new kid in town and has some very enticing models when it comes to handle state. Fun.Blazor recently put out its version 2.0.0 which adds a bunch of nice things all around, it allows you to create UI's with F# Computation Expressions (CE) in a similar fashion to bolero, it also has the ability to use string templates (much like Fable.Lit) and has some options for routing like a giraffe style router it offers seamless integration with blazor's dependency injection, very useful to interoperate with JavaScript and other Blazor services.&lt;/p&gt;

&lt;p&gt;Your typical Fun.Blazor component looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;adaptiview&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// changeable value&lt;/span&gt;

    &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Counter: {count}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun.Blazor brings the power of &lt;code&gt;FSharp.Data.Adaptive&lt;/code&gt; to allow incremental updates to the view, this package works like excel cells, where one cell may be the source of truth for others, the other cells can recompute values depending on the first cell this allows for performant updates in the UI because only the &lt;em&gt;reactive&lt;/em&gt; parts change. This model is very close to Sutil's model as well In fact you can use stores in Fun.Blazor as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;myComponent&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IComponentHook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseStore&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Observable&lt;/span&gt;
            &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;AVal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofObservable&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Current&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddDispose&lt;/span&gt;

        &lt;span class="n"&gt;adaptiview&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;countValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WithSetter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;doubleValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;double&lt;/span&gt;

            &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Counter: {countValue}, Double: {doubleValue}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="n"&gt;countValue&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCount&lt;/span&gt; &lt;span class="n"&gt;countValue&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adaptive views are a concept that I'd love if other frameworks implemented because the reactive model really resonates with me that being said, I know you want to see elmish in action, so I'm pleased to tell you that yes, it supports MVU as well&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;initModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// using elmish directly&lt;/span&gt;
&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elmish&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Count: {state.value}"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// using elmish with adaptive views&lt;/span&gt;
&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IComponentHook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseElmish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;adaptiview&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
            &lt;span class="n"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Count: {count.value}"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun.Blazor has a lot of potential it is still young it needs more real world usage to validate many of the efforts taken in v2.0.0, although young it feels like a solid option, but keep in mind that just like Sutil it has only one maintainer, so if you like this you should look into contributing to the framework because it feels like really good option.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/fsprojects/Avalonia.FuncUI" rel="noopener noreferrer"&gt;Avalonia.FuncUI&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This one might come as a surprise for many because Avalonia is a &lt;strong&gt;&lt;em&gt;Desktop&lt;/em&gt;&lt;/strong&gt; application framework! but as seen in this &lt;a href="https://github.com/AngelMunoz/FuncUI.Wasm.Template" rel="noopener noreferrer"&gt;Avalonia.FuncUI WASM Template&lt;/a&gt; it is possible to bring the power of avalonia into the desktop via WASM, The main advantage of Avalonia.FuncUI is that you will be able to share code between browsers, android, ios, mac, linux, and windows.&lt;/p&gt;

&lt;p&gt;Avalonia.FuncUI was also recently updated and added this reactive-like model in v0.5.0&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="nc"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useState&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nn"&gt;DockPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;DockPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verticalAlignment&lt;/span&gt; &lt;span class="nn"&gt;VerticalAlignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;
        &lt;span class="nn"&gt;DockPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontalAlignment&lt;/span&gt; &lt;span class="nn"&gt;HorizontalAlignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;
        &lt;span class="nn"&gt;DockPanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nn"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nn"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dock&lt;/span&gt; &lt;span class="nn"&gt;Dock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Top&lt;/span&gt;
                &lt;span class="nn"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dock&lt;/span&gt; &lt;span class="nn"&gt;Dock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bottom&lt;/span&gt;
                &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Current&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="s2"&gt;"-"&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dock&lt;/span&gt; &lt;span class="nn"&gt;Dock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bottom&lt;/span&gt;
                &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="s2"&gt;"+"&lt;/span&gt;
                &lt;span class="nn"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The concepts of &lt;code&gt;IWritable&amp;lt;'T&amp;gt;&lt;/code&gt; and &lt;code&gt;IReadable&amp;lt;'T&amp;gt;&lt;/code&gt; work just like adaptive/changeable/stores/observables that we've seen before in sutil/fun.blazor so Avalonia.FuncUI could start becoming a competitor in the Web landscape specially if you already have some desktop app development experience, this is part of the power of WASM in practice in the case of Avalonia.FuncUI WASM you don't really need to know anything of web development to get started, just jump in!&lt;/p&gt;

&lt;p&gt;That being said, Avalonia uses Skia to render in a canvas (likely using webgl) so you won't have any kind of DOM nodes to inspect and also as far as I'm aware (and I'd love to be corrected if necessary) you throw accesibility out of the window because of that so assistive technology won't work with this kind of web sites, it also worth noting that as far as I'm aware (at the time of writing) Web support in Avalonia is in beta state so there might be a couple few bugs lurking out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  MAUI
&lt;/h2&gt;

&lt;p&gt;The elephant in the room here would likely be MAUI because it also has blazor support but I'm going to be very dismissive here and I plead guilty about it because it is a Microsoft product, I'd prefer to Microsoft to step out of the way of its .NET ecosystem and I'd love to better cultivate alternatives like Uno/Avalonia, but whatever right?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good Luck about F# support&lt;/li&gt;
&lt;li&gt;Good Luck to have linux support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both things can be done with Avalonia without any major issues, also all of the other alternatives discussed here can be developed on any of the three major operating systems heck even from your Raspberry Pi 4 that's not something I'd expect in MAUI soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;That being said the F# Frontend Landscape is not that big it might feel confusing with all the tweets and news going all over the place but thankfully as most things F#: We are pretty much settled how we use things and even if there is variety most alternatives can live together in one way or another.&lt;/p&gt;

&lt;p&gt;Here's the &lt;strong&gt;&lt;em&gt;tl;dr&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Use Fable if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to take advantage of the JavaScript Ecosystem&lt;/li&gt;
&lt;li&gt;You want to have the possibility to migrate away from F# if necessary&lt;/li&gt;
&lt;li&gt;You want the lowest amount of kb of resources to the browser.&lt;/li&gt;
&lt;li&gt;You must interact with JavaScript on a heavy basis&lt;/li&gt;
&lt;li&gt;You Like and want to use React or Lit or Vanilla (Sutil)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use Fable if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You really dislike JS&lt;/li&gt;
&lt;li&gt;You want true type safety&lt;/li&gt;
&lt;li&gt;You can afford go vanilla F# (i.e. not use most of the JS ecosystem)&lt;/li&gt;
&lt;li&gt;You don't want to learn or deal with the JavaScript Tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use WebSharper if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't mind about going F# first JS second&lt;/li&gt;
&lt;li&gt;You want type safe HTML via type providers&lt;/li&gt;
&lt;li&gt;You want to blur the lines between client side F# and server side F#&lt;/li&gt;
&lt;li&gt;You don't care too much of the toolchain and only care about final deployable assets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use WebSharper if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to literally make manual js files and adjustments to the compiling toolchain&lt;/li&gt;
&lt;li&gt;You need a more JavaScript oriented application&lt;/li&gt;
&lt;li&gt;You need to build on top of the existing node tooling&lt;/li&gt;
&lt;li&gt;You care about an extra runtime for your application (around 8kb)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use Web Assembly if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want to run native F# in the browser not a fake one&lt;/li&gt;
&lt;li&gt;You want to leverage the .NET ecosystem&lt;/li&gt;
&lt;li&gt;You want to enjoy .NET tooling to publish, distribute and build your F#&lt;/li&gt;
&lt;li&gt;You want to share code between Desktop, Server, and Mobile (like Avalonia will allow you)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't use Web Assembly if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't want to ship heavy websites (even if there's trimming support for some libs)&lt;/li&gt;
&lt;li&gt;You need low TTI (time to interaction) and TFP (Time to Fist Painting)&lt;/li&gt;
&lt;li&gt;You need a more mature ecosystem&lt;/li&gt;
&lt;li&gt;You need heavy JS interop&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Personal Opinion
&lt;/h2&gt;

&lt;p&gt;No one asked for it, and no one should because my opinion shouldn't have any weight on your decision making.&lt;br&gt;
That being said...&lt;/p&gt;

&lt;p&gt;My personal top is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fable.Lit&lt;/li&gt;
&lt;li&gt;Sutil - Fun.Blazor&lt;/li&gt;
&lt;li&gt;Feliz.Solid?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main reason is that Fable.Lit checks the boxes when it comes to web standards and I'm primarily a frontend developer and React is just not my thing mainly due to it's focus on hooks which in the case of React they might make sense but are too magical and very prone to errors and performance issues if badly used (I'm looking at you useFootGun, I mean useEffect)&lt;/p&gt;

&lt;p&gt;Sutil comes in second because of it is extremely awesome to have a pure F# framework that also offers a reactive state management model it simply fits my mental model of doing websites.&lt;/p&gt;

&lt;p&gt;Fun.Blazor comes swiftly on second place as well for the Tie because it takes the same concepts sutil uses for state management and takes it a step further&lt;/p&gt;

&lt;p&gt;Feliz.Solid comes in third because it also offers a reactive model that is likely going to replace React in a lot of places and codebases in the future. It doesn't suffer of the same problems React has and its future looks Bright, its author was recently hired (at the time of writing) at Netlify so it can only go up from here while the Feliz integration matures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So there you have it I hope this post sheds some light on what the current state of F# frontend is and what you should take into account if you want to choose one or the other alternatives we have.&lt;/p&gt;

&lt;p&gt;Ultimately you should not be pressured to &lt;em&gt;chose right&lt;/em&gt;. F# solutions even if they are not &lt;em&gt;"mature"&lt;/em&gt; are pretty solid, after all that's the main reason we chose F# to either work with or have fun (sometimes both) you should avoid one of these alternatives if you truly have reasons not to pick it otherwise it's likely going to work your needs.&lt;/p&gt;

&lt;p&gt;These frameworks are excellent work of members of the F# community even if they look &lt;em&gt;young&lt;/em&gt; or are in &lt;em&gt;beta&lt;/em&gt; these tools are extremely well done and are far more capable than what you would think when you hear those words give them a try give feedback to its authors and remember not everything is React or derivatives you have choices today :)&lt;/p&gt;

&lt;p&gt;Until the next one, don't forget to leave your comments and questions if needed!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>webassembly</category>
      <category>webdev</category>
    </item>
    <item>
      <title>F# Bits: Http Requests</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Mon, 11 Apr 2022 03:40:21 +0000</pubDate>
      <link>https://forem.com/tunaxor/f-bits-http-requests-24pa</link>
      <guid>https://forem.com/tunaxor/f-bits-http-requests-24pa</guid>
      <description>&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;I have the pleasure to announce you the pilot of a new series I have in mind, this time it will be video based.&lt;/p&gt;

&lt;p&gt;Feel free to also watch it on the &lt;a href="https://www.youtube.com/watch?v=R3y0nPEJR70" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt; I'll try to post on both sites but it's very likely that YouTube videos get posted first.&lt;/p&gt;

&lt;p&gt;In case you want to learn more about the topic above, feel free to check this blog post&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/tunaxor" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F35800%2F28156be5-48cb-4192-94a4-9c848f92a80a.jpg" alt="tunaxor"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/tunaxor/making-http-requests-in-f-1n0b" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Making Http Requests in F#&lt;/h2&gt;
      &lt;h3&gt;Angel Daniel Munoz Gonzalez ・ Mar 15 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#fsharp&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dotnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#samples&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#http&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;In any case feel free to drop some feedback and if you are interested in more short (or longer) videos like this.&lt;/p&gt;

&lt;p&gt;Have an amazing week!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>script</category>
      <category>http</category>
    </item>
    <item>
      <title>F# and WebAssembly</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Fri, 28 Jan 2022 19:39:24 +0000</pubDate>
      <link>https://forem.com/tunaxor/f-and-webassembly-n0a</link>
      <guid>https://forem.com/tunaxor/f-and-webassembly-n0a</guid>
      <description>&lt;h2&gt;
  
  
  F# and WebAssembly
&lt;/h2&gt;

&lt;p&gt;When I talk about F# and Web development I tend to speak about &lt;a href="https://fable.io" rel="noopener noreferrer"&gt;Fable&lt;/a&gt; which is an &lt;code&gt;F# -&amp;gt; JS&lt;/code&gt; compiler (although, in Fable 4+ it will officially target more than just JS), in a sense you're basically replacing Typescript or Flow or any other JS compiler for F#.&lt;/p&gt;

&lt;p&gt;Today we will talk about &lt;a href="https://fsbolero.io" rel="noopener noreferrer"&gt;Bolero&lt;/a&gt; and &lt;a href="https://slaveoftime.github.io/Fun.Blazor/" rel="noopener noreferrer"&gt;Fun.Blazor&lt;/a&gt; which are some F# abstractions over &lt;a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt;, Microsoft's Frontend Framework, objectively speaking blazor is a direct competitor to &lt;a href="https://angular.io" rel="noopener noreferrer"&gt;Angular&lt;/a&gt;, &lt;a href="https://vuejs.org/" rel="noopener noreferrer"&gt;Vue&lt;/a&gt;, &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;, &lt;a href="https://aurelia.io/" rel="noopener noreferrer"&gt;Aurelia&lt;/a&gt; and similar alternatives.&lt;/p&gt;

&lt;p&gt;I know there is people out there waiting both patiently and desperately for the death of javascript while I don't think that day will ever come, I understand how being able a single language to fill many if not all parts of the stack is useful (we already do that with JS and thousands of people love it), so let's take a look!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bolero
&lt;/h2&gt;

&lt;p&gt;Getting started with Bolero is pretty simple&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new -i Bolero.Templates
dotnet new bolero-app -o MyApp
cd MyApp &amp;amp;&amp;amp; dotnet run -p src/MyApp.Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Most of the features I will talk about here are well described in bolero's website so don't forget to check them out there as well&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Hosting models
&lt;/h3&gt;

&lt;p&gt;Bolero (just as blazor) can be used for both server side and client side apps, the templates by default provide both backend and frontend projects, this is because there's a feature that allows you to have Hot Reload for HTML templates but it only works when you run the frontend app with the server project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Markup and DSLs
&lt;/h3&gt;

&lt;p&gt;Bolero two main ways to write HTML, one is using HTML templates and the other is to use an HTML DSL, both things ultimately will produce&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML Templates
&lt;/h4&gt;

&lt;p&gt;Bolero uses a custom HTML &lt;a href="https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/type-providers/" rel="noopener noreferrer"&gt;Type Provider&lt;/a&gt; that allows you to write HTML templates in a type safe manner and provides some Hot Reload capabilities&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Hello&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"""&amp;lt;div id="&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Hello, world!&amp;lt;/div&amp;gt;"""&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// or using a filepath&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Hello&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"templates/hello.html"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this gives you a type which has information about your HTML template, templates can have &lt;em&gt;holes&lt;/em&gt; in them, which can be used to insert F# values.&lt;/p&gt;

&lt;p&gt;Let's make a simple counter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"${Classes}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Count: &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;${Count}&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"${Increment}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"${Decrement}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Decrement&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"${Reset}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Reset&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with that HTML as our template we can handle these holes inside our F# code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"templates/counter.html"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getCounterCls&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="s2"&gt;"warning"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;"normal"&lt;/span&gt;
    &lt;span class="c1"&gt;// let's fill each of the wholes we made on the template&lt;/span&gt;
    &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Classes&lt;/span&gt;&lt;span class="o"&gt;($&lt;/span&gt;&lt;span class="s2"&gt;"counter {getCounterCls count}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// once we're done call Elt() to get the instance of the template&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Elt&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// use this somewhere else&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startsAt100&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startsAt0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;.Elt()&lt;/code&gt; at the end is to get the instance of the template.&lt;/p&gt;

&lt;p&gt;You can also get nested templates, bind inputs, and radios for example by the way don't be scared by the &lt;code&gt;mutable&lt;/code&gt; keyword right there is just to show a brief example in a normal situation you would likely be using &lt;a href="https://elmish.github.io/elmish/" rel="noopener noreferrer"&gt;Elmish&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If plain HTML is not your cup of tea, let's move on to an F# DSL then&lt;/p&gt;

&lt;h4&gt;
  
  
  HTML DSL
&lt;/h4&gt;

&lt;p&gt;The HTML DSL is a fairly standard one it is composed from a function that takes two lists as arguments, the first is for attributes and the second one is for children elements our counter example would look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getCounterCls&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="s2"&gt;"warning"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;"normal"&lt;/span&gt;

    &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``class``&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"counter {getCounterCls count}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Count: "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"%i{count}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Reset"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// use this somewhere else&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startsAt100&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startsAt0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getCounter&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main advantages of this approach is that you get the full power of the F# language. You can call functions, use ifs, pattern matches, get out of the box type safety and whatever you can do with F#.&lt;/p&gt;

&lt;p&gt;The main disadvantages are that since this is F# code it must get compiled any time you make changes to it so... You will have to re-start your server any time you make changes to it this can be slow and discouraging for some people which is completely understandable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Elmish
&lt;/h3&gt;

&lt;p&gt;One of the popular architectures for F# enthusiasts is the ability to use the elmish architecture (also known as MVU) bolero offers elmish in the form of Components this is super convenient given how unscalable can elmish be on more complex aplications, rather than have a main elmish loop, you can have each of your website parts as a different elmish loop allowing you to de-couple some logic and even pages when not needed.&lt;/p&gt;

&lt;p&gt;let's revisit our counter example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Reset&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;decrement&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the core part of our elmish model we will see how it works with both HTML and the DSL approaches&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// the HTML template stays the same&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getCounterCls&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="s2"&gt;"warning"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;"normal"&lt;/span&gt;
    &lt;span class="c1"&gt;// let's fill each of the wholes we made on the template&lt;/span&gt;
    &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Classes&lt;/span&gt;&lt;span class="o"&gt;($&lt;/span&gt;&lt;span class="s2"&gt;"counter {getCounterCls state.count}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// once we're done call Elt() to get the instance of the template&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Elt&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getCounterCls&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="s2"&gt;"warning"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;"normal"&lt;/span&gt;

    &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;``class``&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"counter {getCounterCls count}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Count: "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"%i{count}"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Decrement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dispatch&lt;/span&gt; &lt;span class="nc"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="s2"&gt;"Reset"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's tie in all of that in our component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;MyCounter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="nc"&gt;ProgramComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Msg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;// Grab the value from outside of the component&lt;/span&gt;
    &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nc"&gt;InitialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="c1"&gt;// ensure there's a default value for our elmish loop&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InitialValue&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="c1"&gt;// with partial application give it the initial value&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;

        &lt;span class="c1"&gt;// Run the elmish program&lt;/span&gt;
        &lt;span class="nn"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkSimple&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see regardless if we chose HTML or the DSL the elmish component works the same. there are also other types of elmish components that you could use to fine-tune your component's performance these are called &lt;code&gt;View Components&lt;/code&gt; but I'll leave those to you as homework 😜&lt;/p&gt;

&lt;p&gt;To use this component we can call it in the following way&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startsAt0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;startsAt100&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MyCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"InitialValue"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could use these in other &lt;code&gt;view&lt;/code&gt; function somehwere or if I'm not mistaken even in a C#'s blazor file but don't quote me on that because I haven't tried it.&lt;/p&gt;

&lt;p&gt;All in all this is more-less the gist of Bolero there are other features like routing, remoting, dependency injection interop, JS services that can be used but I think many of those require their own articles, if you're interested in those let me know!&lt;/p&gt;

&lt;h2&gt;
  
  
  Fun.Blazor
&lt;/h2&gt;

&lt;p&gt;Fun Blazor is a library built on top of bolero it can interop with bolero nicely but it offers a few more options in the DSL and state management department.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that &lt;strong&gt;Fun.Blazor&lt;/strong&gt; is still a young library but I find it promising and more ergonomic to use to the point I actually could favor it over other solutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get started with Fun.Blazor you can either use this extremely minimal (PWA ready) template I made for this post&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AngelMunoz/Fun.Blazor.Sample" rel="noopener noreferrer"&gt;https://github.com/AngelMunoz/Fun.Blazor.Sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or use the official templates&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet new --install Fun.Blazor.Templates::*
dotnet new fun-blazor-min-wasm -o MyApp
cd MyApp &amp;amp;&amp;amp; dotnet run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I will continue here with the simplified template I made which differs a little bit from the official but the core concepts remain the same.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fun.Blazor offers three alternative ways to write your HTML code, the first is by using a set of Computation Expressions (CE), the second is by using a &lt;a href="https://github.com/Zaid-Ajaj/Feliz" rel="noopener noreferrer"&gt;Feliz&lt;/a&gt; style of DSL and the third one is to write HTML Templates as well&lt;/p&gt;

&lt;p&gt;Let's revise our counter example again&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;adaptiview&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;WithSetter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Count: {counter}"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="s2"&gt;"Reset"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're familiar with JSX/Swift/Kotlin this might be the best way for you to work with your views it's also the most performant variation of Fun.Blazor's way to write HTML content, Fun.Blazor offers custom operations that help you model views in a slightly less verbose manner than the plain DSL offered by bolero, it's worth noting that &lt;a href="https://github.com/fsbolero/Bolero/issues/249" rel="noopener noreferrer"&gt;bolero is working on a variation like this&lt;/a&gt; but it might take a while to land.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;adaptiview&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;WithSetter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childContent&lt;/span&gt;&lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Count: {counter}"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;evts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Increment"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;evts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;evts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Reset"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you've ever used &lt;a href="https://github.com/Zaid-Ajaj/Feliz" rel="noopener noreferrer"&gt;Feliz&lt;/a&gt; or &lt;a href="https://github.com/fsprojects/Avalonia.FuncUI" rel="noopener noreferrer"&gt;Avalonia.FuncUI&lt;/a&gt; then this DSL will make you feel at home, it's less verbose than the original DSL and gives you basically the same benefits, in the case of Fun.Blazor is slightly less performant but it is a viable alternative&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;adaptiview&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;WithSetter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="nn"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
      &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""
      &amp;lt;section&amp;gt;
        &amp;lt;p&amp;gt;Count: &amp;lt;span&amp;gt;{counter}&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;button onclick="&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button onclick="&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Decrement&amp;lt;/button&amp;gt;
        &amp;lt;button onclick="&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;Reset&amp;lt;/button&amp;gt;
      &amp;lt;/section&amp;gt;
      """&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might look at this and think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why on earth would I use this version? no syntax coloring, no nothing ewww!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;If you're using VSCode you can use the &lt;a href="https://marketplace.visualstudio.com/items?itemName=alfonsogarciacaro.vscode-template-fsharp-highlight" rel="noopener noreferrer"&gt;Highlight HTML/SQL templates in F#&lt;/a&gt; extension and you will automatically get HTML intellisense, syntax coloring as well as F# support!&lt;/li&gt;
&lt;li&gt;If you're using Visual Studio you can use &lt;a href="https://marketplace.visualstudio.com/items?itemName=daniel-hardt.html-for-fsharp-lit-template" rel="noopener noreferrer"&gt;Html for F#&lt;/a&gt;, it won't give you HTML intellisense but it at least offers the HTML syntax coloring which is greatly appreciated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides that, one strong point for that HTML templates is the ability to use Web Components/Custom Elements which are on the rise on usage, sadly these are not able to do Hot Reload like bolero's ones but they have their own benefits&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;While Fun.Blazor also allows you to use Elmish, it also offers a different way to handle view's states (which hopefully you spotted already) since Web Assembly uses F# code, it means it has access to a vast amount of libraries in the wild and take advantage of those as well, Fable is somewhat limited by javascript itself so any improvements you can get from the F# language is also limited to what Javascript can understand.&lt;/p&gt;

&lt;h4&gt;
  
  
  Adaptiviews
&lt;/h4&gt;

&lt;p&gt;This is not the case though! Fun.Blazor takes advantage of the &lt;a href="https://github.com/fsprojects/FSharp.Data.Adaptive" rel="noopener noreferrer"&gt;FSharp.Data.Adaptive&lt;/a&gt; library which works more-less like an excel spreadsheet's cells, each value is fixed unless it's being computed by another this allows performant updates on-demand, and with the help of &lt;code&gt;adaptiview() {}&lt;/code&gt; it can leverage those same efficient updates to allow performant views in F#, in essence &lt;code&gt;adaptiviews&lt;/code&gt; are hook-like abstractions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;adaptiview&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my initial value"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;WithSetter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// do whatever else you want with the value and set value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Stores
&lt;/h4&gt;

&lt;p&gt;If you like Sutil/Svelte like state management using stores then using Fun.Blazor components will be a great option for you&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counterComponent&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
  &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IComponentHook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseStore&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;
    &lt;span class="c1"&gt;// this value will automatically update any time counter changes&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;adaptiveValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseAVal&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

    &lt;span class="n"&gt;adaptiview&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// we can use the value directly if we bind it on the&lt;/span&gt;
      &lt;span class="c1"&gt;// adaptiview CE&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adaptiveValue&lt;/span&gt;
      &lt;span class="c1"&gt;// use the computed value&lt;/span&gt;
      &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Count: %i{computed}"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// publish updates to the store&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="s2"&gt;"Increment"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;computed&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="s2"&gt;"Decrement"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;onclick&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;childContent&lt;/span&gt; &lt;span class="s2"&gt;"Reset"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be a powerful approach because you can have functions that take a store and push updates to it, the adaptive value will always be up to date and you can even produce multiple adaptive values from the same store! meaning that sub components or other views can also enjoy the benefits of sharing stores and generate their own adaptive values.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;p&gt;We just saw a component with stores and adaptive values, but the benefits of Fun.Blazor components don't stop there, they let you access blazor's components lifecycle hooks seamlessly!&lt;/p&gt;

&lt;p&gt;for &lt;a href="https://github.com/AngelMunoz/Mandadin/blob/fun-blazor-migration/Mandadin/Navbar.fs#L69-L107" rel="noopener noreferrer"&gt;example&lt;/a&gt; you can tap on the after first render and do async operations that can be integrated with observables thanks to &lt;a href="http://fsprojects.github.io/FSharp.Control.Reactive/index.html" rel="noopener noreferrer"&gt;FSharp.Control.Reactive&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IHookComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;themeService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IThemeService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UseStore&lt;/span&gt; &lt;span class="nn"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Dark&lt;/span&gt;

    &lt;span class="c1"&gt;// Event&lt;/span&gt;
    &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OnFirstAfterRender&lt;/span&gt;
    &lt;span class="c1"&gt;// async task&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;themeService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetTheme&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;switchTask&lt;/span&gt;
    &lt;span class="c1"&gt;// update the store&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subscribe&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Publish&lt;/span&gt;
    &lt;span class="c1"&gt;// ensure disposal from the component's subscription&lt;/span&gt;
    &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AddDispose&lt;/span&gt;

    &lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the rest of the view code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same can be done with other blazor lifecycle hooks.&lt;/p&gt;

&lt;p&gt;Fun.Blazor offers ways to handle state outside elmish which I believe they are very powerful and can help you avoid some annoyances you can get when you use existing frameworks/approaches&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;So there it is! I gave some love to WebAssembly in F# as well I'm still on the fence of going full web assembly but it is certainly an alternative you can consider thanks to the fact that web assembly is part of the browser standards and not some propietary plugin thing (sorry Silverlight)&lt;/p&gt;

&lt;p&gt;Bolero wasn't really appealing to me given the options for state handling (elmish only basically) and the DSL options but with Fun.Blazor and the state management options it provides I really feel like it's worth trying out, it is still a young library so make sure you try it, report bugs, give feedback and help it grow!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
      <category>webassembly</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Progressively Adding Fable to JS Projects</title>
      <dc:creator>Angel Daniel Munoz Gonzalez</dc:creator>
      <pubDate>Fri, 07 Jan 2022 05:20:32 +0000</pubDate>
      <link>https://forem.com/tunaxor/progressively-adding-fable-to-js-projects-2f7m</link>
      <guid>https://forem.com/tunaxor/progressively-adding-fable-to-js-projects-2f7m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;You can find the sources for the code we'll use in this post in &lt;a href="https://github.com/AngelMunoz/blogpostdrafts/tree/main/progressively" rel="noopener noreferrer"&gt;this repository's directory&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;Hopefully you have had an amazing end of the year and the holidays are finally finishing for many (mine are done for sure), what a better time to start something new or even better yet progressively enhancing something that already exists!&lt;/p&gt;

&lt;h2&gt;
  
  
  First of all What is Fable and what are the alternatives?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://fable.io" rel="noopener noreferrer"&gt;Fable&lt;/a&gt; project is a &lt;code&gt;F#&lt;/code&gt; -&amp;gt; &lt;code&gt;&amp;lt;Lang&amp;gt;&lt;/code&gt; compiler where &lt;code&gt;&amp;lt;Lang&amp;gt;&lt;/code&gt; is any of &lt;code&gt;Javascript&lt;/code&gt;, &lt;code&gt;Typescript&lt;/code&gt; and &lt;code&gt;Python&lt;/code&gt; at the time of writing the last two are more experimental and the main support is for Javascript future iterations of Fable will cover these languages and even more like &lt;a href="https://github.com/fable-compiler/Fable/pull/2653" rel="noopener noreferrer"&gt;PHP, Rust, Dart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One could say Fable is a direct competitor to projects like Typescript, Flow, Purescript, ReasonML and similar projects which aim to write in a typed language to produce safer code. While every of the mentioned projects has their own pros/cons I won't discuss that here since it's very likely you already chose Fable for the new code effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does Fable do?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;As mentioned above Fable is an &lt;code&gt;F#&lt;/code&gt; -&amp;gt; &lt;code&gt;&amp;lt;Lang&amp;gt;&lt;/code&gt; compiler but from here on we will talk about fable in the context of an &lt;code&gt;F#&lt;/code&gt; -&amp;gt; &lt;code&gt;Javascript&lt;/code&gt; compiler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fable is distributed via a &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools" rel="noopener noreferrer"&gt;.NET tool&lt;/a&gt; which can be installed both globally and locally via &lt;code&gt;dotnet tool install -g fable&lt;/code&gt; (or remove the &lt;code&gt;-g&lt;/code&gt; to do it locally) meaning that it requires that you have the &lt;a href="https://get.dot.net" rel="noopener noreferrer"&gt;.NET SDK&lt;/a&gt; installed on your machine.&lt;/p&gt;

&lt;p&gt;Before continuing into the complete topic there are a few myths that I want to get out of the way for sure&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fable is a framework&lt;/li&gt;
&lt;li&gt;Fable is react&lt;/li&gt;
&lt;li&gt;Fable is for SPAs&lt;/li&gt;
&lt;li&gt;Fable is for new projects&lt;/li&gt;
&lt;li&gt;Fable requires Node.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The truth and only truth is that Fable is an F# -&amp;gt; JS Compiler hence you can treat it like any other, just like you would treat typescript or purescript or reasonml or even babel. The reality would actually be&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fable is a tool to produce Javascript code.&lt;/li&gt;
&lt;li&gt;Fable allows you to use React JS code as well as Svelte, Lit, and others.&lt;/li&gt;
&lt;li&gt;Fable can be used for single JS scripts as well as full SPA projects there are no hard requirements.&lt;/li&gt;
&lt;li&gt;Fable produces JS code, so wherever you can consume JS code Fable will work&lt;sup&gt;*&lt;/sup&gt; even slightly older projects.&lt;/li&gt;
&lt;li&gt;Fable can be used in any context outside nodejs like any python, ruby, or php servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;sup&gt;*&lt;/sup&gt; Fable emits modern javascript so your target needs to at least support the ES2015 ecmascript specification, in some cases (for older environments) further processing will be needed to re-transpile the JS code to ES3/ES5.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having that said, let's dive into the topic at last.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Javascript projects
&lt;/h2&gt;

&lt;p&gt;If you are not very familiar to nodejs because you are either a backend dev from other ecosystem or a frontend developer who happens to use node because that's how the ecosystem is right now I'll give you a run down the very basics of a node project.&lt;/p&gt;

&lt;p&gt;type on the terminal on a new directory the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it should print something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/path/to/directory/package.json:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That... in essence is a node project even if you haven't created a &lt;code&gt;index.js&lt;/code&gt; as is indicated in the main field, of course you can add the file and adjust the newly created package.json like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/index.js&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node ./src/index.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run &lt;code&gt;npm start&lt;/code&gt; or &lt;code&gt;npm run start&lt;/code&gt; you should see the lovely &lt;em&gt;Hello, World!&lt;/em&gt; message.&lt;/p&gt;

&lt;p&gt;Yeah, yeah I know you didn't come here for the node part; New Fable projects are also very very simple, with the .NET SDK installed you just need to run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# you can delete the previous src directory just to make this work smoothly&lt;/span&gt;
dotnet new console &lt;span class="nt"&gt;-lang&lt;/span&gt; F# &lt;span class="nt"&gt;-o&lt;/span&gt; src
&lt;span class="c"&gt;# The following commands are to install the fable .NET tool locally&lt;/span&gt;
dotnet new tool-manifest
dotnet tool &lt;span class="nb"&gt;install &lt;/span&gt;fable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While we can run fable from the terminal whenever we want we can leverage the fact that we're inside a node project and leverage the npm commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/Program.fs.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start-app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node ./src/Program.fs.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dotnet fable src --run npm run start-app"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NOTE:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;required&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;output&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now you can enter &lt;code&gt;npm start&lt;/code&gt; and you'll see Fable compiling then getting a &lt;em&gt;Hello from F#&lt;/em&gt; even if it was not run in .NET but node.js&lt;/p&gt;

&lt;p&gt;If you want to target node.js this is a basic setup you can try. There are other tools like pm2 or nodemon that can help you minimize the developer feedback loop that can re-run servers or node processes and allow the debugger to connect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing Javascript projects
&lt;/h2&gt;

&lt;p&gt;Let's create a new node project again and this time instead of creating a console app, we will create a class library&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
dotnet new classlib &lt;span class="nt"&gt;-o&lt;/span&gt; src &lt;span class="nt"&gt;-lang&lt;/span&gt; F#
&lt;span class="c"&gt;# The following commands are to install the fable .NET tool locally&lt;/span&gt;
dotnet new tool-manifest
dotnet tool &lt;span class="nb"&gt;install &lt;/span&gt;fable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;replace the contents of the package.json file with the following contents&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"project2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start-app"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node ./src/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dotnet fable src --run npm run start-app"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file structure looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package.json
  | src
    index.js
    Library.fs
    src.fsproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then add the following index.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;hello&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Library.fs.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Javascript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and run &lt;code&gt;npm start&lt;/code&gt; you should see the lovely &lt;em&gt;Hello Javascript&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point we can assume that any existing project and file on those projects in this case represented by our &lt;code&gt;index.js&lt;/code&gt; can introduce F# in the code base and the reasoning for this is that this is the exact mechanism you can use to introduce typescript in a code base. Although, typescript benefits Javascript code from the editor and other tooling around so it's arguably easier but I digress, the main point is that you can either incrementally add F# code to your javascript project and let them co-exist side by side or you can slowly migrate JS code to F# code, file by file, module by module, however you feel the pace is better for your team.&lt;/p&gt;

&lt;p&gt;Now let's take this exercise a little bit further just to show that we can do it, we will create a new vitejs project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init vite@latest project3 --template lit
cd project3 &amp;amp;&amp;amp; npm install &amp;amp;&amp;amp; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should run a lit plain JS project let's add two simple F# files to &lt;code&gt;src&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- App.fsproj --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net6.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;GenerateDocumentationFile&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/GenerateDocumentationFile&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Compile&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Library.fs"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Fable.Lit"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.4.1"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Library.fs&lt;/span&gt;
&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Lit&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LitElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my-counter"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// This call is obligatory to initialize the web component&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="o"&gt;_,&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;LitElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="o"&gt;{|&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Prop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|})&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;useState&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Value&lt;/span&gt;
    &lt;span class="n"&gt;html&lt;/span&gt;
        &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""
        &amp;lt;article&amp;gt;
            &amp;lt;p&amp;gt;{counter}&amp;lt;/p&amp;gt;
            &amp;lt;button @click={fun _ -&amp;gt; setCounter(counter + 1)}&amp;gt;+&amp;lt;/button&amp;gt;
            &amp;lt;button @click={fun _ -&amp;gt; setCounter(counter - 1)}&amp;gt;-&amp;lt;/button&amp;gt;
        &amp;lt;/article&amp;gt;
        """&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inside &lt;code&gt;src/my-element.js&lt;/code&gt; we will import the compiled fable file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/my-element.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;css&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// this should be already there&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Library.fs.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// this line&lt;/span&gt;
&lt;span class="c1"&gt;// ... the rest of the file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;next we will modify the "dev" script in package.json for the following &lt;code&gt;"dev": "dotnet fable src --watch --run vite serve"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lastly we will add inside &lt;code&gt;index.html&lt;/code&gt; the following content right inside the body element&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;my-element&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is child content&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- This content is from our Fable Code  --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;my-counter&amp;gt;&amp;lt;/my-counter&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/my-element&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now let's run &lt;code&gt;npm run dev&lt;/code&gt; and visit &lt;code&gt;localhost:3000&lt;/code&gt; and we should see our counter inside the default&lt;/p&gt;

&lt;p&gt;This particular technique is very powerful given that Fable.Lit produces web components meaning that you can render those in any existing framework so you can slowly migrate away from angular/react/vue using Fable.Lit components!&lt;/p&gt;

&lt;h3&gt;
  
  
  Typescript Projects
&lt;/h3&gt;

&lt;p&gt;In the case of typescript projects you only need to add &lt;code&gt;"allowJS": true&lt;/code&gt; to the &lt;code&gt;tsconfig.json&lt;/code&gt;'s compiler options&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Webpack and other bundlers/dev servers
&lt;/h3&gt;

&lt;p&gt;In the last example we used vite which loads ES modules by default, other modern tools like webpack/snowpack/parcel should be exactly the same, just import those fable output files where you need them and the bundler should manage that since (and I emphasize) Fable output is modern standards javascript.&lt;/p&gt;

&lt;p&gt;that will make typescript to also process your Fable output files&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: If you have a strict config enabled you might face issues with &lt;em&gt;implicit any&lt;/em&gt; errors, you can also add &lt;code&gt;"checkJs": false&lt;/code&gt; so your Fable output doesn't get re-checked by typescript (after all it has already been checked by F#)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Good ol' monoliths
&lt;/h2&gt;

&lt;p&gt;I hear you, you have a [Django | ASP.NET | Express | Flask | RoR | Laravel | Slim] app that doesn't use a SPA like tool chain that serves it's own javascript files statically (wwwroot in the case of .NET)&lt;/p&gt;

&lt;p&gt;I have good news for you, you can use any of the approaches above to produce your javascript and include it in your &lt;code&gt;JS modules&lt;/code&gt; or directly in the &lt;code&gt;index.html&lt;/code&gt; there are are some caveats about Fable projects with JS dependencies. There are two approaches here you are managing your JS dependencies in any of the following ways&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;via NPM&lt;/li&gt;
&lt;li&gt;via CDN/Local Dist file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If it's via NPM and you already have sorted out how to serve those then it's about just using Fable as usual and let it emit your JS files directly to the static files directory via fable's outDir flag: &lt;code&gt;-o --outDir Redirect compilation output to a directory&lt;/code&gt;, something along the lines of &lt;code&gt;dotnet fable fable-sources -o wwwroot&lt;/code&gt; and it should just work.&lt;/p&gt;

&lt;p&gt;If you need to handle dependencies via CDN/Local Dist file then some dependencies won't work because they use node like imports &lt;code&gt;import {} from 'lit/some/sub/directory.js&lt;/code&gt; browser imports need to start with &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;./&lt;/code&gt; or even &lt;code&gt;../&lt;/code&gt; so they can be valid ES module imports thankfully for this you can check out in a shameless plug one of the projects I'm working on: &lt;a href="https://github.com/AngelMunoz/Perla" rel="noopener noreferrer"&gt;Perla&lt;/a&gt; which handles this precise case but I digress, the ideal situation would be you with npm and already figured out how to serve node dependencies to your compiled code.&lt;/p&gt;

&lt;p&gt;Please remember that each F# file is equal to a single JS File when it's ran through fable so you can create scripts for specific pages, you don't need to import/export everything from a single entry point and you can use &lt;a href="https://github.com/fable-compiler/fable-browser" rel="noopener noreferrer"&gt;fable-browser&lt;/a&gt; to do DOM manipulation, so it is not necessary to add a whole SPA framework to enhance parts of your monolith.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;A brief recap, we just saw how to add Fable&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New node projects&lt;/li&gt;
&lt;li&gt;Existing node projects&lt;/li&gt;
&lt;li&gt;New/Existing Vite/Webpack/Typescript projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the short summary would be this&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the .NET SDK&lt;/li&gt;
&lt;li&gt;Create a new F# project (either console or class library)&lt;/li&gt;
&lt;li&gt;Install Fable as a local/global tool&lt;/li&gt;
&lt;li&gt;Integrate the fable command as part of your workflow (in our case the npm scripts we modified above)&lt;/li&gt;
&lt;li&gt;Run Fable and start enhancing with or migrating to F# your code base.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also we got remembered that Fable outputs Javascript, not react, not a SPA, not anything else (in the context of this blog post) so your existing knowledge of how to use Javascript inside a SPA, Monolith, Node.js applies exactly the same.&lt;/p&gt;

&lt;p&gt;I put a lot emphasis on that because I have seen people who believe Fable &lt;em&gt;must&lt;/em&gt; be used in a certain way or that there's a religious way to use it. No it's a tool and has several uses feel free to pick your own way to use it.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>fsharp</category>
      <category>fable</category>
    </item>
  </channel>
</rss>
