There’s definitely a big hole in the “inner loop” for build->run->see changes for all kinds of .NET apps. There’s really two fundamental reasons for this:
- The .NET build system does a lot of work up front before the F# compiler even gets called. Many, many assemblies have to get resolved (you could have different versions of different things on your machine, has to pick which to reference). In my reply I pasted in a list of every assembly for a brand-new webapi project that the .NET build system has to go find and reference and pass to the F# compiler. All of this happens before the F# compiler is ever invoked.
There’s just no getting around this cost right now. In the node world, things are faster because it just runs against stuff on-demand and things fail when they fail. It can lead to really hard-to-diagnose issues down the line when things get larger, so it’s all just a tradeoff. You can grow a .NET 5+ app to be enormous and the build system will always pick up the correct dependency graph, but the cost is that when you’re building it for the first time, it’s not that fast.
On my machine,
dotnet msbuild /clp:performancesummary shows ~4.5s total to build on my macbook and 1.2s of that was the build system building up a command line to pass to the F# compiler. That ratio will change over time as your F# codebase grows, or if you happen across a particular coding pattern that can torture the F# compiler. But in general, you can expect this kind of ratio for a while until your F# codebase grows to be quite large.
- The F# compiler isn’t terribly fast either. I wouldn’t call it outright slow, and with .NET 5 the compiler got significantly faster, as demonstrated here but there’s plenty more work to do there. It has to do a lot during the emit phase of compilation and writing artifacts to disk. This just isn’t applicable in the JS world since the node runtime just runs JS files.
Point 2 is interesting, since if you just execute source code in F# Interactive, it’s actually more comparable to node since this is the F# Interactive process simply accepting F# source code and evaluating it without having to produce a .NET assembly. It’s much faster than
dotnet build for evaluating simple changes to source code.
The biggest downside to F# Interactive right now is that it can’t run an ASP.NET Core web project right now. There’s an open issue to do this and the hope is that it can be done by .NET 6.
The other piece of this is addressing point (1). The .NET team has recognized that “inner loop” performance is a problem not just in your scenario, but also for web UI development (Blazor vs. react/redux) and mobile dev (xamarin vs. Dart). In the other cases, there are positive things that .NET brings to the table that the others don’t, but the cost of (1) is severe enough that it does push some people away. I think we can all look forward to some progress on this front by the end of the year, since it is a focus for the .NET team to make progress on this year.