34 views
 owned this note
# Reflex: Are we everywhere yet? ## Context Because of resource constraints, support for modern GHC versions in the Reflex ecosystem has been lacking. More generally, with work on GHCJS ending to instead focus on the mainline JS backend, using Haskell for frontend development has been mostly stuck in time, frozen at years-old nixpkgs and GHC(JS) 8.6/8.10. Recently, exciting advances in new backends targeting the web have kindled new maintenance efforts to advance the Reflex ecosystem. This document tries to show the roadmap to using the new backends. ## Support Matrix Generally support can be considered in a matrix consisting of the dimensions: 1. ghc versions 2. reflex-frp libraries (primarily reflex and reflex-dom, but there are many others in the ecosystem) 3. build methods (cabal, reflex-platform, obelisk, vanilla nixpkgs, vanilla haskell.nix) 4. platforms (native, js, wasm) We won't consider here versions older than GHC 9.12 since it - is the latest supported version by the reflex/obelisk libraries - is the first release where code size of JS output beats GHCJS 8.10 (though not 8.6) - has TH support for WASM This means we can collapse the version axis. Moreover, having fixed 9.12, no relevant library is known to work with a build method but not another as long as the build method supports the chosen platform so we will treat version/method as unrelated and compare both against the platform axis. | library | JS<sup>1</sup> | WASM<sup>2</sup> | Linux | MacOS | Windows | Android | iOS | | - | - | - | - | - | - | - | - | | [reflex](https://hackage.haskell.org/package/reflex) | Y | Y | Y | Y | ? | Y | Y | | [reflex-dom](https://hackage.haskell.org/package/reflex-dom) | Y | Y | Y | Y | N | Y | Y | 1. Requires override for splitmix [#75](https://github.com/haskellari/splitmix/pull/75) 2. Requires override for splitmix [#73](https://github.com/haskellari/splitmix/pull/73) | build method | JS | WASM | Linux | MacOS | Windows | Android | iOS | | - | - | - | - | - | - | - | - | | nixpkgs | Y<sup>1</sup> | [N](https://github.com/NixOS/nixpkgs/pull/225000) | Y | Y | [WIP](https://github.com/NixOS/nixpkgs/pull/357744) | [WIP](https://github.com/NixOS/nixpkgs/pull/355543) | N | reflex-platform<sup>2</sup> | [WIP](https://github.com/reflex-frp/reflex-platform/pull/846) | N | Y | Y | N | ? | N | obelisk | [WIP](https://github.com/obsidiansystems/obelisk/pull/1126) | N | Y | Y | N | ? | N | haskell.nix | ? | ? | ? | ? | ? | ? | ? | 1. Requires an [workaround](https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14362) 2. Reflex-platform contains tweaks which are (or at least were sometime in the past) critical for reflex performance, even if other build methods already work, performance might be terrible. (See below) ### What’s missing? * Updating reflex-platform is potentially more complicated because it includes a lot of moving components including a performance critical patch (FastWeak references) to GHC which needs to be ported forward. (See below) ## Are we JS backend, yet? ### What’s missing? * The new JS backend seems to be **relatively ready for production** and large parts of the jsaddle ecosystem are already compatible. * **TemplateHaskell** support exists (from which version?) * Regarding Codesize, we got the following information from Luite: * Between 8.6 and 8.10 the local optimizer (in the Gen2.Optimizer module) was disabled because of bugs. GHC 9.10 has a new implementation that's much simpler, faster and almost as effective. And as far as we know doesn't have the terrible bugs of the old version. This optimizer does local optimization in generated functions, simplifying expressions and moving things around using a bit of knowledge about the Haskell RTS (that's why it can do better than uglifyjs or closure compiler). * External minifier support used to exist, but was broken for a long time, [thanks to Serge for doing the work here to fix it!](https://blog.haskell.org/report-of-js-code-minification/) * Libraries have been growing over time, thanks again to Serge for addressing some dependency issues improving code size! * GHCJS has a link-time optimization pass called compactor. This pass does global renaming of functions, optional deduplication of code and collects all static data in a more compact format. Of these, the **compactor is the only major component still missing from GHC**. * Primary hold-up is thus getting reflex-platform or something equivalent up to 9.10 or 9.12. * Besides that the impact of the missing compactor step on production builds will probably depend on the project. ## Are we WASM backend, yet? ### What’s missing? * This is mostly uncharted territory. * [An example reflex-dom app has successfully been built and run with the WASM backend.](https://github.com/tweag/ghc-wasm-miso-examples/pull/18) * (Using a [jsaddle implementation](https://github.com/amesgen/jsaddle-wasm) which serializes commands between wasm and js, like it does between native and js in warp mode.) * Performance characteristics of js-ffi are different on WASM thus while technically already running, larger changes to reflex-dom might be necessary for optimal performance. * Compilation to WASM support has not reached nixpkgs yet because of too many vendored dependencies. * [TemplateHaskell support](https://www.tweag.io/blog/2024-11-21-ghc-wasm-th-ghci/) exists from 9.14 and has been backported to 9.10/9.12 ## Sidenote: Future of reflex-platform * obsidian is working on migrating reflex-platform to haskell.nix instead of nixpkgs * contributors have voiced desire to also achieve nixpkgs support * once all libraries support the 9.X versions migrating the build-methods can be tackled * reflex-platform & obelisk contain various modifications which ideally can be upstreamed or made composable, ideally making them obsolete, but there is still quite some work to be done. * Alternatively and more short term reflex-platform will jump to the GHC 9.X versions. * As mentioned above, some fixes and configurations in reflex-platform are essential for performant usage of reflex-dom. For serious production use we will need to port forward those tweaks. They include: * Enable `-fexpose-all-unfoldings` (and other GHC flags?). This is not complicated but is has to be applied to the whole dependency tree and it must not be forgotten. * Upstream the FastWeak patch for better suited weak reference to GHC: [GHC Tracking Issue](https://gitlab.haskell.org/ghc/ghc/-/issues/25373) This patch is essential to prevent memory leaks in practice. It is currently only applied to ghcjs in reflex-platform, but (if the underlying issue is still reproducible) should be made available for all targets, i.e. js, wasm and native. * In an unreleased reflex-platform version there exists an alternative TH mode which dumps TH splices during the native build and reuses them for the JS target. While both new backends have their own TH support, the alternative approach might be more performant in situations where it applies, but requires build system support. This mode is expected to still be used for mobile builds for the time being. * reflex-platform replaces text for ghcjs with text-jsstring, which used to bring significant performance improvements. However, text 2.0 has since been released so it is unknown whether this remains the case.