Skip to content

Contributing

All contributions are welcome: using the library, reporting issues, joining discussions, writing docs, and adding features.

Where to help

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, and fsharp solid fences on them compile and run as live examples.

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 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 CLI in partas-solid.fsproj:

dotnet run --project partas-solid.fsproj -- build     # build Partas.Solid and the plugin (Release)
dotnet run --project partas-solid.fsproj -- test      # clean, restore, snapshot tests, then the runtime tests

Add -q (--quick) while you iterate. It skips the tool restore, the clean and the solution restore. A full test run cleans every bin folder, runs fable clean and restores everything again. --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 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 Partas.Solid.Tests.Plugin/Partas.Solid.Tests.Plugin.fsproj -- --filter "Plugin/SolidCases.MergeProps"
dotnet run --project 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

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 the build CLI runs

Run these from 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.

Trying things out

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 --watch

Workbench

For plugin and binding work, the workbench is much faster. It is a SageFs session that keeps Fable's checker warm: it rechecks every snapshot case in under a second, reloads the plugin in about two, prints the AST the plugin receives, and writes the runtime suites' .fs.jsx files for node run.mjs <suite> --no-compile.

Conventions

  • Fantomas is configured through .editorconfig, but no build step runs it, and much of the code does not pass fantomas --check. Format only the files you touch, and keep their layout. Never format Partas.Solid.FablePlugin/Plugin.fs.
  • Incomplete pattern matches are errors in both shipped projects, not warnings.
  • Partas.Solid and Partas.Solid.FablePlugin always share a version. The release notes are generated from commit messages by git-cliff, so write commits in conventional-commit style (fix(plugin): ..., feat: ...).

When something does not compile the way you expect, see Submitting issues.

Edit this page