Contributing
All contributions are welcome: using the library, reporting issues, joining discussions, writing docs, and adding features.
Docs
I am too close to this project to see what needs more explaining. Help writing pages, or telling me where you got stuck, is much appreciated. The pages live in docs/site/content/ as markdown. An fsharp solid fence is compiled when the site builds and runs as a live example: add jsx to show the JSX it compiles to, and render=Name to mount a component the fence declares. dotnet fsi tools/build.fsx -- docs builds the site and fails on a fence that does not compile; --watch serves it.
Bindings
Submissions to the bindings are welcome. Most of them will be relevant to, and useful for, Oxpecker.Solid too. You can port them there yourself, or I will do it and credit you.
When you add or change a binding, check it against the Solid 2 API. docs/API-COVERAGE-solid2.md maps what upstream exports to the bindings in this repository. The solid/ folder is a git submodule of upstream Solid, kept for reference only: nothing in the repository builds from it, and you should never edit it.
The JSX surface is generated by tools/JsxGen from @solidjs/web's type declarations: HtmlAttributes.fs, AriaAttributes.fs, Svg.fs, and everything after the // <generated> line of Tags.fs. Never edit those by hand. Change tools/JsxGen/overrides.json or the generator, then run npm ci in tools/JsxGen and dotnet run -c Release --project tools/JsxGen. tools/JsxGen/out/diff.md reports what changed. The exported bindings, such as createSignal and For, are written by hand.
The plugin
The plugin is where the real work happens. Read the developer notes before you change it.
Building
You need the .NET SDK, and Node 22.12 or later on your PATH for the runtime tests. The solution is Partas.Solid.slnx. Build and test through the build script, tools/build.fsx, from the repository root:
dotnet fsi tools/build.fsx -- build # build Partas.Solid and the plugin (Release)
dotnet fsi tools/build.fsx -- test # restore, fable clean, build, snapshot tests, then the runtime tests
dotnet fsi tools/build.fsx -- --help # every command
Add -q (--quick) while you iterate. It skips the restores and fable clean. --skip-tests skips the tests.
Tools are pinned in .config/dotnet-tools.json. Run dotnet tool restore first if you call dotnet fable yourself.
Testing
There are two kinds of test. The snapshot tests pin what the plugin writes. The runtime tests check what that output does in a browser-like environment.
Snapshot tests
Because the plugin rewrites the AST, the JSX it writes is the specification. The snapshot tests live in tests/Partas.Solid.Tests.Plugin/Compiled/, a separate Fable project that references the library and the plugin:
Compiled/<Category>Cases/<Readable case name>/<CaseName>.fs <- F# input
<CaseName>.fs.jsx <- generated by Fable
<CaseName>.expected <- the committed snapshot
The categories are IssueCases (one folder per GitHub issue, named with the issue number), SolidCases (features of the DSL) and AttributeCases.
To add a test, add a folder. Write Foo.fs, compile, read the Foo.fs.jsx that Fable writes, and once it is right, copy it to Foo.expected. There is no registration code to edit.
The tests use Expecto. Run one case, or one category, with a filter:
dotnet run --project tests/Partas.Solid.Tests.Plugin/Partas.Solid.Tests.Plugin.fsproj -- --filter "Plugin/SolidCases.MergeProps"
dotnet run --project tests/Partas.Solid.Tests.Plugin/Partas.Solid.Tests.Plugin.fsproj -- --filter-test-list SolidCases
The test run compiles the cases with Fable first. If that step fails, every test fails.
Runtime tests
tests/Partas.Solid.Tests.Runtime/ compiles F# fixtures with Fable and the plugin, then compiles the result again with the real Solid 2 JSX compiler. vitest runs it in jsdom. There are three suites: Primitives (reactivity and stores, no DOM), Dom (elements, attributes, events, refs, SVG) and Integration (components, control flow, context, async and small apps).
node run.mjs dom # compile the Dom suite, then run its specs
node run.mjs integration Apps -t "sorts by name" # extra arguments go to vitest
node run.mjs primitives --no-compile # rerun specs without recompiling
node run.mjs integration --watch # recompile and rerun as you edit
node run.mjs all # what build.fsx test runs
Run these from tests/Partas.Solid.Tests.Runtime/. To add a test, add a folder with an F# fixture and a spec that imports the compiled .fs.jsx. One fixture that fails to compile fails the whole suite, so run the suite before you finish.
A test for a known bug stays in the suite as it.fails, with the correct assertion and a // BUG: comment beside it. Never weaken the assertion. When the bug is fixed, vitest reports the it.fails as failing, and you change it to it. A bug in Solid itself, reproduced with hand-written JSX, is marked // UPSTREAM: instead; recheck those when the Solid version is bumped.
Trying things out
tests/ScratchTests/ is a playground for checking the JSX of code that is not a test case yet. From inside it:
dotnet fable --exclude Partas.Solid.FablePlugin --noCache -e .fs.jsx --optimize --typedArrays false --watch
Workbench
For plugin and binding work, the workbench is much faster: a SageFs session that rechecks every snapshot case in under a second.
Conventions
- Fantomas is configured through
.editorconfig, but no build step runs it, and much of the code does not passfantomas --check. Format only the files you touch, and keep their layout. Never formatsrc/Partas.Solid.FablePlugin/Plugin.fs. - Incomplete pattern matches (warnings 3239 and 0025) are errors in both shipped projects.
- Partas.Solid and Partas.Solid.FablePlugin always share a version. EasyBuild.ShipIt calculates it, and writes
src/CHANGELOG.md, from the commits that touchsrc/, so write commits in conventional-commit style (fix(plugin): ...,feat: ...,feat!: ...for a breaking change). Never bump the<Version>in either.fsprojby hand.
When something does not compile the way you expect, see Submitting issues.
