├── .gitignore ├── Blazor ├── App.razor ├── Blazor.csproj ├── Layout │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ └── NavMenu.razor.css ├── Pages │ ├── Home.razor │ └── Problem.razor ├── Program.cs ├── Properties │ └── launchSettings.json ├── _Imports.razor └── wwwroot │ ├── css │ ├── app.css │ └── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── favicon.png │ ├── icon-192.png │ ├── index.html │ └── sample-data │ └── weather.json ├── Bolero ├── BoleroPrime.Client │ ├── BoleroPrime.Client.fsproj │ ├── Main.fs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.fs │ └── wwwroot │ │ ├── css │ │ └── index.css │ │ ├── favicon.ico │ │ └── main.html └── BoleroPrime.Server │ ├── BoleroPrime.Server.fsproj │ ├── Index.fs │ ├── Properties │ └── launchSettings.json │ ├── Startup.fs │ └── data │ └── books.json ├── Console6 ├── Console6.fsproj └── Program.fs ├── Console7 ├── Console.fsproj └── Program.fs ├── Console8 ├── Console.fsproj └── Program.fs ├── Fable ├── .config │ └── dotnet-tools.json ├── Fable.fsproj ├── Program.fs ├── Program.fs.js ├── index.html ├── package-lock.json ├── package.json └── vite.config.ts ├── FunBlazor ├── App.fs ├── FunBlazor.fsproj ├── Properties │ └── launchSettings.json ├── README.md ├── Startup.fs └── wwwroot │ ├── favicon.ico │ └── index.html ├── Images ├── WebAssembly_compile.png ├── blazor-webassembly.png ├── results-8.png ├── results-aot.png ├── results.png ├── wasi-overview.jpg └── webtools.png ├── SharedProblem ├── Library.fs.js ├── Prime.fs └── SharedProblem.fsproj ├── Stats └── graphs.fsx ├── Wasi ├── Program.fs ├── Properties │ └── AssemblyInfo.cs ├── README.md ├── Wasi.fsproj └── runtimeconfig.template.json ├── Wasm.sln └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | 5 | Fable/fable_modules/ 6 | Fable/node_modules/ 7 | Prime.fs.js 8 | -------------------------------------------------------------------------------- /Blazor/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /Blazor/Blazor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | true 9 | 10 | 11 | 12 | true 13 | 14 | 15 | true 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Blazor/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 |
3 | 6 | 7 |
8 |
9 | About 10 |
11 | 12 |
13 | @Body 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /Blazor/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Blazor/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 | 24 | 25 | @code { 26 | private bool collapseNavMenu = true; 27 | 28 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 29 | 30 | private void ToggleNavMenu() 31 | { 32 | collapseNavMenu = !collapseNavMenu; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Blazor/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .bi { 15 | display: inline-block; 16 | position: relative; 17 | width: 1.25rem; 18 | height: 1.25rem; 19 | margin-right: 0.75rem; 20 | top: -1px; 21 | background-size: cover; 22 | } 23 | 24 | .bi-house-door-fill-nav-menu { 25 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); 26 | } 27 | 28 | .bi-plus-square-fill-nav-menu { 29 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); 30 | } 31 | 32 | .bi-list-nested-nav-menu { 33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); 34 | } 35 | 36 | .nav-item { 37 | font-size: 0.9rem; 38 | padding-bottom: 0.5rem; 39 | } 40 | 41 | .nav-item:first-of-type { 42 | padding-top: 1rem; 43 | } 44 | 45 | .nav-item:last-of-type { 46 | padding-bottom: 1rem; 47 | } 48 | 49 | .nav-item ::deep a { 50 | color: #d7d7d7; 51 | border-radius: 4px; 52 | height: 3rem; 53 | display: flex; 54 | align-items: center; 55 | line-height: 3rem; 56 | } 57 | 58 | .nav-item ::deep a.active { 59 | background-color: rgba(255,255,255,0.37); 60 | color: white; 61 | } 62 | 63 | .nav-item ::deep a:hover { 64 | background-color: rgba(255,255,255,0.1); 65 | color: white; 66 | } 67 | 68 | @media (min-width: 641px) { 69 | .navbar-toggler { 70 | display: none; 71 | } 72 | 73 | .collapse { 74 | /* Never collapse the sidebar for wide screens */ 75 | display: block; 76 | } 77 | 78 | .nav-scrollable { 79 | /* Allow sidebar to scroll for tall menus */ 80 | height: calc(100vh - 3.5rem); 81 | overflow-y: auto; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Blazor/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Home 4 | 5 |

Hello, world!

6 | 7 | Welcome to your new app. 8 | -------------------------------------------------------------------------------- /Blazor/Pages/Problem.razor: -------------------------------------------------------------------------------- 1 | @page "/problem" 2 | 3 | Problem 4 | 5 |

Problem

6 | 7 |

@answer

8 | 9 | 10 | 11 | @code { 12 | private string answer = ""; 13 | 14 | private void Calculate() 15 | { 16 | answer = "Starting"; 17 | answer = SharedProblem.Prime.run(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Blazor/Program.cs: -------------------------------------------------------------------------------- 1 | using Blazor; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | builder.RootComponents.Add("#app"); 7 | builder.RootComponents.Add("head::after"); 8 | 9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 10 | 11 | await builder.Build().RunAsync(); 12 | -------------------------------------------------------------------------------- /Blazor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:20395", 8 | "sslPort": 44357 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 17 | "applicationUrl": "http://localhost:5214", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 27 | "applicationUrl": "https://localhost:7188;http://localhost:5214", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Blazor/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using Blazor 10 | @using Blazor.Layout 11 | -------------------------------------------------------------------------------- /Blazor/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | a, .btn-link { 10 | color: #0071c1; 11 | } 12 | 13 | .btn-primary { 14 | color: #fff; 15 | background-color: #1b6ec2; 16 | border-color: #1861ac; 17 | } 18 | 19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 21 | } 22 | 23 | .content { 24 | padding-top: 1.1rem; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid red; 33 | } 34 | 35 | .validation-message { 36 | color: red; 37 | } 38 | 39 | #blazor-error-ui { 40 | background: lightyellow; 41 | bottom: 0; 42 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 43 | display: none; 44 | left: 0; 45 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 46 | position: fixed; 47 | width: 100%; 48 | z-index: 1000; 49 | } 50 | 51 | #blazor-error-ui .dismiss { 52 | cursor: pointer; 53 | position: absolute; 54 | right: 0.75rem; 55 | top: 0.5rem; 56 | } 57 | 58 | .blazor-error-boundary { 59 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 60 | padding: 1rem 1rem 1rem 3.7rem; 61 | color: white; 62 | } 63 | 64 | .blazor-error-boundary::after { 65 | content: "An error has occurred." 66 | } 67 | 68 | .loading-progress { 69 | position: relative; 70 | display: block; 71 | width: 8rem; 72 | height: 8rem; 73 | margin: 20vh auto 1rem auto; 74 | } 75 | 76 | .loading-progress circle { 77 | fill: none; 78 | stroke: #e0e0e0; 79 | stroke-width: 0.6rem; 80 | transform-origin: 50% 50%; 81 | transform: rotate(-90deg); 82 | } 83 | 84 | .loading-progress circle:last-child { 85 | stroke: #1b6ec2; 86 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 87 | transition: stroke-dasharray 0.05s ease-in-out; 88 | } 89 | 90 | .loading-progress-text { 91 | position: absolute; 92 | text-align: center; 93 | font-weight: bold; 94 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 95 | } 96 | 97 | .loading-progress-text:after { 98 | content: var(--blazor-load-percentage-text, "Loading"); 99 | } 100 | 101 | code { 102 | color: #c02d76; 103 | } 104 | -------------------------------------------------------------------------------- /Blazor/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Blazor/wwwroot/favicon.png -------------------------------------------------------------------------------- /Blazor/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Blazor/wwwroot/icon-192.png -------------------------------------------------------------------------------- /Blazor/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Blazor 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 | An unhandled error has occurred. 26 | Reload 27 | 🗙 28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Blazor/wwwroot/sample-data/weather.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2022-01-06", 4 | "temperatureC": 1, 5 | "summary": "Freezing" 6 | }, 7 | { 8 | "date": "2022-01-07", 9 | "temperatureC": 14, 10 | "summary": "Bracing" 11 | }, 12 | { 13 | "date": "2022-01-08", 14 | "temperatureC": -13, 15 | "summary": "Freezing" 16 | }, 17 | { 18 | "date": "2022-01-09", 19 | "temperatureC": -16, 20 | "summary": "Balmy" 21 | }, 22 | { 23 | "date": "2022-01-10", 24 | "temperatureC": -2, 25 | "summary": "Chilly" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/BoleroPrime.Client.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/Main.fs: -------------------------------------------------------------------------------- 1 | module BoleroPrime.Client.Main 2 | 3 | open System 4 | open Elmish 5 | open Bolero 6 | open Bolero.Html 7 | open Bolero.Remoting 8 | open Bolero.Remoting.Client 9 | open Bolero.Templating.Client 10 | 11 | /// Routing endpoints definition. 12 | type Page = 13 | | [] Home 14 | | [] Problem 15 | 16 | /// The Elmish application's model. 17 | type Model = 18 | { 19 | page: Page 20 | answer: string 21 | error: string option 22 | } 23 | 24 | 25 | let initModel = 26 | { 27 | page = Home 28 | error = None 29 | answer = "Not calculated yet" 30 | } 31 | 32 | /// The Elmish application's update messages. 33 | type Message = 34 | | SetPage of Page 35 | | SolveProblem 36 | | Error of exn 37 | | ClearError 38 | 39 | let solveProblem () = 40 | SharedProblem.Prime.run() 41 | 42 | let update message model = 43 | match message with 44 | | SetPage page -> 45 | { model with page = page }, Cmd.none 46 | 47 | | SolveProblem -> 48 | { model with answer = solveProblem() }, Cmd.none 49 | 50 | | Error exn -> 51 | { model with error = Some exn.Message }, Cmd.none 52 | | ClearError -> 53 | { model with error = None }, Cmd.none 54 | 55 | /// Connects the routing system to the Elmish application. 56 | let router = Router.infer SetPage (fun model -> model.page) 57 | 58 | type Main = Template<"wwwroot/main.html"> 59 | 60 | let homePage model dispatch = 61 | Main.Home().Elt() 62 | 63 | let problemPage model dispatch = 64 | Main.Problem() 65 | .SolveProblem(fun _ -> dispatch SolveProblem) 66 | .Answer(model.answer) 67 | .Elt() 68 | 69 | let menuItem (model: Model) (page: Page) (text: string) = 70 | Main.MenuItem() 71 | .Active(if model.page = page then "is-active" else "") 72 | .Url(router.Link page) 73 | .Text(text) 74 | .Elt() 75 | 76 | let view model dispatch = 77 | Main() 78 | .Menu(concat { 79 | menuItem model Home "Home" 80 | menuItem model Problem "Problem" 81 | }) 82 | .Body( 83 | cond model.page <| function 84 | | Home -> homePage model dispatch 85 | | Problem -> problemPage model dispatch 86 | ) 87 | .Error( 88 | cond model.error <| function 89 | | None -> empty() 90 | | Some err -> 91 | Main.ErrorNotification() 92 | .Text(err) 93 | .Hide(fun _ -> dispatch ClearError) 94 | .Elt() 95 | ) 96 | .Elt() 97 | 98 | type MyApp() = 99 | inherit ProgramComponent() 100 | 101 | override this.Program = 102 | Program.mkProgram (fun _ -> initModel, Cmd.ofMsg ClearError) update view 103 | |> Program.withRouter router 104 | #if DEBUG 105 | |> Program.withHotReload 106 | #endif 107 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true 5 | }, 6 | "profiles": { 7 | "BoleroPrime.Client": { 8 | "commandName": "Project", 9 | "launchBrowser": true, 10 | "environmentVariables": { 11 | "DOTNET_ENVIRONMENT": "Development" 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace BoleroPrime.Client 2 | 3 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 4 | open Bolero.Remoting.Client 5 | 6 | module Program = 7 | 8 | [] 9 | let Main args = 10 | let builder = WebAssemblyHostBuilder.CreateDefault(args) 11 | builder.RootComponents.Add("#main") 12 | builder.Services.AddRemoting(builder.HostEnvironment) |> ignore 13 | builder.Build().RunAsync() |> ignore 14 | 0 15 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/wwwroot/css/index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | #main { 11 | flex: 1; 12 | } 13 | 14 | #main > .columns { 15 | min-height: 100%; 16 | } 17 | 18 | .sidebar { 19 | width: 250px; 20 | background: whitesmoke; 21 | min-height: 100%; 22 | } 23 | 24 | #counter { 25 | width: 80px; 26 | } 27 | 28 | #notification-area { 29 | position: fixed; 30 | bottom: 0; 31 | left: 0; 32 | right: 0; 33 | } 34 | 35 | #notification-area > div { 36 | max-width: 600px; 37 | margin-left: auto; 38 | margin-right: auto; 39 | margin-bottom: 5px; 40 | } 41 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Bolero/BoleroPrime.Client/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Client/wwwroot/main.html: -------------------------------------------------------------------------------- 1 |
2 | 14 |
15 |
16 | ${Body} 17 | 18 |
19 |
${Error}
20 |
21 | 22 | 38 | 39 | 48 | 49 | 55 | 56 |
57 |
58 |
59 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Server/BoleroPrime.Server.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | true 21 | PreserveNewest 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Server/Index.fs: -------------------------------------------------------------------------------- 1 | module BoleroPrime.Server.Index 2 | 3 | open Bolero 4 | open Bolero.Html 5 | open Bolero.Server.Html 6 | open BoleroPrime 7 | 8 | let page = doctypeHtml { 9 | head { 10 | meta { attr.charset "UTF-8" } 11 | meta { attr.name "viewport"; attr.content "width=device-width, initial-scale=1.0" } 12 | title { "Bolero Application" } 13 | ``base`` { attr.href "/" } 14 | link { attr.rel "stylesheet"; attr.href "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" } 15 | link { attr.rel "stylesheet"; attr.href "css/index.css" } 16 | } 17 | body { 18 | nav { 19 | attr.``class`` "navbar is-dark" 20 | "role" => "navigation" 21 | attr.aria "label" "main navigation" 22 | div { 23 | attr.``class`` "navbar-brand" 24 | a { 25 | attr.``class`` "navbar-item has-text-weight-bold is-size-5" 26 | attr.href "https://fsbolero.io" 27 | img { attr.style "height:40px"; attr.src "https://github.com/fsbolero/website/raw/master/src/Website/img/wasm-fsharp.png" } 28 | "  Bolero" 29 | } 30 | } 31 | } 32 | div { attr.id "main"; rootComp } 33 | boleroScript 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true 5 | }, 6 | "profiles": { 7 | "BoleroPrime.Server": { 8 | "commandName": "Project", 9 | "launchBrowser": true, 10 | "applicationUrl": "http://localhost:58755", 11 | "environmentVariables": { 12 | "ASPNETCORE_ENVIRONMENT": "Development" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Server/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace BoleroPrime.Server 2 | 3 | open Microsoft.AspNetCore 4 | open Microsoft.AspNetCore.Authentication.Cookies 5 | open Microsoft.AspNetCore.Builder 6 | open Microsoft.AspNetCore.Hosting 7 | open Microsoft.Extensions.DependencyInjection 8 | open Bolero 9 | open Bolero.Remoting.Server 10 | open Bolero.Server 11 | open BoleroPrime 12 | open Bolero.Templating.Server 13 | 14 | type Startup() = 15 | 16 | // This method gets called by the runtime. Use this method to add services to the container. 17 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 18 | member this.ConfigureServices(services: IServiceCollection) = 19 | services.AddMvc() |> ignore 20 | services.AddServerSideBlazor() |> ignore 21 | services 22 | .AddAuthorization() 23 | .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 24 | .AddCookie() 25 | .Services 26 | .AddBoleroHost() 27 | #if DEBUG 28 | .AddHotReload(templateDir = __SOURCE_DIRECTORY__ + "/../BoleroPrime.Client") 29 | #endif 30 | |> ignore 31 | 32 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 33 | member this.Configure(app: IApplicationBuilder, env: IWebHostEnvironment) = 34 | app 35 | .UseAuthentication() 36 | .UseRemoting() 37 | .UseStaticFiles() 38 | .UseRouting() 39 | .UseBlazorFrameworkFiles() 40 | .UseEndpoints(fun endpoints -> 41 | #if DEBUG 42 | endpoints.UseHotReload() 43 | #endif 44 | endpoints.MapBlazorHub() |> ignore 45 | endpoints.MapFallbackToBolero(Index.page) |> ignore) 46 | |> ignore 47 | 48 | module Program = 49 | 50 | [] 51 | let main args = 52 | WebHost 53 | .CreateDefaultBuilder(args) 54 | .UseStaticWebAssets() 55 | .UseStartup() 56 | .Build() 57 | .Run() 58 | 0 59 | -------------------------------------------------------------------------------- /Bolero/BoleroPrime.Server/data/books.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "The Fellowship of the Ring", 4 | "author": "J.R.R Tolkien", 5 | "publishDate": "1954-07-29", 6 | "isbn": "978-0345339706" 7 | }, 8 | { 9 | "title": "The Two Towers", 10 | "author": "J.R.R Tolkien", 11 | "publishDate": "1954-11-11", 12 | "isbn": "978-0345339713" 13 | }, 14 | { 15 | "title": "The Return of the King", 16 | "author": "J.R.R Tolkien", 17 | "publishDate": "1955-10-20", 18 | "isbn": "978-0345339737" 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /Console6/Console6.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Console6/Program.fs: -------------------------------------------------------------------------------- 1 | printfn "Solving problem 6" 2 | printfn "%s" (SharedProblem.Prime.run()) -------------------------------------------------------------------------------- /Console7/Console.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net7.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Console7/Program.fs: -------------------------------------------------------------------------------- 1 | printfn "Solving problem" 2 | printfn "%s" (SharedProblem.Prime.run()) -------------------------------------------------------------------------------- /Console8/Console.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Console8/Program.fs: -------------------------------------------------------------------------------- 1 | printfn "Solving problem" 2 | printfn "%s" (SharedProblem.Prime.run()) -------------------------------------------------------------------------------- /Fable/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fable": { 6 | "version": "4.9.0", 7 | "commands": [ 8 | "fable" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Fable/Fable.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Fable/Program.fs: -------------------------------------------------------------------------------- 1 | open Browser 2 | 3 | // Mutable variable to count the number of times we clicked the button 4 | let mutable count = 0 5 | 6 | // Get a reference to our button and cast the Element to an HTMLButtonElement 7 | let myButton = document.querySelector(".my-button") :?> Browser.Types.HTMLButtonElement 8 | 9 | // Register our listener 10 | myButton.onclick <- fun _ -> 11 | let answer = SharedProblem.Prime.run() 12 | myButton.innerText <- answer 13 | -------------------------------------------------------------------------------- /Fable/Program.fs.js: -------------------------------------------------------------------------------- 1 | import { createAtom } from "./fable_modules/fable-library.4.9.0/Util.js"; 2 | import { run } from "../SharedProblem/Prime.fs.js"; 3 | 4 | export let count = createAtom(0); 5 | 6 | export const myButton = document.querySelector(".my-button"); 7 | 8 | myButton.onclick = ((_arg) => { 9 | const answer = run(); 10 | myButton.innerText = answer; 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /Fable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fable 7 | 8 | 9 |
10 |

Fable is running

11 |

Click button to solve problem:

12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Fable/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fable", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "fable", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "vite": "^5.0.10" 13 | } 14 | }, 15 | "node_modules/@esbuild/aix-ppc64": { 16 | "version": "0.19.11", 17 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", 18 | "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==", 19 | "cpu": [ 20 | "ppc64" 21 | ], 22 | "dev": true, 23 | "optional": true, 24 | "os": [ 25 | "aix" 26 | ], 27 | "engines": { 28 | "node": ">=12" 29 | } 30 | }, 31 | "node_modules/@esbuild/android-arm": { 32 | "version": "0.19.11", 33 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz", 34 | "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==", 35 | "cpu": [ 36 | "arm" 37 | ], 38 | "dev": true, 39 | "optional": true, 40 | "os": [ 41 | "android" 42 | ], 43 | "engines": { 44 | "node": ">=12" 45 | } 46 | }, 47 | "node_modules/@esbuild/android-arm64": { 48 | "version": "0.19.11", 49 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz", 50 | "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==", 51 | "cpu": [ 52 | "arm64" 53 | ], 54 | "dev": true, 55 | "optional": true, 56 | "os": [ 57 | "android" 58 | ], 59 | "engines": { 60 | "node": ">=12" 61 | } 62 | }, 63 | "node_modules/@esbuild/android-x64": { 64 | "version": "0.19.11", 65 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz", 66 | "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==", 67 | "cpu": [ 68 | "x64" 69 | ], 70 | "dev": true, 71 | "optional": true, 72 | "os": [ 73 | "android" 74 | ], 75 | "engines": { 76 | "node": ">=12" 77 | } 78 | }, 79 | "node_modules/@esbuild/darwin-arm64": { 80 | "version": "0.19.11", 81 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz", 82 | "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==", 83 | "cpu": [ 84 | "arm64" 85 | ], 86 | "dev": true, 87 | "optional": true, 88 | "os": [ 89 | "darwin" 90 | ], 91 | "engines": { 92 | "node": ">=12" 93 | } 94 | }, 95 | "node_modules/@esbuild/darwin-x64": { 96 | "version": "0.19.11", 97 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz", 98 | "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==", 99 | "cpu": [ 100 | "x64" 101 | ], 102 | "dev": true, 103 | "optional": true, 104 | "os": [ 105 | "darwin" 106 | ], 107 | "engines": { 108 | "node": ">=12" 109 | } 110 | }, 111 | "node_modules/@esbuild/freebsd-arm64": { 112 | "version": "0.19.11", 113 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz", 114 | "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==", 115 | "cpu": [ 116 | "arm64" 117 | ], 118 | "dev": true, 119 | "optional": true, 120 | "os": [ 121 | "freebsd" 122 | ], 123 | "engines": { 124 | "node": ">=12" 125 | } 126 | }, 127 | "node_modules/@esbuild/freebsd-x64": { 128 | "version": "0.19.11", 129 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz", 130 | "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==", 131 | "cpu": [ 132 | "x64" 133 | ], 134 | "dev": true, 135 | "optional": true, 136 | "os": [ 137 | "freebsd" 138 | ], 139 | "engines": { 140 | "node": ">=12" 141 | } 142 | }, 143 | "node_modules/@esbuild/linux-arm": { 144 | "version": "0.19.11", 145 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz", 146 | "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==", 147 | "cpu": [ 148 | "arm" 149 | ], 150 | "dev": true, 151 | "optional": true, 152 | "os": [ 153 | "linux" 154 | ], 155 | "engines": { 156 | "node": ">=12" 157 | } 158 | }, 159 | "node_modules/@esbuild/linux-arm64": { 160 | "version": "0.19.11", 161 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz", 162 | "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==", 163 | "cpu": [ 164 | "arm64" 165 | ], 166 | "dev": true, 167 | "optional": true, 168 | "os": [ 169 | "linux" 170 | ], 171 | "engines": { 172 | "node": ">=12" 173 | } 174 | }, 175 | "node_modules/@esbuild/linux-ia32": { 176 | "version": "0.19.11", 177 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz", 178 | "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==", 179 | "cpu": [ 180 | "ia32" 181 | ], 182 | "dev": true, 183 | "optional": true, 184 | "os": [ 185 | "linux" 186 | ], 187 | "engines": { 188 | "node": ">=12" 189 | } 190 | }, 191 | "node_modules/@esbuild/linux-loong64": { 192 | "version": "0.19.11", 193 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz", 194 | "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==", 195 | "cpu": [ 196 | "loong64" 197 | ], 198 | "dev": true, 199 | "optional": true, 200 | "os": [ 201 | "linux" 202 | ], 203 | "engines": { 204 | "node": ">=12" 205 | } 206 | }, 207 | "node_modules/@esbuild/linux-mips64el": { 208 | "version": "0.19.11", 209 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz", 210 | "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==", 211 | "cpu": [ 212 | "mips64el" 213 | ], 214 | "dev": true, 215 | "optional": true, 216 | "os": [ 217 | "linux" 218 | ], 219 | "engines": { 220 | "node": ">=12" 221 | } 222 | }, 223 | "node_modules/@esbuild/linux-ppc64": { 224 | "version": "0.19.11", 225 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz", 226 | "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==", 227 | "cpu": [ 228 | "ppc64" 229 | ], 230 | "dev": true, 231 | "optional": true, 232 | "os": [ 233 | "linux" 234 | ], 235 | "engines": { 236 | "node": ">=12" 237 | } 238 | }, 239 | "node_modules/@esbuild/linux-riscv64": { 240 | "version": "0.19.11", 241 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz", 242 | "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==", 243 | "cpu": [ 244 | "riscv64" 245 | ], 246 | "dev": true, 247 | "optional": true, 248 | "os": [ 249 | "linux" 250 | ], 251 | "engines": { 252 | "node": ">=12" 253 | } 254 | }, 255 | "node_modules/@esbuild/linux-s390x": { 256 | "version": "0.19.11", 257 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz", 258 | "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==", 259 | "cpu": [ 260 | "s390x" 261 | ], 262 | "dev": true, 263 | "optional": true, 264 | "os": [ 265 | "linux" 266 | ], 267 | "engines": { 268 | "node": ">=12" 269 | } 270 | }, 271 | "node_modules/@esbuild/linux-x64": { 272 | "version": "0.19.11", 273 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz", 274 | "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==", 275 | "cpu": [ 276 | "x64" 277 | ], 278 | "dev": true, 279 | "optional": true, 280 | "os": [ 281 | "linux" 282 | ], 283 | "engines": { 284 | "node": ">=12" 285 | } 286 | }, 287 | "node_modules/@esbuild/netbsd-x64": { 288 | "version": "0.19.11", 289 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz", 290 | "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==", 291 | "cpu": [ 292 | "x64" 293 | ], 294 | "dev": true, 295 | "optional": true, 296 | "os": [ 297 | "netbsd" 298 | ], 299 | "engines": { 300 | "node": ">=12" 301 | } 302 | }, 303 | "node_modules/@esbuild/openbsd-x64": { 304 | "version": "0.19.11", 305 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz", 306 | "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==", 307 | "cpu": [ 308 | "x64" 309 | ], 310 | "dev": true, 311 | "optional": true, 312 | "os": [ 313 | "openbsd" 314 | ], 315 | "engines": { 316 | "node": ">=12" 317 | } 318 | }, 319 | "node_modules/@esbuild/sunos-x64": { 320 | "version": "0.19.11", 321 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz", 322 | "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==", 323 | "cpu": [ 324 | "x64" 325 | ], 326 | "dev": true, 327 | "optional": true, 328 | "os": [ 329 | "sunos" 330 | ], 331 | "engines": { 332 | "node": ">=12" 333 | } 334 | }, 335 | "node_modules/@esbuild/win32-arm64": { 336 | "version": "0.19.11", 337 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz", 338 | "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==", 339 | "cpu": [ 340 | "arm64" 341 | ], 342 | "dev": true, 343 | "optional": true, 344 | "os": [ 345 | "win32" 346 | ], 347 | "engines": { 348 | "node": ">=12" 349 | } 350 | }, 351 | "node_modules/@esbuild/win32-ia32": { 352 | "version": "0.19.11", 353 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz", 354 | "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==", 355 | "cpu": [ 356 | "ia32" 357 | ], 358 | "dev": true, 359 | "optional": true, 360 | "os": [ 361 | "win32" 362 | ], 363 | "engines": { 364 | "node": ">=12" 365 | } 366 | }, 367 | "node_modules/@esbuild/win32-x64": { 368 | "version": "0.19.11", 369 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz", 370 | "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==", 371 | "cpu": [ 372 | "x64" 373 | ], 374 | "dev": true, 375 | "optional": true, 376 | "os": [ 377 | "win32" 378 | ], 379 | "engines": { 380 | "node": ">=12" 381 | } 382 | }, 383 | "node_modules/@rollup/rollup-android-arm-eabi": { 384 | "version": "4.9.2", 385 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.2.tgz", 386 | "integrity": "sha512-RKzxFxBHq9ysZ83fn8Iduv3A283K7zPPYuhL/z9CQuyFrjwpErJx0h4aeb/bnJ+q29GRLgJpY66ceQ/Wcsn3wA==", 387 | "cpu": [ 388 | "arm" 389 | ], 390 | "dev": true, 391 | "optional": true, 392 | "os": [ 393 | "android" 394 | ] 395 | }, 396 | "node_modules/@rollup/rollup-android-arm64": { 397 | "version": "4.9.2", 398 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.2.tgz", 399 | "integrity": "sha512-yZ+MUbnwf3SHNWQKJyWh88ii2HbuHCFQnAYTeeO1Nb8SyEiWASEi5dQUygt3ClHWtA9My9RQAYkjvrsZ0WK8Xg==", 400 | "cpu": [ 401 | "arm64" 402 | ], 403 | "dev": true, 404 | "optional": true, 405 | "os": [ 406 | "android" 407 | ] 408 | }, 409 | "node_modules/@rollup/rollup-darwin-arm64": { 410 | "version": "4.9.2", 411 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.2.tgz", 412 | "integrity": "sha512-vqJ/pAUh95FLc/G/3+xPqlSBgilPnauVf2EXOQCZzhZJCXDXt/5A8mH/OzU6iWhb3CNk5hPJrh8pqJUPldN5zw==", 413 | "cpu": [ 414 | "arm64" 415 | ], 416 | "dev": true, 417 | "optional": true, 418 | "os": [ 419 | "darwin" 420 | ] 421 | }, 422 | "node_modules/@rollup/rollup-darwin-x64": { 423 | "version": "4.9.2", 424 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.2.tgz", 425 | "integrity": "sha512-otPHsN5LlvedOprd3SdfrRNhOahhVBwJpepVKUN58L0RnC29vOAej1vMEaVU6DadnpjivVsNTM5eNt0CcwTahw==", 426 | "cpu": [ 427 | "x64" 428 | ], 429 | "dev": true, 430 | "optional": true, 431 | "os": [ 432 | "darwin" 433 | ] 434 | }, 435 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 436 | "version": "4.9.2", 437 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.2.tgz", 438 | "integrity": "sha512-ewG5yJSp+zYKBYQLbd1CUA7b1lSfIdo9zJShNTyc2ZP1rcPrqyZcNlsHgs7v1zhgfdS+kW0p5frc0aVqhZCiYQ==", 439 | "cpu": [ 440 | "arm" 441 | ], 442 | "dev": true, 443 | "optional": true, 444 | "os": [ 445 | "linux" 446 | ] 447 | }, 448 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 449 | "version": "4.9.2", 450 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.2.tgz", 451 | "integrity": "sha512-pL6QtV26W52aCWTG1IuFV3FMPL1m4wbsRG+qijIvgFO/VBsiXJjDPE/uiMdHBAO6YcpV4KvpKtd0v3WFbaxBtg==", 452 | "cpu": [ 453 | "arm64" 454 | ], 455 | "dev": true, 456 | "optional": true, 457 | "os": [ 458 | "linux" 459 | ] 460 | }, 461 | "node_modules/@rollup/rollup-linux-arm64-musl": { 462 | "version": "4.9.2", 463 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.2.tgz", 464 | "integrity": "sha512-On+cc5EpOaTwPSNetHXBuqylDW+765G/oqB9xGmWU3npEhCh8xu0xqHGUA+4xwZLqBbIZNcBlKSIYfkBm6ko7g==", 465 | "cpu": [ 466 | "arm64" 467 | ], 468 | "dev": true, 469 | "optional": true, 470 | "os": [ 471 | "linux" 472 | ] 473 | }, 474 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 475 | "version": "4.9.2", 476 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.2.tgz", 477 | "integrity": "sha512-Wnx/IVMSZ31D/cO9HSsU46FjrPWHqtdF8+0eyZ1zIB5a6hXaZXghUKpRrC4D5DcRTZOjml2oBhXoqfGYyXKipw==", 478 | "cpu": [ 479 | "riscv64" 480 | ], 481 | "dev": true, 482 | "optional": true, 483 | "os": [ 484 | "linux" 485 | ] 486 | }, 487 | "node_modules/@rollup/rollup-linux-x64-gnu": { 488 | "version": "4.9.2", 489 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.2.tgz", 490 | "integrity": "sha512-ym5x1cj4mUAMBummxxRkI4pG5Vht1QMsJexwGP8547TZ0sox9fCLDHw9KCH9c1FO5d9GopvkaJsBIOkTKxksdw==", 491 | "cpu": [ 492 | "x64" 493 | ], 494 | "dev": true, 495 | "optional": true, 496 | "os": [ 497 | "linux" 498 | ] 499 | }, 500 | "node_modules/@rollup/rollup-linux-x64-musl": { 501 | "version": "4.9.2", 502 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.2.tgz", 503 | "integrity": "sha512-m0hYELHGXdYx64D6IDDg/1vOJEaiV8f1G/iO+tejvRCJNSwK4jJ15e38JQy5Q6dGkn1M/9KcyEOwqmlZ2kqaZg==", 504 | "cpu": [ 505 | "x64" 506 | ], 507 | "dev": true, 508 | "optional": true, 509 | "os": [ 510 | "linux" 511 | ] 512 | }, 513 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 514 | "version": "4.9.2", 515 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.2.tgz", 516 | "integrity": "sha512-x1CWburlbN5JjG+juenuNa4KdedBdXLjZMp56nHFSHTOsb/MI2DYiGzLtRGHNMyydPGffGId+VgjOMrcltOksA==", 517 | "cpu": [ 518 | "arm64" 519 | ], 520 | "dev": true, 521 | "optional": true, 522 | "os": [ 523 | "win32" 524 | ] 525 | }, 526 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 527 | "version": "4.9.2", 528 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.2.tgz", 529 | "integrity": "sha512-VVzCB5yXR1QlfsH1Xw1zdzQ4Pxuzv+CPr5qpElpKhVxlxD3CRdfubAG9mJROl6/dmj5gVYDDWk8sC+j9BI9/kQ==", 530 | "cpu": [ 531 | "ia32" 532 | ], 533 | "dev": true, 534 | "optional": true, 535 | "os": [ 536 | "win32" 537 | ] 538 | }, 539 | "node_modules/@rollup/rollup-win32-x64-msvc": { 540 | "version": "4.9.2", 541 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.2.tgz", 542 | "integrity": "sha512-SYRedJi+mweatroB+6TTnJYLts0L0bosg531xnQWtklOI6dezEagx4Q0qDyvRdK+qgdA3YZpjjGuPFtxBmddBA==", 543 | "cpu": [ 544 | "x64" 545 | ], 546 | "dev": true, 547 | "optional": true, 548 | "os": [ 549 | "win32" 550 | ] 551 | }, 552 | "node_modules/esbuild": { 553 | "version": "0.19.11", 554 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz", 555 | "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==", 556 | "dev": true, 557 | "hasInstallScript": true, 558 | "bin": { 559 | "esbuild": "bin/esbuild" 560 | }, 561 | "engines": { 562 | "node": ">=12" 563 | }, 564 | "optionalDependencies": { 565 | "@esbuild/aix-ppc64": "0.19.11", 566 | "@esbuild/android-arm": "0.19.11", 567 | "@esbuild/android-arm64": "0.19.11", 568 | "@esbuild/android-x64": "0.19.11", 569 | "@esbuild/darwin-arm64": "0.19.11", 570 | "@esbuild/darwin-x64": "0.19.11", 571 | "@esbuild/freebsd-arm64": "0.19.11", 572 | "@esbuild/freebsd-x64": "0.19.11", 573 | "@esbuild/linux-arm": "0.19.11", 574 | "@esbuild/linux-arm64": "0.19.11", 575 | "@esbuild/linux-ia32": "0.19.11", 576 | "@esbuild/linux-loong64": "0.19.11", 577 | "@esbuild/linux-mips64el": "0.19.11", 578 | "@esbuild/linux-ppc64": "0.19.11", 579 | "@esbuild/linux-riscv64": "0.19.11", 580 | "@esbuild/linux-s390x": "0.19.11", 581 | "@esbuild/linux-x64": "0.19.11", 582 | "@esbuild/netbsd-x64": "0.19.11", 583 | "@esbuild/openbsd-x64": "0.19.11", 584 | "@esbuild/sunos-x64": "0.19.11", 585 | "@esbuild/win32-arm64": "0.19.11", 586 | "@esbuild/win32-ia32": "0.19.11", 587 | "@esbuild/win32-x64": "0.19.11" 588 | } 589 | }, 590 | "node_modules/fsevents": { 591 | "version": "2.3.3", 592 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 593 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 594 | "dev": true, 595 | "hasInstallScript": true, 596 | "optional": true, 597 | "os": [ 598 | "darwin" 599 | ], 600 | "engines": { 601 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 602 | } 603 | }, 604 | "node_modules/nanoid": { 605 | "version": "3.3.7", 606 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 607 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 608 | "dev": true, 609 | "funding": [ 610 | { 611 | "type": "github", 612 | "url": "https://github.com/sponsors/ai" 613 | } 614 | ], 615 | "bin": { 616 | "nanoid": "bin/nanoid.cjs" 617 | }, 618 | "engines": { 619 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 620 | } 621 | }, 622 | "node_modules/picocolors": { 623 | "version": "1.0.0", 624 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 625 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 626 | "dev": true 627 | }, 628 | "node_modules/postcss": { 629 | "version": "8.4.32", 630 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", 631 | "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", 632 | "dev": true, 633 | "funding": [ 634 | { 635 | "type": "opencollective", 636 | "url": "https://opencollective.com/postcss/" 637 | }, 638 | { 639 | "type": "tidelift", 640 | "url": "https://tidelift.com/funding/github/npm/postcss" 641 | }, 642 | { 643 | "type": "github", 644 | "url": "https://github.com/sponsors/ai" 645 | } 646 | ], 647 | "dependencies": { 648 | "nanoid": "^3.3.7", 649 | "picocolors": "^1.0.0", 650 | "source-map-js": "^1.0.2" 651 | }, 652 | "engines": { 653 | "node": "^10 || ^12 || >=14" 654 | } 655 | }, 656 | "node_modules/rollup": { 657 | "version": "4.9.2", 658 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.2.tgz", 659 | "integrity": "sha512-66RB8OtFKUTozmVEh3qyNfH+b+z2RXBVloqO2KCC/pjFaGaHtxP9fVfOQKPSGXg2mElmjmxjW/fZ7iKrEpMH5Q==", 660 | "dev": true, 661 | "bin": { 662 | "rollup": "dist/bin/rollup" 663 | }, 664 | "engines": { 665 | "node": ">=18.0.0", 666 | "npm": ">=8.0.0" 667 | }, 668 | "optionalDependencies": { 669 | "@rollup/rollup-android-arm-eabi": "4.9.2", 670 | "@rollup/rollup-android-arm64": "4.9.2", 671 | "@rollup/rollup-darwin-arm64": "4.9.2", 672 | "@rollup/rollup-darwin-x64": "4.9.2", 673 | "@rollup/rollup-linux-arm-gnueabihf": "4.9.2", 674 | "@rollup/rollup-linux-arm64-gnu": "4.9.2", 675 | "@rollup/rollup-linux-arm64-musl": "4.9.2", 676 | "@rollup/rollup-linux-riscv64-gnu": "4.9.2", 677 | "@rollup/rollup-linux-x64-gnu": "4.9.2", 678 | "@rollup/rollup-linux-x64-musl": "4.9.2", 679 | "@rollup/rollup-win32-arm64-msvc": "4.9.2", 680 | "@rollup/rollup-win32-ia32-msvc": "4.9.2", 681 | "@rollup/rollup-win32-x64-msvc": "4.9.2", 682 | "fsevents": "~2.3.2" 683 | } 684 | }, 685 | "node_modules/source-map-js": { 686 | "version": "1.0.2", 687 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 688 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 689 | "dev": true, 690 | "engines": { 691 | "node": ">=0.10.0" 692 | } 693 | }, 694 | "node_modules/vite": { 695 | "version": "5.0.10", 696 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", 697 | "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", 698 | "dev": true, 699 | "dependencies": { 700 | "esbuild": "^0.19.3", 701 | "postcss": "^8.4.32", 702 | "rollup": "^4.2.0" 703 | }, 704 | "bin": { 705 | "vite": "bin/vite.js" 706 | }, 707 | "engines": { 708 | "node": "^18.0.0 || >=20.0.0" 709 | }, 710 | "funding": { 711 | "url": "https://github.com/vitejs/vite?sponsor=1" 712 | }, 713 | "optionalDependencies": { 714 | "fsevents": "~2.3.3" 715 | }, 716 | "peerDependencies": { 717 | "@types/node": "^18.0.0 || >=20.0.0", 718 | "less": "*", 719 | "lightningcss": "^1.21.0", 720 | "sass": "*", 721 | "stylus": "*", 722 | "sugarss": "*", 723 | "terser": "^5.4.0" 724 | }, 725 | "peerDependenciesMeta": { 726 | "@types/node": { 727 | "optional": true 728 | }, 729 | "less": { 730 | "optional": true 731 | }, 732 | "lightningcss": { 733 | "optional": true 734 | }, 735 | "sass": { 736 | "optional": true 737 | }, 738 | "stylus": { 739 | "optional": true 740 | }, 741 | "sugarss": { 742 | "optional": true 743 | }, 744 | "terser": { 745 | "optional": true 746 | } 747 | } 748 | } 749 | } 750 | } 751 | -------------------------------------------------------------------------------- /Fable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fable", 3 | "version": "1.0.0", 4 | "main": "Program.fs.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "vite": "^5.0.10" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Fable/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | // https://vitejs.dev/config/ 3 | export default defineConfig({ 4 | clearScreen: false, 5 | server: { 6 | watch: { 7 | ignored: [ 8 | "**/*.fs" // Don't watch F# files 9 | ] 10 | } 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /FunBlazor/App.fs: -------------------------------------------------------------------------------- 1 | // hot-reload 2 | // hot-reload is the flag to let cli know this file should be included 3 | // It has dependency requirement: the root is the app which is used in the Startup.fs 4 | // All other files which want have hot reload, need to drill down to that file, and all the middle file should also add the '// hot-reload' flag at the top of taht file 5 | [] 6 | module FunBlazor.App 7 | 8 | open FSharp.Data.Adaptive 9 | open Fun.Blazor 10 | 11 | let app = 12 | adaptiview () { 13 | let! answer, setAnswer = cval("Uncalculated").WithSetter() 14 | 15 | div { 16 | div { $"{answer}" } 17 | button { 18 | onclick (fun _ -> setAnswer (SharedProblem.Prime.run())) 19 | "Calculate" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FunBlazor/FunBlazor.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /FunBlazor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:57135/", 7 | "sslPort": 44390 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "FunBlazor": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /FunBlazor/README.md: -------------------------------------------------------------------------------- 1 | This is a blazor WASM mode app with Fun.Blazor 2 | 3 | wwwroot/index.html is the entry point for browser. it will load all the necessary files. 4 | 5 | App.fs contains UI logic 6 | 7 | Startup.fs is for hooking up everything and configuring server 8 | 9 | 10 | ## Dev with hot-reload 11 | 12 | Open terminal and run 13 | dotnet run 14 | 15 | Open terminal and run 16 | fun-blazor watch .\FunBlazor.fsproj 17 | 18 | > dotnet tool install --global Fun.Blazor.Cli --version 2.1.* 19 | -------------------------------------------------------------------------------- /FunBlazor/Startup.fs: -------------------------------------------------------------------------------- 1 | #nowarn "0020" 2 | 3 | open System 4 | open Microsoft.Extensions.DependencyInjection 5 | open Microsoft.AspNetCore.Components.WebAssembly.Hosting 6 | open Fun.Blazor 7 | open FunBlazor 8 | 9 | let builder = WebAssemblyHostBuilder.CreateDefault(Environment.GetCommandLineArgs()) 10 | 11 | #if DEBUG 12 | builder.AddFunBlazor("#app", html.hotReloadComp(app, "FunBlazor.App.app")) 13 | #else 14 | builder.AddFunBlazor("#app", app) 15 | #endif 16 | 17 | builder.Services.AddFunBlazorWasm() 18 | 19 | builder.Build().RunAsync() 20 | -------------------------------------------------------------------------------- /FunBlazor/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/FunBlazor/wwwroot/favicon.ico -------------------------------------------------------------------------------- /FunBlazor/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Fun Blazor 8 | 9 | 10 | 11 | 12 |
Loading...
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Images/WebAssembly_compile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/WebAssembly_compile.png -------------------------------------------------------------------------------- /Images/blazor-webassembly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/blazor-webassembly.png -------------------------------------------------------------------------------- /Images/results-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/results-8.png -------------------------------------------------------------------------------- /Images/results-aot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/results-aot.png -------------------------------------------------------------------------------- /Images/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/results.png -------------------------------------------------------------------------------- /Images/wasi-overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/wasi-overview.jpg -------------------------------------------------------------------------------- /Images/webtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidglassborow/fsharp-wasm/8ea32c647a3c8ec176fabb4bfaa2002f521e6cf3/Images/webtools.png -------------------------------------------------------------------------------- /SharedProblem/Library.fs.js: -------------------------------------------------------------------------------- 1 | import { fill } from "../Fable/src/fable_modules/fable-library.4.0.0-theta-018/Array.js"; 2 | import { singleton, append, delay, cache, unfold, skipWhile, item } from "../Fable/src/fable_modules/fable-library.4.0.0-theta-018/Seq.js"; 3 | import { op_Subtraction, now } from "../Fable/src/fable_modules/fable-library.4.0.0-theta-018/Date.js"; 4 | import { totalSeconds } from "../Fable/src/fable_modules/fable-library.4.0.0-theta-018/TimeSpan.js"; 5 | 6 | function primesAPF32() { 7 | const oddprimes = () => { 8 | let i_9, r_2; 9 | const BUFSZ = (1 << 17) | 0; 10 | const buf = new Uint32Array(BUFSZ >> 5); 11 | const BUFRNG = ((BUFSZ >>> 0) << 1) >>> 0; 12 | const mkpi = (i_mut, low_mut) => { 13 | let i_2, r, i_7; 14 | mkpi: 15 | while (true) { 16 | const i = i_mut, low = low_mut; 17 | if (i >= BUFSZ) { 18 | const nlow = low + BUFRNG; 19 | fill(buf, 0, buf.length, 0); 20 | const low_1 = nlow; 21 | const max = (low_1 + BUFRNG) - 1; 22 | const max_1 = (max < low_1) ? (-1 >>> 0) : max; 23 | const sqrtlm = Math.sqrt(max_1) >>> 0; 24 | const sqrtlmndx = (~(~((sqrtlm - 3) >>> 1))) | 0; 25 | if (low_1 <= 3) { 26 | for (let i_1 = 0; i_1 <= sqrtlmndx; i_1++) { 27 | if ((i_2 = (i_1 | 0), ((buf[i_2 >> 5] & ((1 << (i_2 & 31)) >>> 0)) >>> 0) === 0)) { 28 | const p = ((i_1 + i_1) + 3) >>> 0; 29 | const low_2 = 3; 30 | const s = p * p; 31 | const p_1 = p; 32 | const cull$0027 = (i_3_mut) => { 33 | cull$0027: 34 | while (true) { 35 | const i_3 = i_3_mut; 36 | if (i_3 < BUFSZ) { 37 | const i_4 = i_3 | 0; 38 | const w = (i_4 >> 5) | 0; 39 | buf[w] = ((buf[w] | ((1 << (i_4 & 31)) >>> 0)) >>> 0); 40 | i_3_mut = (i_3 + (~(~p_1))); 41 | continue cull$0027; 42 | } 43 | break; 44 | } 45 | }; 46 | cull$0027((s >= low_2) ? (~(~((s - low_2) >>> 1))) : ((r = (((low_2 - s) >>> 1) % p_1), (r === 0) ? 0 : (~(~(p_1 - r)))))); 47 | } 48 | } 49 | } 50 | else { 51 | item(0, skipWhile((p_2) => { 52 | let r_1; 53 | const s_1 = p_2 * p_2; 54 | if ((p_2 > 65535) ? true : (s_1 > max_1)) { 55 | return false; 56 | } 57 | else { 58 | const low_3 = low_1; 59 | const s_2 = s_1; 60 | const p_3 = p_2; 61 | const cull$0027_1 = (i_5_mut) => { 62 | cull$0027_1: 63 | while (true) { 64 | const i_5 = i_5_mut; 65 | if (i_5 < BUFSZ) { 66 | const i_6 = i_5 | 0; 67 | const w_1 = (i_6 >> 5) | 0; 68 | buf[w_1] = ((buf[w_1] | ((1 << (i_6 & 31)) >>> 0)) >>> 0); 69 | i_5_mut = (i_5 + (~(~p_3))); 70 | continue cull$0027_1; 71 | } 72 | break; 73 | } 74 | }; 75 | cull$0027_1((s_2 >= low_3) ? (~(~((s_2 - low_3) >>> 1))) : ((r_1 = (((low_3 - s_2) >>> 1) % p_3), (r_1 === 0) ? 0 : (~(~(p_3 - r_1)))))); 76 | return true; 77 | } 78 | }, baseprimes)); 79 | } 80 | i_mut = 0; 81 | low_mut = nlow; 82 | continue mkpi; 83 | } 84 | else if ((i_7 = (i | 0), ((buf[i_7 >> 5] & ((1 << (i_7 & 31)) >>> 0)) >>> 0) === 0)) { 85 | return [i, low]; 86 | } 87 | else { 88 | i_mut = (i + 1); 89 | low_mut = low; 90 | continue mkpi; 91 | } 92 | break; 93 | } 94 | }; 95 | const low_4 = 3; 96 | const max_2 = (low_4 + BUFRNG) - 1; 97 | const max_3 = (max_2 < low_4) ? (-1 >>> 0) : max_2; 98 | const sqrtlm_1 = Math.sqrt(max_3) >>> 0; 99 | const sqrtlmndx_1 = (~(~((sqrtlm_1 - 3) >>> 1))) | 0; 100 | if (low_4 <= 3) { 101 | for (let i_8 = 0; i_8 <= sqrtlmndx_1; i_8++) { 102 | if ((i_9 = (i_8 | 0), ((buf[i_9 >> 5] & ((1 << (i_9 & 31)) >>> 0)) >>> 0) === 0)) { 103 | const p_4 = ((i_8 + i_8) + 3) >>> 0; 104 | const low_5 = 3; 105 | const s_3 = p_4 * p_4; 106 | const p_5 = p_4; 107 | const cull$0027_2 = (i_10_mut) => { 108 | cull$0027_2: 109 | while (true) { 110 | const i_10 = i_10_mut; 111 | if (i_10 < BUFSZ) { 112 | const i_11 = i_10 | 0; 113 | const w_2 = (i_11 >> 5) | 0; 114 | buf[w_2] = ((buf[w_2] | ((1 << (i_11 & 31)) >>> 0)) >>> 0); 115 | i_10_mut = (i_10 + (~(~p_5))); 116 | continue cull$0027_2; 117 | } 118 | break; 119 | } 120 | }; 121 | cull$0027_2((s_3 >= low_5) ? (~(~((s_3 - low_5) >>> 1))) : ((r_2 = (((low_5 - s_3) >>> 1) % p_5), (r_2 === 0) ? 0 : (~(~(p_5 - r_2)))))); 122 | } 123 | } 124 | } 125 | else { 126 | item(0, skipWhile((p_6) => { 127 | let r_3; 128 | const s_4 = p_6 * p_6; 129 | if ((p_6 > 65535) ? true : (s_4 > max_3)) { 130 | return false; 131 | } 132 | else { 133 | const low_6 = low_4; 134 | const s_5 = s_4; 135 | const p_7 = p_6; 136 | const cull$0027_3 = (i_12_mut) => { 137 | cull$0027_3: 138 | while (true) { 139 | const i_12 = i_12_mut; 140 | if (i_12 < BUFSZ) { 141 | const i_13 = i_12 | 0; 142 | const w_3 = (i_13 >> 5) | 0; 143 | buf[w_3] = ((buf[w_3] | ((1 << (i_13 & 31)) >>> 0)) >>> 0); 144 | i_12_mut = (i_12 + (~(~p_7))); 145 | continue cull$0027_3; 146 | } 147 | break; 148 | } 149 | }; 150 | cull$0027_3((s_5 >= low_6) ? (~(~((s_5 - low_6) >>> 1))) : ((r_3 = (((low_6 - s_5) >>> 1) % p_7), (r_3 === 0) ? 0 : (~(~(p_7 - r_3)))))); 151 | return true; 152 | } 153 | }, baseprimes)); 154 | } 155 | return unfold((tupledArg) => { 156 | const i_14 = tupledArg[0] | 0; 157 | const lw = tupledArg[1]; 158 | const patternInput = mkpi(i_14, lw); 159 | const nlw = patternInput[1]; 160 | const ni = patternInput[0] | 0; 161 | const p_8 = nlw + (((ni >>> 0) << 1) >>> 0); 162 | return (p_8 < lw) ? (void 0) : [p_8, [ni + 1, nlw]]; 163 | }, [0, 3]); 164 | }; 165 | const baseprimes = cache(oddprimes()); 166 | return delay(() => append(singleton(2), delay(oddprimes))); 167 | } 168 | 169 | function calculatePrime(nth) { 170 | return item(nth, primesAPF32()); 171 | } 172 | 173 | function time(f) { 174 | const start = now(); 175 | const answer = f(); 176 | const stop = op_Subtraction(now(), start); 177 | return [answer, totalSeconds(stop)]; 178 | } 179 | 180 | export function calculateHugePrime(nth) { 181 | const patternInput = time(() => calculatePrime(nth)); 182 | const tim = patternInput[1]; 183 | const ans = patternInput[0]; 184 | return `Calculated ${nth} prime in ${tim} = ${ans}`; 185 | } 186 | 187 | -------------------------------------------------------------------------------- /SharedProblem/Prime.fs: -------------------------------------------------------------------------------- 1 | namespace SharedProblem 2 | 3 | module Prime = 4 | 5 | // https://stackoverflow.com/a/17820204/131701 6 | let private primesAPF32() = 7 | let rec oddprimes() = 8 | let BUFSZ = 1<<<17 in let buf = Array.zeroCreate (BUFSZ>>>5) in let BUFRNG = uint32 BUFSZ<<<1 9 | let inline testbit i = (buf.[i >>> 5] &&& (1u <<< (i &&& 0x1F))) = 0u 10 | let inline cullbit i = let w = i >>> 5 in buf.[w] <- buf.[w] ||| (1u <<< (i &&& 0x1F)) 11 | let inline cullp p s low = let rec cull' i = if i < BUFSZ then cullbit i; cull' (i + int p) 12 | cull' (if s >= low then int((s - low) >>> 1) 13 | else let r = ((low - s) >>> 1) % p in if r = 0u then 0 else int(p - r)) 14 | let inline cullpg low = //cull composites from whole buffer page for efficiency 15 | let max = low + BUFRNG - 1u in let max = if max < low then uint32(-1) else max 16 | let sqrtlm = uint32(sqrt(float max)) in let sqrtlmndx = int((sqrtlm - 3u) >>> 1) 17 | if low <= 3u then for i = 0 to sqrtlmndx do if testbit i then let p = uint32(i + i + 3) in cullp p (p * p) 3u 18 | else baseprimes |> Seq.skipWhile (fun p -> //force side effect of culling to limit of buffer 19 | let s = p * p in if p > 0xFFFFu || s > max then false else cullp p s low; true) |> Seq.item 0 |> ignore 20 | let rec mkpi i low = 21 | if i >= BUFSZ then let nlow = low + BUFRNG in Array.fill buf 0 buf.Length 0u; cullpg nlow; mkpi 0 nlow 22 | else (if testbit i then i,low else mkpi (i + 1) low) 23 | cullpg 3u; Seq.unfold (fun (i,lw) -> //force cull the first buffer page then doit 24 | let ni,nlw = mkpi i lw in let p = nlw + (uint32 ni <<< 1) 25 | if p < lw then None else Some(p,(ni+1,nlw))) (0,3u) 26 | and baseprimes = oddprimes() |> Seq.cache 27 | seq { yield 2u; yield! oddprimes() } 28 | 29 | 30 | let private calculatePrime nth = 31 | primesAPF32() |> Seq.item nth 32 | 33 | /// Time a problem, use simple DateTime so compatible with Fable etc. 34 | let private time f = 35 | let start = System.DateTime.Now 36 | let answer = f() 37 | let stop = System.DateTime.Now - start 38 | answer,stop.TotalSeconds 39 | 40 | let private calculateHugePrime nth = 41 | let ans, tim = time <| fun () -> calculatePrime nth 42 | $"Calculated {nth} prime in {tim} = {ans}" 43 | 44 | let run () = 45 | //calculateHugePrime 2_500_000 46 | calculateHugePrime 10_000_000 47 | //calculateHugePrime 100_000_000 -------------------------------------------------------------------------------- /SharedProblem/SharedProblem.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Stats/graphs.fsx: -------------------------------------------------------------------------------- 1 | #r "nuget: Plotly.NET" 2 | open Plotly.NET 3 | 4 | let tenMillion = [ 5 | "Wasm Safari Debug", 15.88 6 | "Wasm Safari Release", 11.79 7 | "Wasm Safari Release AOT", 11.129 8 | "Wasm Chrome Debug", 35.2 9 | "Wasm Chrome Release", 28.99 10 | "Wasm Chrome Release AOT", 28.88 11 | "Native .net7", 1.93 12 | "Native .net6", 1.96 13 | "Fable Safari", 4.58 14 | "Fable Chrome", 1.32 15 | ] 16 | 17 | let net8 = [ 18 | "Wasm Firefox Release AOT", 13.41 19 | "Wasitime", 11.31 20 | "Wasm Safari Release AOT", 9.53 21 | "Wasm Chrome Release AOT", 9.14 22 | "Fable Firebox", 2.70 23 | "Fable Chrome", 1.59 24 | "Fable Safari", 1.24 25 | "Native .net7 (debug)", 1.93 26 | "Native .net8 (debug)", 1.84 27 | "Native .net8 (release)", 0.83 28 | ] 29 | 30 | Chart.Column( 31 | net8 |> List.map snd, 32 | net8 |> List.map fst ) |> Chart.show 33 | -------------------------------------------------------------------------------- /Wasi/Program.fs: -------------------------------------------------------------------------------- 1 | printfn "Solving problem in Wasi" 2 | printfn "%s" (SharedProblem.Prime.run()) -------------------------------------------------------------------------------- /Wasi/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | 4 | [assembly:System.Runtime.Versioning.SupportedOSPlatform("wasi")] 5 | -------------------------------------------------------------------------------- /Wasi/README.md: -------------------------------------------------------------------------------- 1 | ## .NET WASI app 2 | 3 | ## Build 4 | 5 | You can build the app from Visual Studio or from the command-line: 6 | 7 | ``` 8 | dotnet build -c Debug/Release 9 | ``` 10 | 11 | After building the app, the result is in the `bin/$(Configuration)/net8.0/wasi-wasm/AppBundle` directory. 12 | 13 | ## Run 14 | 15 | You can build the app from Visual Studio or the command-line: 16 | 17 | ``` 18 | dotnet run -c Debug/Release 19 | ``` 20 | 21 | Or directly start node from the AppBundle directory: 22 | 23 | ``` 24 | wasmtime bin/$(Configuration)/net8.0/browser-wasm/AppBundle/Wasi.wasm 25 | ``` 26 | 27 | See also: https://devblogs.microsoft.com/dotnet/extending-web-assembly-to-the-cloud/ 28 | -------------------------------------------------------------------------------- /Wasi/Wasi.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | wasi-wasm 5 | Exe 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Wasi/runtimeconfig.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "wasmHostProperties": { 3 | "perHostConfig": [ 4 | { 5 | "name": "wasmtime", 6 | "Host": "wasmtime" 7 | } 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Wasm.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33122.133 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazor", "Blazor\Blazor.csproj", "{4CA4EEEA-9D9B-4B1C-ADE2-F7A0310888AE}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SharedProblem", "SharedProblem\SharedProblem.fsproj", "{200AF1BF-762F-4B8B-ABF3-A50CDBE995A3}" 9 | EndProject 10 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Console", "Console\Console.fsproj", "{4CD0C2FB-0362-450F-9FC1-9CA3EF4C6970}" 11 | EndProject 12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fable", "Fable\src\Fable.fsproj", "{EA7FDB2A-4E2A-4CF4-94DA-B9012715B2A9}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{68420C39-625C-44AC-8F6B-8008ABD057D5}" 15 | ProjectSection(SolutionItems) = preProject 16 | readme.md = readme.md 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Bolero", "Bolero", "{78DAF1E7-4D83-47CE-A8D2-61723AED4853}" 20 | EndProject 21 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BoleroPrime.Client", "Bolero\BoleroPrime.Client\BoleroPrime.Client.fsproj", "{C7569116-FA91-41D6-AA22-AC7D54D90321}" 22 | EndProject 23 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "BoleroPrime.Server", "Bolero\BoleroPrime.Server\BoleroPrime.Server.fsproj", "{3FD61436-1BC0-4762-8F95-A3E3CE185C40}" 24 | EndProject 25 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Console6", "Console6\Console6.fsproj", "{E864BBD2-0430-4428-835F-F71BD1D301BE}" 26 | EndProject 27 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Wasi", "Wasi\Wasi.fsproj", "{3C6E74DC-D551-4D64-A370-4B272B37EBD4}" 28 | EndProject 29 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FunBlazor", "FunBlazor\FunBlazor.fsproj", "{A697E662-689A-418F-A989-0FFFA03A40FF}" 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {4CA4EEEA-9D9B-4B1C-ADE2-F7A0310888AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {4CA4EEEA-9D9B-4B1C-ADE2-F7A0310888AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {4CA4EEEA-9D9B-4B1C-ADE2-F7A0310888AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {4CA4EEEA-9D9B-4B1C-ADE2-F7A0310888AE}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {200AF1BF-762F-4B8B-ABF3-A50CDBE995A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {200AF1BF-762F-4B8B-ABF3-A50CDBE995A3}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {200AF1BF-762F-4B8B-ABF3-A50CDBE995A3}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {200AF1BF-762F-4B8B-ABF3-A50CDBE995A3}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {4CD0C2FB-0362-450F-9FC1-9CA3EF4C6970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {4CD0C2FB-0362-450F-9FC1-9CA3EF4C6970}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {4CD0C2FB-0362-450F-9FC1-9CA3EF4C6970}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {4CD0C2FB-0362-450F-9FC1-9CA3EF4C6970}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {EA7FDB2A-4E2A-4CF4-94DA-B9012715B2A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {EA7FDB2A-4E2A-4CF4-94DA-B9012715B2A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {EA7FDB2A-4E2A-4CF4-94DA-B9012715B2A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {EA7FDB2A-4E2A-4CF4-94DA-B9012715B2A9}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {C7569116-FA91-41D6-AA22-AC7D54D90321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {C7569116-FA91-41D6-AA22-AC7D54D90321}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {C7569116-FA91-41D6-AA22-AC7D54D90321}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {C7569116-FA91-41D6-AA22-AC7D54D90321}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {3FD61436-1BC0-4762-8F95-A3E3CE185C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 58 | {3FD61436-1BC0-4762-8F95-A3E3CE185C40}.Debug|Any CPU.Build.0 = Debug|Any CPU 59 | {3FD61436-1BC0-4762-8F95-A3E3CE185C40}.Release|Any CPU.ActiveCfg = Release|Any CPU 60 | {3FD61436-1BC0-4762-8F95-A3E3CE185C40}.Release|Any CPU.Build.0 = Release|Any CPU 61 | {E864BBD2-0430-4428-835F-F71BD1D301BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {E864BBD2-0430-4428-835F-F71BD1D301BE}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {E864BBD2-0430-4428-835F-F71BD1D301BE}.Release|Any CPU.ActiveCfg = Release|Any CPU 64 | {E864BBD2-0430-4428-835F-F71BD1D301BE}.Release|Any CPU.Build.0 = Release|Any CPU 65 | {3C6E74DC-D551-4D64-A370-4B272B37EBD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 66 | {3C6E74DC-D551-4D64-A370-4B272B37EBD4}.Debug|Any CPU.Build.0 = Debug|Any CPU 67 | {3C6E74DC-D551-4D64-A370-4B272B37EBD4}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {3C6E74DC-D551-4D64-A370-4B272B37EBD4}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {A697E662-689A-418F-A989-0FFFA03A40FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 70 | {A697E662-689A-418F-A989-0FFFA03A40FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 71 | {A697E662-689A-418F-A989-0FFFA03A40FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 72 | {A697E662-689A-418F-A989-0FFFA03A40FF}.Release|Any CPU.Build.0 = Release|Any CPU 73 | EndGlobalSection 74 | GlobalSection(SolutionProperties) = preSolution 75 | HideSolutionNode = FALSE 76 | EndGlobalSection 77 | GlobalSection(ExtensibilityGlobals) = postSolution 78 | SolutionGuid = {0264D51F-F18E-4CA2-829C-5F73408CFC01} 79 | EndGlobalSection 80 | GlobalSection(NestedProjects) = preSolution 81 | {C7569116-FA91-41D6-AA22-AC7D54D90321} = {78DAF1E7-4D83-47CE-A8D2-61723AED4853} 82 | {3FD61436-1BC0-4762-8F95-A3E3CE185C40} = {78DAF1E7-4D83-47CE-A8D2-61723AED4853} 83 | EndGlobalSection 84 | EndGlobal 85 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # A brief introduction to WebAssembly in .NET and F# 2 | 3 | This is my post in the annual [F# Advent](https://sergeytihon.com/2022/10/28/f-advent-calendar-in-english-2022/). 4 | I've decided to do some investigation on something I've not worked with before, WebAssembly (WASM), and in particular how it works in the .net and F# worlds. 5 | 6 | The questions I've looked at are: 7 | 8 | 1. What is WASM ? 9 | 2. Why was it created and where can I use it ? 10 | 3. How do you I use it from C# ? 11 | 4. How do you I use if from F# 12 | 5. How fast is it ? 13 | 14 | ## 1. What is WASM and why was it created ? 15 | 16 | [Web Assembly](https://en.wikipedia.org/wiki/WebAssembly) was created by a group including Mozilla, Microsoft, Apple and Google, back in 2015, as a way of having portable high performance apps written for a common 'assembly' target. I think of it like the CLR and JVM, and machine portable target to run low level code. It's the web's first binary / non-text language to run in browsers. 17 | 18 | - [The spec is on Github](https://github.com/WebAssembly/spec) 19 | - [Can I use](https://caniuse.com/?search=wasm) reports all reasonabiy up to date browsers support it including the mobile browsers on ios and android. 20 | 21 | It's designed so that multiple programming languages can be compiled into WASM: 22 | 23 | ![web-compile](Images/WebAssembly_compile.png) 24 | 25 | Image from [https://arghya.xyz/articles/webassembly-wasm-wasi/](https://arghya.xyz/articles/webassembly-wasm-wasi/) 26 | 27 | ## 2. Where can I use it ? 28 | 29 | Originally just in web browers, but it's starting to be seen server side now. 30 | 31 | Cloudflare supports running [WASM on cloud flare workers](https://blog.cloudflare.com/webassembly-on-cloudflare-workers/). There are various projects to run [WASM on Kubernetes](https://cloudblogs.microsoft.com/opensource/2020/04/07/announcing-krustlet-kubernetes-rust-kubelet-webassembly-wasm/). 32 | 33 | A key part of this is the [WebAssembly System Interface (WASI)](https://en.wikipedia.org/wiki/WebAssembly#WASI) initiative. When running WASM on a browser, WASM has no access to the outside world, it's in a secure sandbox and can only call back to the Javascript that is hosting it. WASI is an attempt to define operating system like APIs to standardise how server side WASM can talk to files, the network, etc. WASM and WASI are in flux at the moment, it's early days with none of the APIs set in stone yet. 34 | 35 | ![overview](Images/wasi-overview.jpg) 36 | 37 | Image from [https://files.speakerdeck.com/presentations/fbfddfe5eccb4700a3ae600b814a9ce9/slide_19.jpg](https://files.speakerdeck.com/presentations/fbfddfe5eccb4700a3ae600b814a9ce9/slide_19.jpg) 38 | 39 | 40 | There are various WASM runtimes available alreaday that implement WASI, the most popular seems to be [Wasmtime](https://wasmtime.dev), but there are others like [Wasmer](https://wasmer.io). 41 | 42 | 43 | Steve Sanderson, the author of Blazor, has got an [Experimental WASI SDK for .NET Core](https://github.com/SteveSandersonMS/dotnet-wasi-sdk) nuget that allows both C# and F# to be written to run inside WASI containers, and has an [excellant talk](https://www.youtube.com/watch?v=lP_qdhAHFlg) at NDC Porto 2022 where he goes into more detail on WASI and .NET. 44 | 45 | 46 | ## 3. How do you use it from C# ? 47 | 48 | It's built into .net these days, to allow C# to run inside the browser by being compiled to WASM. It's just a case of using BlazorWebAssembly as the Project SDK: 49 | 50 | ```csharp 51 | 52 | ``` 53 | 54 | ![blazor](Images/blazor-webassembly.png) 55 | 56 | The .NET build chain uses Mono, [compiled via Emscripten](https://github.com/dotnet/runtime/tree/main/src/mono/wasm), and can even [add native dependencies](https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-native-dependencies?view=aspnetcore-7.0#use-native-code) from C or C++. 57 | 58 | Once compiled, on startup the project uses the [WebAssemblyHost](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.webassembly.hosting.webassemblyhost), builders and interfaces to bootstrap the Aspnet pipeline with DI etc. 59 | 60 | Build in routing components provide the runtime WASM files for the browser to load as it starts up, you can see below the loading of the various DLLs to run in the browser, in release mode these are all bundled together. 61 | 62 | ![network-loading](/Images/webtools.png) 63 | 64 | ## 4. How do you use if from F# 65 | 66 | The F# blazor equivalents use the same underlying BlazorWebAssembly project SDK, and WebAssemblyHost. There are two projects I'm aware of in the space: 67 | 68 | - [Bolero](https://fsbolero.io) 69 | - [Fun Blazor](https://slaveoftime.github.io/Fun.Blazor.Docs/) 70 | 71 | I've only had a quick play with both, they both seem good, with various Computation Expressions, Elmish MVU mechanisms, hot-reloading, etc. to make writing nice functional style rich clients. Below are a couple of example code snippets I took from their homepages: 72 | 73 | ```fsharp 74 | // Bolero 75 | let loginForm= 76 | form { 77 | attr.id "login-form" 78 | input { attr.placeholder "First name" 79 | input { attr.placeholder "Last name" 80 | button { 81 | on.click (fun_ -> printfn "Welcome!") 82 | "Log In" 83 | } 84 | } 85 | 86 | // Fun Blazor: 87 | let entry = 88 | adaptiview () { 89 | let! count1, setCount1 = cval(1).WithSetter() 90 | div { 91 | h6 { $"Count1={count1}" } 92 | button { 93 | onclick (fun _ -> setCount1 (count1 + 1)) 94 | "Increase count 1" 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | ## 5. How fast is it ? 101 | 102 | One of the aims of WASM is to be fast, faster than Javascript is. It's compiled rather than interpreted. 103 | 104 | To run some quick perf tests I've choosen an implementation of the sieve of Eratosthenes as a test of pure computation throughput (rather than IO etc). I found [this crazy fast](https://stackoverflow.com/a/17820204/131701) implementation on StackOverflow. 105 | 106 | No, I don't know how it works, but it is incredibly fast, it can work out the first 10 million primes in under 2 seconds in native code. 107 | 108 | ```fsharp 109 | // https://stackoverflow.com/a/17820204/131701 110 | let private primesAPF32() = 111 | let rec oddprimes() = 112 | let BUFSZ = 1<<<17 in let buf = Array.zeroCreate (BUFSZ>>>5) in let BUFRNG = uint32 BUFSZ<<<1 113 | let inline testbit i = (buf.[i >>> 5] &&& (1u <<< (i &&& 0x1F))) = 0u 114 | let inline cullbit i = let w = i >>> 5 in buf.[w] <- buf.[w] ||| (1u <<< (i &&& 0x1F)) 115 | let inline cullp p s low = let rec cull' i = if i < BUFSZ then cullbit i; cull' (i + int p) 116 | cull' (if s >= low then int((s - low) >>> 1) 117 | else let r = ((low - s) >>> 1) % p in if r = 0u then 0 else int(p - r)) 118 | let inline cullpg low = //cull composites from whole buffer page for efficiency 119 | let max = low + BUFRNG - 1u in let max = if max < low then uint32(-1) else max 120 | let sqrtlm = uint32(sqrt(float max)) in let sqrtlmndx = int((sqrtlm - 3u) >>> 1) 121 | if low <= 3u then for i = 0 to sqrtlmndx do if testbit i then let p = uint32(i + i + 3) in cullp p (p * p) 3u 122 | else baseprimes |> Seq.skipWhile (fun p -> //force side effect of culling to limit of buffer 123 | let s = p * p in if p > 0xFFFFu || s > max then false else cullp p s low; true) |> Seq.item 0 |> ignore 124 | let rec mkpi i low = 125 | if i >= BUFSZ then let nlow = low + BUFRNG in Array.fill buf 0 buf.Length 0u; cullpg nlow; mkpi 0 nlow 126 | else (if testbit i then i,low else mkpi (i + 1) low) 127 | cullpg 3u; Seq.unfold (fun (i,lw) -> //force cull the first buffer page then doit 128 | let ni,nlw = mkpi i lw in let p = nlw + (uint32 ni <<< 1) 129 | if p < lw then None else Some(p,(ni+1,nlw))) (0,3u) 130 | and baseprimes = oddprimes() |> Seq.cache 131 | seq { yield 2u; yield! oddprimes() } 132 | 133 | let calculatePrime nth = 134 | primesAPF32() |> Seq.item nth 135 | ``` 136 | 137 | I playing around, and found the time to calculate the first 10 million primes seemed to give indicative results. Less than 10 million was too quick, and getting up into the half billion range started to take a minute or so. 138 | 139 | I compared Blazor with Bolero and Fun Blazor but they all gave the same sorts of performance, which makes sence given they are all running on the same underlying code. 140 | 141 | I quickly noticed that the browser made a huge difference to performance, so recorded different times for Safari (v16.1) and Chrome (v108). I also added native code and javascript (compiled via Fable) to compare to the WASM code. Times are in seconds: 142 | 143 | ![results](Images/results.png) 144 | 145 | Caveats for these tests: 146 | 147 | - They were done quickly, not rigourlessly 148 | - I didn't always run test in release mode. 149 | - All tests run on my 2015 iMac, a quad core 4Ghz i7. 150 | 151 | I was very surprised in the differences between Safari and Chrome, with Safari running wasm x2.5 faster than Chrome, but Chrome running Javascript x3 faster than Safari. I did compare .net6 and .net7, but they were really too close to see if there was any difference that wasn't noise. 152 | 153 | The fastest code in the end was Chrome running javascript via Fable, which is shocking, but I guess larger tests might help dotnet native where the JIT gets time to kick in. 154 | 155 | I did also try and run the tests compiled with the WASI experimental nuget package mentioned above, and run via *wasmtime*. I was able to get it working with F# ok, and running general code, but found it crashes in the primes problem, and generally seemed to have problems with recursive functions in F#. 156 | 157 | My overall take away from all of this is just **how good** Fable is, and how fast it is in the browsers, and where as wasm helps C# run in the browser, we in the F# community are very lucky to have the other option of running transpiled Javascript directly. 158 | 159 | Happy Xmas to all, 160 | Cheers, 161 | Dave 162 | 163 | # Update AOT 164 | 165 | Following [Paul Biggar](https://twitter.com/paulbiggar/status/1604833374929326081?s=46&t=GOI1I3iFkS_JlTiMzd-HyQ)'s suggestion (of [DarkLang](https://darklang.com) fame), I've updated the Blazor WASM test to include AOT. I've also explicitly show the difference between Blazor in (non-AOT) debug vs release mode builds. 166 | 167 | You can see Release builds help a fair bit, and AOT a little bit more, but Fable is still king by a mile 😎 168 | 169 | ![perf-aot](Images/results-aot.png) 170 | 171 | # Further Notes 172 | 173 | - 2022-12-30: Docker has added support for [running WASM containers](https://docs.docker.com/desktop/wasm/) 174 | - 2023-01-04: [F# Wasm on Supabase](https://hashset.dev/article/19_f_in_strange_places_supabase_edge_functions) including info on `browser-wasm` 175 | 176 | ![docker-wasm](https://www.docker.com/wp-content/uploads/2022/10/docker-containerd-wasm-diagram.png) 177 | 178 | # December 2023 - dotnet 8 update 179 | 180 | I've re-run the tests using dotnet 8.0.100, same hardware as last time, latest copies of Safari, Google and Firefox (for my collegue Miles). 181 | Quick summary: everything got faster, WASM got a lot faster, and Safari now has the Javascript crown: 182 | 183 | - Wasm on Chrome is x3 faster than a year ago 184 | - Safari got faster at native javascript than a year ago, 3.5 times faster. 185 | - WASI is now supported by net8, I've added it to the test, running on the wasmtime runtime. See the [microsoft blog about wasm](https://devblogs.microsoft.com/dotnet/extending-web-assembly-to-the-cloud/) 186 | - I finally ran native F# in release mode, and it smoked everything (AOT didn't make any difference). I don't have dotnet7 of my mac anymore, so don't have the figures for native .net7 in release mode. 187 | 188 | ![perf-8](Images/results-8.png) 189 | 190 | # Appendix 191 | 192 | I've run out of time but other things I'd like to investigate further when I get a chance: 193 | 194 | - Running F# in WASM on Cloudflare 195 | 196 | Misc references: 197 | 198 | - [Experimental WebAssembly Multithreading in .NET 7](https://visualstudiomagazine.com/articles/2022/10/11/blazor-webassembly-net7.aspx) 199 | - A few hacker new discussions around WASM performance, WASI, etc: 200 | - [https://news.ycombinator.com/item?id=33792322](https://news.ycombinator.com/item?id=33792322) 201 | - [https://news.ycombinator.com/item?id=33816186](https://news.ycombinator.com/item?id=33816186) 202 | 203 | Code: 204 | 205 | - The Primes problem code is in the [Shared problem / prime.fs file](SharedProblem/Prime.fs) 206 | - All the various projects run on .net8 207 | --------------------------------------------------------------------------------