Skip to content

fix: make the published packages Fable-consumable - #78

Merged
dbrattli merged 2 commits into
mainfrom
fix/package-fable-sources
Aug 6, 2026
Merged

fix: make the published packages Fable-consumable#78
dbrattli merged 2 commits into
mainfrom
fix/package-fable-sources

Conversation

@dbrattli

@dbrattli dbrattli commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fable compiles a package from the F# sources under fable/, not from the DLL. A package without them fails silently — it type-checks under dotnet build and emits no code under dotnet fable. All three packages were broken, in two different ways.

  • Fable.Giraffe.Beam shipped no sources at all. It never had the Content item that js and python have.
  • js and python had one, but it didn't work. **\*.fs is project-relative, so none of the 14 shared files one level up were included — the packaged fsproj pointed at ../Core.fs and friends that weren't in the package. The same glob swept obj/ AssemblyInfo in.

Why this isn't a missing line

Three attempts, each ruled out empirically:

packed layout result
fsproj at fable/beam/, shared at fable/ Fable never finds the fsproj; compiles from the DLL, generates nothing
fsproj at fable/, shared at package root Fable inlines them, then: error FABLE: Cannot locate the OTP app directory ... expected output path to contain a 'fable_modules/<package>' segment
everything under fable/ ✅ works

The third requires every Compile path to resolve inside the packed tree, which means no fsproj can reference sources outside its own directory. Both sibling repos already satisfy this — Fable.Beam by having self-contained packages with no shared sources, Fable.Actor by being a single multi-target project. Giraffe was the only one violating it, and the only one whose package didn't work.

The change

The three backend projects move from src/<target>/ up to src/, where the shared files already are. Only the fsprojs move — the backend .fs files stay in src/beam, src/js, src/python and are now referenced as beam/WebHost.fs etc. Every Compile path becomes project-relative, and each project packs its own sources with an explicit per-backend glob rather than **\*.fs, so the three packages no longer swallow each other's files:

<Content Include="$(MSBuildProjectFile); *.fs; *.fsi" PackagePath="fable\" />
<Content Include="beam\*.fs; beam\*.fsi" PackagePath="fable\beam\" />

Generated BEAM module names change again as a result (fable_giraffe_beam_beam_middleware under the package). Nothing needs updating for it — the handler names itself via ?MODULE as of #76. That's now four distinct names the same source has produced; pinning it by hand was never going to hold.

Testing

Packed Fable.Giraffe.Beam to a local feed and consumed it from a throwaway project:

  • Fable parses 71 source files — identical to the ProjectReference path (it was 52, compiling against the DLL, before this).
  • rebar3 compile clean, and the result serves 200 OK on BEAM with WebHost - Giraffe listening on port 8080.
  • Giraffe lands as its own OTP app in fable_modules/ rather than being inlined into the consumer — also an improvement over the ProjectReference path, which duplicates it into every consuming app.

All three suites unchanged: BEAM 94 passed / 7 skipped, JS 100 / 1 skipped, Python 101. dotnet build clean on all three projects.

Notes

  • PackageTags are normalised in a follow-up commit. Beam and js never carried the ecosystem tags; python already had them. All three now read fsharp;fable;fable-library;fable-<target>;.... fable-library rather than fable-binding: Fable.Python and Fable.Beam bind platform APIs, while Fable.Logging and Fable.Reactive are F# libraries compiled to several targets — Giraffe is the latter. Purely nuget.org discovery metadata; no effect on the fix above.
  • Three fsprojs now share src/. Legal, and the Justfile addresses them by explicit path, but dotnet build src/ is no longer unambiguous.

🤖 Generated with Claude Code

dbrattli and others added 2 commits August 6, 2026 07:11
Fable compiles a package from the F# sources under fable/, not from the DLL, so
a package without them silently produces nothing: it type-checks under
`dotnet build` and emits no code under `dotnet fable`. All three packages were
broken, in two different ways.

Fable.Giraffe.Beam shipped no sources at all -- it never had the Content item
that js and python have. Those two had one, but its `**\*.fs` glob is
project-relative, so none of the 14 shared files one level up were included:
the packaged fsproj pointed at ../Core.fs and friends that were not in the
package. The same glob also swept obj/ AssemblyInfo into the package.

The fix is structural, not a missing line. Fable resolves the packaged fsproj at
fable/ and requires every source to live beneath it -- packing the shared files
at the package root instead fails with "expected output path to contain a
fable_modules/<package> segment". Since the fsproj's own Compile paths have to
resolve inside the packed tree, no fsproj can reference sources outside its own
directory. Fable.Beam and Fable.Actor both satisfy this already, one by having
self-contained packages and the other by being a single multi-target project.

So the three backend projects move from src/<target>/ up to src/, where the
shared files are. Only the fsprojs move; the backend .fs files stay in
src/beam, src/js and src/python and are now referenced as beam/WebHost.fs and
so on. Every Compile path is project-relative, and each project packs its own
sources with an explicit per-backend glob rather than `**\*.fs`, so the three
packages no longer swallow each other's files.

Generated BEAM module names change again as a result (fable_giraffe_beam_beam_
middleware under the package). Nothing needs updating for it: the handler names
itself via ?MODULE as of #76.

Verified by packing Fable.Giraffe.Beam to a local feed and consuming it from a
throwaway project: Fable parses 71 source files, the same as the ProjectReference
path, and the compiled result serves 200 OK on BEAM. Giraffe lands as its own OTP
app in fable_modules rather than being inlined into the consumer, which is also
an improvement over the ProjectReference path. All three suites unchanged: BEAM
94 passed / 7 skipped, JS 100 / 1, Python 101.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Beam and js never carried the ecosystem tags; python already had them. All three
now follow the shape the sibling repos use, fsharp;fable;fable-library;fable-<target>
followed by domain tags.

fable-library rather than fable-binding: Fable.Python and Fable.Beam are bindings
to platform APIs, while Fable.Logging and Fable.Reactive are F# libraries compiled
to several targets. Giraffe is the latter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dbrattli
dbrattli merged commit b7006e2 into main Aug 6, 2026
3 checks passed
@dbrattli
dbrattli deleted the fix/package-fable-sources branch August 6, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant