Announcing Elmish Land 2.0 Preview
Version 2.0 brings Elmish Land onto Fable 5 (announcement) and the matching ecosystem majors — Feliz 3, Fable.Core 5, Fable.Elmish 5 etc. New projects created with init get these versions out of the box, and a new upgrade command moves existing projects across in one shot.
This is a major version bump because the underlying libraries contain breaking changes. Most Elmish Land applications will compile after running upgrade, but some will need code edits.
Before you run upgrade on a real project, read the Migrating from 1.x to 2.0 guide — it covers the .NET 10 prerequisites you have to set up manually, exactly which files upgrade rewrites, and the Feliz 3 code patterns you need to fix by hand.
Fable 5 by default
The Fable dotnet tool installed by dotnet elmish-land init is now version 5. This is a major step forward for the F# → JavaScript compiler that powers every Elmish Land app. Existing 1.x projects were on Fable 4; 2.0 makes Fable 5 the default for new projects and gives existing projects a one-command path to move over.
Updated package dependecies
Fable 5 lands together with several aligned ecosystem upgrades:
| Package | Major |
|---|---|
fable (dotnet tool) | 5 |
FSharp.Core | 10 |
Fable.Core | 5 |
Fable.Promise | 3 |
Fable.Elmish | 5 |
Fable.Elmish.HMR | 9 |
Fable.Elmish.React | 5 |
Feliz | 3 |
Feliz.Router | Removed; vendored at .elmish-land/Base/Router.fs |
react / react-dom | 19 |
vite | 8 |
init and upgrade resolves each major to the latest published patch at the moment you run it (see Dynamic version resolution below). The major bumps were upgraded together because they're all part of the same release wave. See the migration guide for the complete list of files upgrade touches.
URL change handling moved to a subscription
As part of the upgrade, the scaffold's URL-change handling has been reworked. Where 1.1 wrapped the entire view tree in a React.router element, 2.0 uses an Elmish subscription:
let private urlChangeSubscription _model =
let routeMode = Feliz.Router.RouteMode.Hash // or .Path, based on elmish-land.json
[ [ "ElmishLand"; "UrlChanges" ],
fun dispatch ->
Feliz.Router.Router.subscribeToUrlChanges
routeMode
(Route.parse >> RouteChanged >> dispatch) ]
let subscribe model =
Sub.batch [
Sub.map "Shared" SharedMsg (Shared.subscriptions model.Shared)
urlChangeSubscription model
// ...
]
Two practical consequences:
- Your view is no longer nested inside an extra router element, which keeps the rendered React tree closer to what you wrote.
- A small vendored copy of Feliz Router lives at
.elmish-land/Base/Router.fs. It's regenerated byinit,upgrade, andrestore, so you should not edit it by hand. TheFeliz.RouterNuGet dependency has been removed from the scaffold, andupgradestrips any leftoverFeliz.RouterPackageVersionentry fromDirectory.Packages.propsand anyPackageReferenceentry from your.fsprojfiles.
If you wrote any code that assumed the view was rendered inside a React.router, you'll want to revisit it after upgrading.
Breaking changes you may hit
Most code edits land in Feliz 3 territory: a handful of APIs that used to do extra work behind the scenes now require an explicit second call. The patterns to watch for are React.memo (now paired with React.memoRender), React.lazy' (paired with React.lazyRender), the redesigned React.context API, hand-written bindings using Interop.reactApi.createElement (now ReactLegacy.createElement), several helpers that moved into the FsReact namespace, and built-in component values that are now PascalCase (React.Fragment, React.Suspense, …).
Fable 5, vite 8, and Fable.Elmish.HMR 9 also ship their own breaking changes — most notably the FABLE_COMPILER_4 → FABLE_COMPILER_5 rename, vite's switch from Rollup to Rolldown, and raised default browser targets.
The migration guide walks through each Feliz 3 pattern with before/after examples and links to the upstream changelogs for the rest.
Dynamic version resolution during init and upgrade
Previously, every scaffolded Directory.Packages.props and package.json had hardcoded versions baked into the CLI. That meant a new patch release of Feliz or vite required a new Elmish Land release before init would pick it up.
In 2.0, init resolves versions dynamically. The CLI keeps a list of pinned majors (Feliz 3, vite 8, Fable 5, …) and asks the NuGet and npm registries for the latest version matching each major at the moment you run init. New projects always start on the freshest available patch and minor releases.
Upgrading an existing project: the new upgrade command
The migration story for existing projects is a single command:
dotnet tool update elmish-land --prerelease
dotnet elmish-land upgrade
upgrade rewrites Directory.Packages.props, package.json, .config/dotnet-tools.json, and the regenerated files under .elmish-land/ so the project lands on the 2.0 dependency set. It does not edit global.json or your project files' <TargetFramework> — those .NET 10 prerequisites you set up by hand first. The migration guide lays out the full sequence, including back-up advice and what upgrade leaves alone.
Quality of life fixes
- The interactive route-mode prompt during
initno longer fights with the spinner — the prompt is shown first, then the spinner takes over for the actual scaffolding. - The generated routing code no longer triggers F# warnings about unused query-parameter bindings on routes that don't declare any query parameters.
- The generated
sharedCommandToCmdfunction no longer triggers a warning about an unusedmsgbinding.
Getting 2.0
If you already have Elmish Land installed:
dotnet tool update elmish-land --prerelease
dotnet elmish-land upgrade
Or for a fresh start:
dotnet tool install elmish-land --prerelease
dotnet elmish-land init
Elmish Land is a framework for building F# browser apps with Fable, React, and Elmish. GitHub repository