XMachines 5.0.0: atoms replace signals
XMachines 5.0.0 changes the reactive core. @xmachines/play-atom replaces @xmachines/play-signals, and every package reads atoms of @xstate/store 4. The same release gives the route pattern language a package of its own, and the actor derives its view instead of writing it. Releases 5.0.1 and 5.0.2 correct only the documentation and the demos.
Atoms replace signals
@xmachines/play-signals wrapped the polyfill of the TC39 Signals proposal. Every renderer also imported createAtom of @xstate/store for its view store. An application therefore shipped two reactive graphs. 5.0.0 keeps one graph, and the bundle is about 3 KB gzip smaller.
The two APIs have the same shape: both read with get(), and both write with set().
@xmachines/play-signals in 4.0 | @xmachines/play-atom in 5.0 |
|---|---|
new Signal.State(value) | createAtom(value) |
new Signal.Computed(fn) | createAtom(fn) |
Signal.State<T> | Atom<T> |
Signal.Computed<T> | ReadonlyAtom<T> |
the equals option | the compare option |
watchSignal(signal, onValue) | watchAtom(atom, onValue) |
useSignalEffect(cb, deps) of @xmachines/play-react | useAtomEffect(sources, cb, deps) |
A computed getter takes the previous value as its only parameter: write createAtom((previous) => …). Signal.subtle.untrack has no equivalent.
A codemod does most of the work. Three behaviors differ, and no codemod can decide them. Read the migration guide before you run it, and the atoms guide after it. The 4.0 signals guide describes the API that you leave.
curl -O https://gitlab.com/xmachin-es/xmachines-js/-/raw/main/scripts/codemod-play-atom.mjsnode codemod-play-atom.mjs --dry-run .node codemod-play-atom.mjs .
pnpm remove @xmachines/play-signalspnpm add @xmachines/play-atom @xstate/store@xmachines/play-atom needs @xstate/store ^4.2.3 as a peer, and it refuses ^3. Install one copy of it.
The actor derives its view
Viewable.currentView is now a ReadonlyAtom<PlaySpec | null>. The actor derives the view from its snapshot, and it no longer writes the view. An Atom satisfies the interface, so a test double that holds a writable atom keeps working.
A failed derivation no longer throws to the caller of actor.currentRoute.get(). The actor reports it through its onError option, and the atom keeps its last good value.
The route pattern language gets its own package
@xmachines/play-pattern is new. It holds one parse of the URLPattern pathname grammar, and the path that buildPath writes back from it. @xmachines/play-url keeps the URL layer: the URLPattern API, the base path, and the location splitter.
@xmachines/play-url no longer exports six helpers. Read the fields of parsePattern(pattern) instead:
| Removed in 5.0 | Replacement |
|---|---|
isParameterizedPattern | .parameterized |
normalizeParamNames, getNormalizedParamNameMap | .normalized |
getPatternParamNames | .declaredNames and .names |
getRequiredPatternParamNames | .requiredNames |
firstMarkIndex | .pathname and .suffix of splitLocation(path) |
A location that starts with # now keeps that character in its pathname, for every caller.
@xmachines/play-xstate/routing now reads @xmachines/play-pattern to parse a route pattern at run time. The core entry point and the /view entry point do not import it. An application that declares no route bundles none of it.
The route grammar in both directions
The actor now reads the whole route grammar when it builds a URL, and not only when it matches one. PathBuildResult carries a reason field:
- Read
result.reason === "missing"before you readresult.missing. - Handle the new
reason: "unresolvable". It names the param, and the value that no path can hold.
buildRouteUrl throws InvalidRouteParamError for such a value. In 4.0 it built a broken URL.
Bring your own cache
RouteMap, createRouteMap and createRouteMapFromTree no longer take cacheSize. The option sized one of three caches, and it did not tell which one. Pass a cache instead:
new RouteMap(mappings, { pathCache: createPatternCache(5000) });An optional peer for the machine graph
@statelyai/graph is now an optional peer of @xmachines/play-router, and no longer a dependency. Add @statelyai/graph@^2.4.0 when you import @xmachines/play-router/xstate. An application that imports only the root entry installs nothing.
Fixes
buildPathrefuses a value that a browser resolves away. In 4.0, the route/files/:namewith the value..built/files/.., and the browser showed/.RouteWatcherHandle.watchreleases the previous watch when it points at the atom of a new actor.
Upgrade
- Run the codemod, and read what it reports. Then replace
@xmachines/play-signalswith@xmachines/play-atomand@xstate/store4. - Replace the removed
@xmachines/play-urlhelpers with the fields ofparsePattern. - Handle
reason: "unresolvable"where you read aPathBuildResult. - Replace
cacheSizewith apathCache. - Add
@statelyai/graphif you import@xmachines/play-router/xstate.
The previous release is 4.0.0. The next release, 6.0.0, renames createPatternCache to createLruCache of @xmachines/core/cache.
The changelog of 5.0.0 lists every change of the release.