FSharpPacker works on scripts (with extension .fsx) written with F# and usually run with `dotnet fsi`. For the F# advent calendar [1] I recently blogged about how those scripts are a viable starting point for developing an application in F#. Actually, it's even possible to easily maintain a scripted version and a compiled version of the same F# app, with basically the same code. For those interested, it's at https://www.asfaload.com/blog/fsharp-fsx-starting-point/
A few of notes regarding AOT compilation if you would like to have the best experience:
1. print* functions with "%A" format do not work because "structured output" uses reflection patterns that the linker cannot analyze. You can easily replace them with string interpolation that F# now has. I carry around these bindings:
open System
let println s = Console.WriteLine s
let print s = Console.Write(x: string)
and use them as
println $"Hello, {thing}"
2. AOT compilation is not a scripting environment so accessing fsi args does not work, here's the snippet that I use for it:
open System
// Detect fsi, if not - slice appropriately
// You do not need this if you know that you will be
// only compiling the script or only running it with fsi
// just use the offset of 2 or 1 respectively :)
let args =
let args = Environment.GetCommandLineArgs()
if args[0].Contains "fsi.dll" then args[2..] else args[1..]
// Use args in some way
args |> Array.iter println
3. For quickly publishing script files you may want to define a (fish) shell function like this:
function fspk
set name (string replace '.fsx' '' $argv[1])
fspack $argv[1] -f net9.0 --aot -o . \
/p:InvariantGlobalization=true \
/p:OptimizationPreference=Size &&
rm -rf ./$name.dbg ./$name.dSYM
end
This will reduce the binary size and will give you just a single executable for every `fspk my_script.fsx`. The binaries do start at ~4MB but beyond that - they scale efficiently with dependencies.
4. Except what is explicitly noted in repo, other scripting features work like referencing nuget packages or importing files. I did not expect to like this as much as I did, it's incredibly productive and you can just throw a few files together while having fully statically typed scripting environment and built-in package manager.
You can also get Ionide for VS Code for language server, autocomplete and debugging support.
It might be! I'd expect that at some point this would be a natural progression.
However, very often you don't need that - you can just compile individual script files which merge together code, package and file references. No need to maintain a separate folder with a separate .fsproj - simply #open some_utils.fsx, #r "nuget: FsHttp" or #r "SomePackage.dll" and 'fspk my_script.fsx'.
Initially, I did not know this existed, but it turned out a friend of mine wrote this tool and I have been using it ever since.
Nice work shipping this! I enjoy and prefer f# to c# but never gave scripts much care because I could never do this, fsi and the repl was not enough. Now? Hmmm I have options !
FSharpPacker works on scripts (with extension .fsx) written with F# and usually run with `dotnet fsi`. For the F# advent calendar [1] I recently blogged about how those scripts are a viable starting point for developing an application in F#. Actually, it's even possible to easily maintain a scripted version and a compiled version of the same F# app, with basically the same code. For those interested, it's at https://www.asfaload.com/blog/fsharp-fsx-starting-point/
1: https://sergeytihon.com/2024/10/26/f-advent-calendar-in-engl...
Any reason to prefer this or fflat (bflat)?
Fflat does not work on macOS and does not support .NET 9. Unfortunately, I had issues with making it work on Windows and Linux too.
This one works like a charm.
A few of notes regarding AOT compilation if you would like to have the best experience:
1. print* functions with "%A" format do not work because "structured output" uses reflection patterns that the linker cannot analyze. You can easily replace them with string interpolation that F# now has. I carry around these bindings:
and use them as 2. AOT compilation is not a scripting environment so accessing fsi args does not work, here's the snippet that I use for it: 3. For quickly publishing script files you may want to define a (fish) shell function like this: This will reduce the binary size and will give you just a single executable for every `fspk my_script.fsx`. The binaries do start at ~4MB but beyond that - they scale efficiently with dependencies.4. Except what is explicitly noted in repo, other scripting features work like referencing nuget packages or importing files. I did not expect to like this as much as I did, it's incredibly productive and you can just throw a few files together while having fully statically typed scripting environment and built-in package manager.
You can also get Ionide for VS Code for language server, autocomplete and debugging support.
Given these limitations, isn’t it easier to create a small dotnet project?
It might be! I'd expect that at some point this would be a natural progression.
However, very often you don't need that - you can just compile individual script files which merge together code, package and file references. No need to maintain a separate folder with a separate .fsproj - simply #open some_utils.fsx, #r "nuget: FsHttp" or #r "SomePackage.dll" and 'fspk my_script.fsx'.
Initially, I did not know this existed, but it turned out a friend of mine wrote this tool and I have been using it ever since.
Nice work shipping this! I enjoy and prefer f# to c# but never gave scripts much care because I could never do this, fsi and the repl was not enough. Now? Hmmm I have options !
I am a huge fan of keeping things as standalone executable, good work!