Obelisk v2: vanilla cabal builds, WASM frontend, Nix module system and simplified architecture#1139
Conversation
Extend existing arch(javascript) buildable guards to also cover arch(wasm32), and add missing guards to executables (ob, obelisk-asset-manifest-generate, obelisk-asset-th-generate, obelisk-selftest) that cannot be built for non-native targets
Replace per-package listings with glob patterns and merge cabal.dependencies.project inline. Add allow-newer constraints for GHC 9.14 and guard older workarounds behind impl(ghc < 9.14).
The change adds a _backendConfig_frontendGhcjsAssets field to BackendConfig , separates it from the general static assets , and uses it to construct a GhcjsApp via serveObeliskApp instead of serveDefaultObeliskApp.
- default.nix: top-level entry point delegating to nix/ - nix/default.nix: project function wrapping nix-haskell with obelisk module pre-imported - nix/module.nix: declare obelisk.static and obelisk.frontend.js as NixOS module options (replacing function arguments) - nix/lib.nix: rename ghcjsFrontend to frontendJs, add reflex-dom and reflex-dom-core as source-repository-packages - Add reflex-dom and nix-haskell submodules
- Pin nix-haskell to commit with named attrset source-repository-packages - Add nix-haskell-patches/js/splitmix import to module.nix
Extracts shared build logic (backend asset linking, GHCJS frontend cross-compilation, static manifest generation) into a library so downstream projects only need a one-line Setup.hs.
…k-setup - Use optional-packages in cabal.project instead of source-repository-packages to avoid cabal-install 3.16 componentAvailableTargetStatus bug. - Add ScopedTypeVariables to Utils.hs for exception handler type annotation.
obelisk-generated-static was previously produced as a hackage overlay: a nix derivation ran obelisk-asset-manifest-generate to create a full cabal package (including .cabal file), then sed-patched the generated Haskell source to fix GHC 9.14's pprint emitting GHC.Internal.Types instead of GHC.Types. This was fragile and duplicated logic. Now obelisk-generated-static is a real package in the project tree (static/generated/) with its own .cabal file, and nix generates only the Haskell module via a preBuild override using --module-only. Changes: Obelisk.Asset.Promoted: - Add writeStaticModule: writes just the .hs file without generating a .cabal, for use when the package already exists (nix preBuild, Setup.hs) - Fix GHC 9.14 Symbol name: on base >= 4.21, construct an explicit GHC.Types.Symbol TH Name instead of using ''Symbol which resolves to GHC.Internal.Types.Symbol and produces uncompilable generated code - GHC.TypeLits import is now conditional (only needed on older GHCs) obelisk-asset-manifest-generate: - Add --module-only flag that calls writeStaticModule instead of writeStaticProject, avoiding .cabal file overwrites that caused version mismatches during nix builds nix/lib.nix: - Remove obelisk-generated-static-manifest derivation and hackage overlay - Add staticManifestOverride using --module-only in preBuild - Rename static-manifest -> obelisk-generated-static in buildTypeOverride nix/module.nix: - Remove hackage-overlays config - Add staticManifestOverride to overrides list obelisk-setup: - Rename Obelisk.Setup.Manifest -> Obelisk.Setup.Static - Deduplicate: use obelisk-asset-manifest (gatherHashedPaths, writeStaticModule) instead of reimplementing hashing with sha256sum - Make native-only modules (Backend, Frontend, Static) and their dependencies conditional on !(arch(javascript) || arch(wasm32)) - Update static/manifest paths to static/generated
Split static module generation into Simple/Custom package pair following the frontend/frontend-custom pattern: - obelisk-generated-static (Simple) builds on all platforms including JS - obelisk-generated-static-custom (Custom) runs Setup.hs to generate the Obelisk.Generated.Static module, buildable: False on JS/WASM The Simple package conditionally depends on the Custom package on native to force correct build order: custom's Setup.hs generates the module into the shared src/ directory (via symlink) before the Simple package compiles it. nix/lib.nix: - staticManifestOverride generates module for both packages via preBuild - Remove dangling src symlink before mkdir in preBuild - Override build-type to Simple for obelisk-generated-static-custom lib/setup/Frontend.hs: - Fix static asset symlink path: static/manifest -> static/generated
Move assets.nix from lib/asset/ to nix/ so it lives alongside the module system that consumes it. The asset pipeline (mkAssets) produces a directory structure with type/encodings entries for obelisk-asset-serve-snap. Refactor the obelisk.static option from a single path into three nested sub-options: - obelisk.static.path: raw static assets path or derivation - obelisk.static.compress: toggle zopfli/gzip compression (default true) - obelisk.static.compressed: final compressed assets derivation In module.nix, hash static files into cache-busting names via obelisk-asset-manifest-generate --module-only, then optionally compress with mkAssets. The uncompressed hashed output (hashedStatic) feeds the manifest generator and static symlinks, while the compressed output feeds obelisk-asset-serve-snap as static.assets. Add compressedStatic parameter to frontendDataOverride and backendDataOverride in lib.nix, which symlinks the compressed assets into the data directory as static.assets alongside the uncompressed static directory. Export obelisk-asset-manifest-generate from lib.nix for use in module.nix's hashedStatic derivation.
Add brotliEncodings which compresses assets with brotli at maximum quality (-q 11), producing ~20% better compression than gzip. Add unionEncodings which composes multiple encoding functions via symlinkJoin, allowing encodings to be mixed without duplicating the identity/gzip logic. Change defaultEncodings from platform-conditional zopfli/gzip to unionEncodings [ brotliEncodings gzipEncodings ], serving brotli (br) with gzip fallback for older clients. No changes needed in obelisk-asset-serve-snap — it already picks the best encoding from whatever files exist in the encodings directory.
Refactor obelisk.frontend.js from a single option into nested
sub-options:
- obelisk.frontend.js.package: the GHCJS-compiled frontend derivation
- obelisk.frontend.js.compressed: mkAssets-processed frontend.jsexe
for obelisk-asset-serve-snap, controlled by obelisk.static.compress
Add compressedFrontendJs parameter to backendDataOverride in lib.nix,
which symlinks the processed output as frontend.jsexe.assets in the
backend data directory. The backend already serves from
frontend.jsexe.assets (processed) with frontend.jsexe (unprocessed) as
fallback via serveAsset.
Add frontend.js.optimize and frontend.js.optimized options. When optimize is true (default), closure-compiler runs SIMPLE optimizations on every .js file in the GHCJS jsexe output. When false, optimized passes through the raw jsexe directory unchanged. Add frontend.js.compress flag (defaults to static.compress) so frontend JS compression can be toggled independently of static assets. The compressed option now feeds from optimized rather than raw package, forming the pipeline: package → optimized → compressed. When either flag is false the corresponding stage is a passthrough, so disabling both serves the raw GHCJS output directly.
Replace the per-file SIMPLE compilation with a whole-program ADVANCED
optimization that compiles all.js as a single unit, using GHCJS's
all.externs.js for extern declarations. The optimized output replaces
all.js while leaving the original files intact.
Uses --isolation_mode IIFE, --assume_function_wrapper, and
--emit_use_strict for safe ADVANCED mode with GHCJS output.
--jscomp_off=undefinedVars is needed because GHCJS generates cross-file
globals and unexpanded macros (MK_INTEGER_S, h$stg_paniczh) that
closure-compiler cannot resolve even with all files present.
Refactor frontend.js.optimize into frontend.js.optimization sub-options:
- optimization.enable: toggle (default true)
- optimization.level: BUNDLE/WHITESPACE_ONLY/SIMPLE/TRANSPILE_ONLY/ADVANCED
(default ADVANCED)
- optimization.externs: additional extern files for closure-compiler
- optimization.extraFlags: arbitrary extra closure-compiler flags
Add frontend.js.compress flag (defaults to static.compress) so frontend
JS compression can be toggled independently. The pipeline is now:
package -> optimized (closure-compiler) -> compressed (mkAssets brotli+gzip).
When either stage is disabled, its output passes through unchanged.
Introduce a complete WASM frontend pipeline that compiles the frontend
via wasm32-wasi and assembles the output into a frontend.jsexe directory
the backend can serve unchanged, reusing the existing GHCJS asset
infrastructure.
Nix pipeline (module.nix, lib.nix):
- Add `obelisk.frontend.target` option ("js" or "wasm", default "wasm")
to select which pipeline feeds the backend
- Add `obelisk.frontend.wasm` option group mirroring frontend.js:
package, optimization (wasm-opt via binaryen), optimized, compress,
compressed — with wasm-tools strip for binary size reduction
- Add `frontendWasm` helper in lib.nix for wasi32 cross-compiled frontend
- Vendor @bjorn3/browser_wasi_shim via fetchTarball for WASI browser support
- Remove hardcoded `/bin/frontend.jsexe` suffix from backendDataOverride
since frontendOutput.optimized is already the jsexe directory for both
JS and WASM targets
WASM browser bootstrap (nix/wasm/shim.js):
- Classic script (not ES module) using async IIFE with dynamic import()
so it works with the existing `type="text/javascript"` script tag
- Captures document.currentScript.src synchronously before any await to
resolve sibling assets (frontend.wasm, wasi-shim.js, ghc_wasm_jsffi.js)
relative to the script URL rather than the page URL
- Uses WebAssembly.instantiate(arrayBuffer) instead of
instantiateStreaming to avoid MIME type requirements
Haskell changes:
- Broaden CPP guards in Frontend.hs from `ghcjs_HOST_OS` to
`defined(ghcjs_HOST_OS) || defined(wasm32_HOST_ARCH)` for hydration
mode and route adjustment, since wasm32 runs in the browser like GHCJS
- Change getConfigs call to use JSM directly on ghcjs/wasm32 (no liftIO)
since on wasm32 JSM ≠ IO unlike GHCJS where JSM = IO
- Rename src-ghcjs to src-js and change getConfigs signature from
IO (Map Text ByteString) to JSM (Map Text ByteString) — this works on
both GHCJS (where JSM = IO) and wasm32 (where JSM provides the
jsaddle-wasm bridge to ghcjs-dom)
- Add arch(wasm32) to cabal conditional so wasm32 uses the DOM-based
config lookup (src-js + ghcjs-dom) instead of the directory-based one
Split Obelisk.Setup.Frontend into separate Js and Wasm modules: - Frontend.Js: renamed from Frontend, unchanged GHCJS cross-build hook - Frontend.Wasm: new Setup.hs hook that builds with wasm32-unknown-wasi-cabal, runs post-link.mjs for JSFFI extraction, assembles jsexe directory with browser WASI shim, and optionally runs wasm-opt/wasm-tools Rename frontend-custom to frontend-js/frontend-wasm in buildTypeOverride. Skip cross-compilation and asset generation in nix-shell by guarding obelisk.frontend.js.package, obelisk.frontend.wasm.package, and obelisk.static.compressed option defaults with lib.inNixShell. Export OBELISK_WASI_SHIM in shell.shellHook for cabal-based WASM builds. Bump nix-haskell submodule (shell.shellHook types.str → types.lines).
Add nix/docs.nix to generate documentation for obelisk.* module options,
reusing eval.nix and docs.nix extracted from nix-haskell.
Reorganize docs/:
- docs/module.{md,man}: generated obelisk options documentation
- docs/nix-haskell/: symlinks to nix-haskell module documentation
- Remove old top-level symlinks to nix-haskell docs
Bump nix-haskell submodule (eval.nix/docs.nix extraction, store path
cleanup in generated docs).
Add flake.nix exposing lib (nix/default.nix) and packages (docs). Export assets.nix and docs.nix from nix/lib.nix for downstream access. Add cache.nixos.org to flake substituters. Bump nix-haskell submodule (cache.nixos.org substituter).
Set optimizations.all = true (mkDefault) in obelisk module so projects get -O2, -fexpose-all-unfoldings, -fspecialise-aggressively, -flate-specialise, -fcross-module-specialise, and -fspecialise out of the box. Can be overridden per-project. Bump nix-haskell submodule (optimizations module).
Let individual projects decide their own optimization flags rather than setting them in the framework.
Extend the project return value with exe.wasm and exe.js, each building the backend executable with the corresponding obelisk.frontend.target override. Uses proj.override to reuse the base project evaluation.
Pulls nix-haskell ef79179 which pins hoogle to 5.0.19.0 and adds conditional allow-newer/constraints for GHC 9.14's bumped boot libraries (base-4.22, containers-0.8, etc.). This fixes the dev shell failing to build hoogle-with-packages when using ghc914, where the cabal solver previously fell back to the ancient hoogle 3.1.
1. ob-run dev script: Add ob-run development script for fast backend iteration ob-run watches backend/, common/, and frontend/ for .hs, .cabal, and .project file changes using inotifywait, then rebuilds and restarts the backend. Pressing Enter forces a manual restart. Extra arguments are passed through to cabal run. Disables all optimization flags (--ghc-options=-O0, -fno-specialise, -fno-expose-all-unfoldings, -fno-specialise-aggressively, -fno-late-specialise, -fno-cross-module-specialise) for faster dev rebuilds. Cross builds receive the same flags via OBELISK_CROSS_CABAL_ARGS. The script is provided as a writeShellApplication with inotify-tools, added to shell.nativeBuildInputs. Usage info is printed on nix-shell entry. 2. Setup.hs optimization forwarding: Forward host optimization level and env flags to cross builds Switch Frontend.Js and Frontend.Wasm Setup.hs hooks from preBuild/postBuild to buildHook, which provides access to LocalBuildInfo. This allows reading withOptimization to forward the host -O level to cross cabal builds. Additionally, read OBELISK_CROSS_CABAL_ARGS env var for extra cabal flags (used by ob-run to disable optimizations in cross builds). Env var flags are appended after optLevelFlags so they take precedence (last-flag-wins). Deduplicate optLevelFlags, crossCabalArgs, and strip into Utils. Remove the global MVar + unsafePerformIO pattern in favor of a local MVar created inside buildHook.
ob-hoogle starts a Hoogle server in the background with start/stop/restart commands. Defaults to port 8080. Detects already-running instances via kill -0 on the PID file to avoid duplicates. The shellHook exports HOOGLE_PIDFILE and sets an EXIT trap to automatically stop the server when exiting nix-shell, even if started manually mid-session.
Implement deployment support based on the original obelisk serverModules,
adapted to the new nix-haskell module system as a proper NixOS module.
nix/server.nix:
NixOS module (services.obelisk) consolidating the old mkBaseEc2,
mkDefaultNetworking, and mkObeliskApp into a single module with options:
nginx reverse proxy with WebSocket support, ACME/HTTPS via Let's Encrypt,
systemd service (symlinks exe contents into working dir, auto-restarts),
firewall rules, system user/group, and domain redirect virtual hosts.
nix/lib.nix:
mkServerExe assembles a flat deployment directory from project outputs
(backend binary, compressed static assets, compressed frontend assets)
by overriding obelisk.frontend.target and reading the corresponding
config.obelisk.static.compressed / config.obelisk.frontend.{target}.compressed.
Also exposes serverModule path.
nix/default.nix:
Captures full nix-haskell eval to access proj.config and nixpkgs.
Exposes serverExe.wasm / serverExe.js on the project, and a server
convenience function that builds a full NixOS system (defaults exe
to serverExe.wasm).
nix/lib.nix: mkContainerImage builds a layered OCI image via dockerTools.buildLayeredImage from a serverExe. Packages the backend binary and compressed assets into /app, includes cacert (for outbound HTTPS) and gnutar (backend dependency). Image name defaults to proj.config.name, tag defaults to "latest". Exposes port 8000/tcp with WorkingDir /app. nix/default.nix: Exposes containerImage.wasm / containerImage.js on the project output, mirroring the serverExe.wasm / serverExe.js pattern. Usage: nix build .#legacyPackages.x86_64-linux.containerImage.wasm podman load < result && podman run -p 8000:8000 <name>:latest
skeleton/:
Minimal obelisk project template derived from a working project, stripped
to bare essentials. Provides the complete directory structure and wiring
needed for a new obelisk app:
- backend: Custom build-type with obelisk-setup, Paths module for data dir
lookup (cabal getDataDir / file-embed for REPL), Main.hs configuring
snap with static and frontend asset paths, minimal Backend.hs
- frontend: Library exposing Frontend module with placeholder head/body,
Frontend.Paths with CPP-guarded data dir for native/GHCJS/WASM/REPL,
executable with WASM foreign export, frontend-js and frontend-wasm
cross-compilation wrapper packages with obelisk-setup
- common: Minimal route types (one BackendRoute, one FrontendRoute),
route encoder, Common re-export module
- static: mkDerivation-based build (css/html/icons/images/js),
generate helper script, obelisk-generated-static and
obelisk-generated-static-custom packages
- Nix: default.nix, flake.nix, project.nix (name: obelisk-skeleton)
- cabal.project with optional-packages for cross frontends,
generated static, and obelisk/reflex-dom deps
nix/module.nix:
Set compiler-nix-name = mkDefault "ghc914" so projects get a sensible
default GHC version without having to specify it explicitly.
The cabal.project used a flat package list that included packages with build-type: Custom depending on obelisk-setup. When the WASM/GHCJS cross-compiler's cabal resolved the project, it could not find obelisk-setup (only available in the native ghc-pkg), causing cross-builds to fail. Move backend, frontend/js, frontend/wasm, and static/generated/custom behind an `if !(arch(javascript) || arch(wasm32))` conditional so the cross-compiler's cabal never attempts to resolve their setup-depends. Add obelisk-setup as a build-depends of obelisk-generated-static-custom so it appears in ghc-pkg for native cabal's setup-depends resolution. Guard frontendDataOverride and backendDataOverride with mkOptionalPackages so they are silently skipped in cross projects where those packages do not exist, fixing an infinite recursion during nix evaluation of projectCross. Fix ob-run's `cabal list-bin` returning the wrong path under -O0 by emitting cabal-level optimization flags (-O0) instead of --ghc-options=-O0, and passing only optimization flags (not the full extraFlags with --ghc-options) to list-bin/assembleAndLinkFrontend/ linkFrontendAssets. Fix ob-run EXIT trap to also kill inotifywait and read processes. Remove unused dependencies (directory from frontend, filepath from backend library).
- release.nix builds the skeleton's serverExe and containerImage for both wasm and js targets via linkFarm, so `nix-build release.nix` builds everything in one go. - Expose config and nixpkgs on project output so downstream consumers (like release.nix) can access the evaluated module config and the nixpkgs instance without re-importing. - Add release to flake.nix packages (nix build .#release).
Rewrite README.md, FAQ.md, and ChangeLog.md to reflect the new nix-haskell module system, vanilla cabal builds, WASM/GHCJS frontend targets, and NixOS/OCI deployment.
There was a problem hiding this comment.
Pull request overview
This PR introduces “Obelisk v2” changes aimed at enabling vanilla cabal workflows, adding a WASM frontend target, and replacing the legacy ob/monolithic Nix setup with a composable Nix module system plus updated deployment/dev tooling.
Changes:
- Replaces legacy
obCLI + old Nix plumbing with anix-haskellmodule system, new build/deploy outputs, and flake-based entrypoints. - Adds cross frontend targets (
frontend-js,frontend-wasm) andobelisk-setupSetup.hs hooks to generate static manifests and trigger cross builds from normalcabal build. - Overhauls the skeleton project layout, static asset pipeline (hashing + brotli/gzip + closure compiler), and adds NixOS/OCI deployment support.
Reviewed changes
Copilot reviewed 144 out of 175 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| skeleton/static/main.css | Removes old placeholder CSS asset from skeleton. |
| skeleton/static/lib.js | Removes old placeholder JS FFI asset from skeleton. |
| skeleton/static/generated/src/Obelisk/Generated/Static.hs | Adds generated static manifest module for hashed assets. |
| skeleton/static/generated/obelisk-generated-static.cabal | Adds generated static manifest Cabal package. |
| skeleton/static/generated/data/static | Adds generated static data reference for skeleton asset pipeline. |
| skeleton/static/generated/custom/src | Adds custom package src indirection for generated static. |
| skeleton/static/generated/custom/obelisk-generated-static-custom.cabal | Adds custom build-type variant for generated static package. |
| skeleton/static/generated/custom/Setup.hs | Uses obelisk-setup static hook for generation step. |
| skeleton/static/generated/Setup.hs | Uses obelisk-setup static hook for generation step. |
| skeleton/static/generate | Adds Nix-backed static asset build script used by Setup hook. |
| skeleton/static/default.nix | Adds skeleton static asset derivation (customizable pipeline). |
| skeleton/project.nix | Adds skeleton nix-haskell module config (static path + shell). |
| skeleton/frontend/wasm/frontend-wasm.cabal | Adds WASM wrapper/reexport package with Custom setup. |
| skeleton/frontend/wasm/data | Adds WASM wrapper data dir linkage. |
| skeleton/frontend/wasm/Setup.hs | Uses obelisk-setup WASM frontend hook. |
| skeleton/frontend/src/Frontend/Paths.hs | Adds platform-conditional dataDir resolution for frontend. |
| skeleton/frontend/src/Frontend.hs | Simplifies skeleton frontend and switches to generated static import. |
| skeleton/frontend/src-bin/main.hs | Removes old skeleton frontend executable entrypoint. |
| skeleton/frontend/js/frontend-js.cabal | Adds GHCJS wrapper/reexport package with Custom setup. |
| skeleton/frontend/js/data | Adds GHCJS wrapper data dir linkage. |
| skeleton/frontend/js/Setup.hs | Uses obelisk-setup JS frontend hook. |
| skeleton/frontend/frontend.cabal | Modernizes skeleton frontend cabal file + flags + app exe layout. |
| skeleton/frontend/app/Main.hs | Adds new frontend app entrypoint + WASM export glue. |
| skeleton/flake.nix | Adds skeleton flake output for legacy packages per system. |
| skeleton/deps/obelisk | Points skeleton to local obelisk dependency. |
| skeleton/default.nix | Replaces legacy .obelisk/impl wiring with new module-based project import. |
| skeleton/config/readme.md | Removes old skeleton config documentation file. |
| skeleton/config/common/route | Removes old config-based route host. |
| skeleton/config/common/example | Removes old shared config example. |
| skeleton/common/src/Common/Route.hs | Simplifies routes + introduces checked encoder helper. |
| skeleton/common/src/Common/Api.hs | Removes old Common.Api sample. |
| skeleton/common/src/Common.hs | Adds Common re-export module. |
| skeleton/common/common.cabal | Modernizes skeleton common cabal file and warnings baseline. |
| skeleton/cabal.project | Reworks skeleton cabal.project for optional/cross packages. |
| skeleton/backend/static | Removes old backend->static path link. |
| skeleton/backend/src/Paths.hs | Adds backend dataDir/temporaryDir resolver with REPL mode. |
| skeleton/backend/src/Backend.hs | Updates backend to import new Common module. |
| skeleton/backend/src-bin/main.hs | Removes old backend executable entrypoint. |
| skeleton/backend/frontendJs/frontend.jsexe | Removes old frontend asset link. |
| skeleton/backend/frontend.jsexe | Removes old frontend asset link. |
| skeleton/backend/backend.cabal | Modernizes backend cabal file, adds cross/wasm flags and Custom setup. |
| skeleton/backend/app/Main.hs | Adds backend app entrypoint using new BackendConfig asset paths. |
| skeleton/backend/Setup.hs | Uses obelisk-setup backend hook. |
| skeleton/.obelisk/impl | Removes legacy .obelisk/impl pointer. |
| skeleton/.obelisk/.gitignore | Removes legacy .obelisk ignore rules. |
| skeleton/.gitignore | Updates skeleton ignores for new dist dirs / asset outputs. |
| scripts/ob-run | Adds file-watching dev server wrapper around cabal run backend. |
| scripts/ob-repl | Adds convenience cabal repl wrapper tuned for dev iteration. |
| scripts/ob-hoogle | Adds hoogle server manager script. |
| release.nix | Replaces old all-builds/all-tests wiring with skeleton-based outputs. |
| nixpkgs-overlays/default.nix | Removes legacy nixpkgs overlay utilities. |
| nix/wasm/shim.js | Adds browser shim to bootstrap WASI WASM frontend runtime. |
| nix/server.nix | Adds NixOS module for deploying an Obelisk backend behind nginx/ACME. |
| nix/module.nix | Adds nix-haskell module defining obelisk options + asset pipelines. |
| nix/lib.nix | Adds Nix helper library: overrides, serverExe/container image builders. |
| nix/docs.nix | Adds doc generation entrypoint for module options. |
| nix/default.nix | Adds top-level Nix entrypoint exporting module + builders and outputs. |
| lib/snap-extras/obelisk-snap-extras.cabal | Expands non-buildable condition to wasm32. |
| lib/setupHls.sh | Removes legacy HLS helper script. |
| lib/setup/src/Obelisk/Setup/Utils.hs | Adds shared Setup.hs utilities (root lookup, symlink, flags). |
| lib/setup/src/Obelisk/Setup/Static.hs | Adds static asset build+manifest generator Setup hook. |
| lib/setup/src/Obelisk/Setup/Frontend/Wasm.hs | Adds WASM cross-build Setup hook + jsexe assembly. |
| lib/setup/src/Obelisk/Setup/Frontend/Js.hs | Adds GHCJS cross-build Setup hook + symlinking. |
| lib/setup/src/Obelisk/Setup/Backend.hs | Adds backend Setup hook symlinking frontend/static assets. |
| lib/setup/obelisk-setup.cabal | Adds new obelisk-setup package for reusable hooks. |
| lib/setup/README.md | Documents obelisk-setup hooks usage. |
| lib/selftest/src-bin/obelisk-selftest.hs | Removes legacy selftest executable. |
| lib/selftest/obelisk-selftest.cabal | Removes legacy selftest package. |
| lib/run/src/Obelisk/Run.hs | Removes legacy obelisk-run implementation. |
| lib/run/obelisk-run.cabal | Removes legacy obelisk-run package. |
| lib/route/test/Main.hs | Adjusts QuickCheck import to avoid Some name clash. |
| lib/hie.yaml | Removes legacy hie-bios configuration file. |
| lib/frontend/src/Obelisk/Frontend.hs | Extends hydrate/adjustRoute conditions to include wasm; adjusts config lookup. |
| lib/executable-config/lookup/src-ios/Obelisk/ExecutableConfig/Lookup.hs | Removes iOS config lookup implementation. |
| lib/executable-config/lookup/src-ghcjs/Obelisk/ExecutableConfig/Lookup.hs | Changes GHCJS config lookup to return JSM and minor cleanups. |
| lib/executable-config/lookup/src-android/Obelisk/ExecutableConfig/Lookup.hs | Removes Android config lookup implementation. |
| lib/executable-config/lookup/src-android/Obelisk/ExecutableConfig/Internal/AssetManager.hsc | Removes Android asset manager FFI module. |
| lib/executable-config/lookup/obelisk-executable-config-lookup.cabal | Simplifies platform conditionals; adds wasm32 pathing. |
| lib/executable-config/lookup/cbits/ExecutableConfig.c | Removes Android JNI config implementation. |
| lib/executable-config/default.nix | Removes legacy executable-config nix module. |
| lib/command/src/Obelisk/Command/VmBuilder.hs | Removes legacy ob CLI implementation. |
| lib/command/src/Obelisk/Command/Utils.hs | Removes legacy ob CLI implementation. |
| lib/command/src/Obelisk/Command/Preprocessor.hs | Removes legacy ob CLI implementation. |
| lib/command/src/Obelisk/Command/Nix.hs | Removes legacy ob CLI implementation. |
| lib/command/src/Obelisk/App.hs | Removes legacy ob CLI monad/app scaffolding. |
| lib/command/src-bin/ob.hs | Removes legacy ob executable entrypoint. |
| lib/command/obelisk-command.cabal | Removes legacy obelisk-command package. |
| lib/cabal.project.config | Consolidates constraints/allow-newer for GHC 9.14 and wasm. |
| lib/cabal.project.ci | Removes CI-specific project file. |
| lib/cabal.project | Simplifies package selection and imports consolidated config. |
| lib/cabal.dependencies.project | Removes legacy dependency project file. |
| lib/backend/src/Obelisk/Backend.hs | Extends BackendConfig to carry compiled frontend assets separately. |
| lib/backend/obelisk-backend.cabal | Expands non-buildable condition to wasm32. |
| lib/asset/serve-snap/obelisk-asset-serve-snap.cabal | Expands non-buildable condition to wasm32. |
| lib/asset/manifest/src/Obelisk/Asset/Promoted.hs | Adds writeStaticModule and adjusts Symbol handling for GHC 9.14. |
| lib/asset/manifest/src-bin/generate.hs | Adds --module-only support to generator CLI. |
| lib/asset/manifest/obelisk-asset-manifest.cabal | Expands non-buildable condition to wasm32 and executables. |
| lib/asset/assets.nix | Switches default encodings to brotli+gzip; removes noisy tracing. |
| lib/README.md | Removes HLS-specific dev workflow section. |
| hydra.json | Removes legacy Hydra jobset config. |
| haskell-overlays/tighten-ob-exes.nix | Removes legacy overlay used for ob/selftest packaging. |
| haskell-overlays/obelisk.nix | Removes legacy Haskell overlay packaging. |
| haskell-overlays/misc-deps.nix | Removes legacy overlay for dependency fixes. |
| guides/app-deploy/README.md | Removes outdated deployment guide tied to old ob CLI. |
| flake.nix | Adds top-level flake exporting nix lib and docs/release packages. |
| docs/nix-haskell/modules.md | Adds docs symlink/reference file for nix-haskell modules. |
| docs/nix-haskell/modules.man | Adds docs symlink/reference file for nix-haskell modules. |
| docs/module.md | Adds generated module-option documentation. |
| docs/module.man | Adds generated manpage for module-option documentation. |
| deps/reflex-dom | Adds reflex-dom submodule pointer. |
| deps/nix-haskell | Adds nix-haskell submodule pointer. |
| dep/reflex-platform/thunk.nix | Removes legacy thunked dependency. |
| dep/reflex-platform/github.json | Removes legacy thunked dependency metadata. |
| dep/reflex-platform/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/nix-thunk/thunk.nix | Removes legacy thunked dependency. |
| dep/nix-thunk/github.json | Removes legacy thunked dependency metadata. |
| dep/nix-thunk/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/hs-git/thunk.nix | Removes legacy thunked dependency. |
| dep/hs-git/github.json | Removes legacy thunked dependency metadata. |
| dep/hs-git/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/hnix/thunk.nix | Removes legacy thunked dependency. |
| dep/hnix/github.json | Removes legacy thunked dependency metadata. |
| dep/hnix/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/gitignore.nix/thunk.nix | Removes legacy thunked dependency. |
| dep/gitignore.nix/github.json | Removes legacy thunked dependency metadata. |
| dep/gitignore.nix/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/cli-nix/thunk.nix | Removes legacy thunked dependency. |
| dep/cli-nix/github.json | Removes legacy thunked dependency metadata. |
| dep/cli-nix/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/cli-git/thunk.nix | Removes legacy thunked dependency. |
| dep/cli-git/github.json | Removes legacy thunked dependency metadata. |
| dep/cli-git/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/cli-extras/thunk.nix | Removes legacy thunked dependency. |
| dep/cli-extras/github.json | Removes legacy thunked dependency metadata. |
| dep/cli-extras/default.nix | Removes legacy thunked dependency entrypoint. |
| dep/README.md | Removes legacy dep directory overview. |
| all-tests.nix | Removes legacy test aggregation. |
| all-builds.nix | Removes legacy build aggregation. |
| ChangeLog.md | Adds comprehensive v2 “Unreleased” changelog section. |
| CONTRIBUTING.md | Updates contribution/testing guidance for new skeleton + release outputs. |
| .hlint.yaml | Removes repo-level HLint config. |
| .gitmodules | Adds submodule definitions for nix-haskell and reflex-dom. |
| .github/workflows/haskell.yml | Removes CI step copying cabal.project.ci into place. |
Comments suppressed due to low confidence (6)
scripts/ob-run:1
set -euo pipefail+trap cleanup EXITmeanscleanupcan run beforePID/INOTIFY_PID/READ_PIDare set, causing an “unbound variable” error due to-u. Initialize these variables upfront (e.g., empty) and/or guard each kill with${VAR:-}checks (or[[ -n "${VAR:-}" ]]) before callingkill.
nix/lib.nix:1config.packages ? ${name}is not valid Nix syntax for a dynamic attribute check (the?operator expects an identifier). Usebuiltins.hasAttr name config.packages(orconfig.packages.${name} or nullstyle) inside the filter predicate to make this expression evaluate correctly.
lib/cabal.project.config:1- The trailing quote (
") at the end of theghc-optionsline will make thiscabal.project.configinvalid/unparseable. Remove the stray quote so cabal can parse the file.
nix/server.nix:1 systemddoes not reliably expand~inWorkingDirectory, so the service may fail to start or use an unexpected directory. Prefer an absolute path like/var/lib/${cfg.user}(matching the declared user home) or"%h"to use the service user’s home directory.
lib/setup/README.md:1- The README references a module
Obelisk.Setup.Frontend(but the code in this PR addsObelisk.Setup.Frontend.JsandObelisk.Setup.Frontend.Wasm) and mentions generatingObelisk.Generated.Static.Instances(while the generator writesObelisk.Generated.Static). Update the README module names/output module name to match the actual exported modules and generated output.
lib/backend/src/Obelisk/Backend.hs:1 _backendConfig_frontendGhcjsAssetsis introduced as a general “compiled frontend assets” path used by the backend serving logic, but the name hard-codes “Ghcjs” even though this PR adds a WASM frontend target that still produces/serves afrontend.jsexedirectory. Consider renaming this field to a target-agnostic name (e.g.,_backendConfig_frontendAssetsor_backendConfig_compiledFrontendAssets) to avoid confusion and accidental misuse.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ghcjs is no longer enabled by default in the skeleton's crossPlatforms. Users can uncomment it and use the -f -wasm flag with cabal/ob-run to enable JS builds.
> Add cabalProject, cabalProjectLocal, cabalProjectFreeze, cabalProjectFileName passthroughs > > These haskell.nix project options are now exposed as top-level > nix-haskell module options and passed through via intersectAttrs. > > Filter null values from the passthrough to avoid overriding > haskell.nix's internally-computed defaults.
> Remove hardcoded *wasm* from cross-compilation LDFLAGS filters > > Instead of always including *wasm* in the linker flag filter patterns, > conditionally add "wasm" to targetPrefixes (both global and per-wrapper) > only when a WASM target is present. Skip shellHook entirely when no > cross platforms are configured.
> Match flake.nix with ./pins
- Add `inputs` parameter to nix-haskell default.nix/eval.nix; when
input names match pin options, override pins via mkDefault
- Use git+file: for nix-haskell submodule input (path: doesn't
include submodule contents in store)
- Thread `inputs` through obelisk flake.nix → nix/, release.nix,
skeleton/ so flake inputs reach nix-haskell
- Legacy nix (inputs={}) continues resolving pins through thunks
Add `flake-compat` flake input (following nix-haskell) and create `inputs.nix` files that extract flake inputs for non-flake evaluation. Thread `inputs` parameter through skeleton's `shell.nix` and `default.nix`. Update `nix-haskell` submodule.
nix-haskell: - Add WASM cross-compilation support module with Node.js dependency reflex-dom: - Add flake inputs and shell.nix for nix-haskell integration
|
Holy poo. >_> |
|
I have to admit, I am very sceptical of this change. I am still not inclined to use haskell.nix but besides that using a bespoke and confusingly named additional abstraction layer on top of it which has not been used anywhere seems the wrong way to go to me. In my opinion obelisk + r-p is already too much abstraction layer anyway. (I am not against abstraction in general, but these nix abstraction layers are nearly always leaky abstractions with bad complexity/pay-off ratio.) It seems unlikely to me, that we @heilmannsoftware would switch to an obelisk based on this. That being said if we don’t release something based on the new backends, the marvel of reflex will just be wasted and this approach is at least a way forward. |
Summary
Complete rewrite of the nix build system around nix-haskell and a NixOS-style
module system. Replaces reflex-platform with direct haskell.nix integration.
Adds WASM frontend target alongside GHCJS. Adds GHC 9.14 support.
Vanilla cabal builds
cabal build/cabal runwithout anynix wrapper. The
obCLI tool is no longer required for development.ob-rundevelopment script usescabal run backenddirectly with inotifywaitfor file watching, forwarding optimization flags to cross-compilation builds.
ob-replstartscabal replwith optimizations disabled for fast iteration.obelisk-setup) handle cross-compilation of thefrontend (WASM or GHCJS) and static asset generation transparently during
a normal
cabal build, with no nix involvement at build time.Nix module system
nix/module.nixmodule with declarative options:obelisk.static.path/obelisk.static.compress/obelisk.static.compressed: static asset pipeline with cache-busting hashes, brotli + gzip compressionobelisk.frontend.target: select"js"(GHCJS) or"wasm"(default) frontend compilation targetobelisk.frontend.js.{package,optimization,optimized,compress,compressed}: GHCJS pipeline with closure-compiler (ADVANCED mode by default)obelisk.frontend.wasm.{package,optimization,optimized,compress,compressed}: WASM pipeline with wasm-opt + wasm-tools stripnix/lib.nixwith composable haskell.nix overrides:buildTypeOverride,staticManifestOverride,frontendDataOverride,backendDataOverride,jsexeOverridemkOptionalPackagesto safely skip absent packages in cross-compilation projectscompiler-nix-name = "ghc914")nix/assets.nixasset compression pipeline with brotli (quality 11) + gzip viamkAssets/unionEncodingsWASM frontend
frontend.jsexedirectory the backend serves unchangednix/wasm/shim.js) using async IIFE with dynamicimport(), resolving assets relative to script URL@bjorn3/browser_wasi_shimfor WASI browser supportwasm-optoptimization andwasm-tools stripfor production buildspost-link.mjsobelisk-setup
obelisk-setuppackage with reusable Setup.hs hooks:Obelisk.Setup.Backend: symlinks frontend assets into backend data dirObelisk.Setup.Frontend.Js: GHCJS cross-compilation with optimization forwardingObelisk.Setup.Frontend.Wasm: WASM cross-compilation with jsexe assembly, JSFFI extraction, optional wasm-opt/wasm-toolsObelisk.Setup.Static: static manifest generation viaobelisk-asset-manifest-generateObelisk.Setup.Utils: shared utilities (project root discovery, symlinks, cabal-level optimization flags)Static asset generation
obelisk-generated-static/obelisk-generated-static-custompackage pair:obelisk-generated-static(build-type: Simple) builds on all platforms including JS/WASMobelisk-generated-static-custom(build-type: Custom) runs Setup.hs to generateObelisk.Generated.Staticmodule,buildable: Falseon JS/WASMobelisk-asset-manifest-generate --module-onlygenerates only the Haskell module without overwriting the.cabalfileSymbolname resolution (GHC.Types.SymbolvsGHC.Internal.Types.Symbol)Backend
_backendConfig_frontendGhcjsAssetsfield toBackendConfigfor separate frontend asset pathsDevelopment tools
ob-run: watch-and-rebuild development server with inotifywait, forwards optimization level to cross-builds viaOBELISK_CROSS_CABAL_ARGS, proper cleanup of all child processes on exitob-repl: optimizations-disabled REPL, defaults to loading backend + common + frontendob-hoogle: local Hoogle documentation server with start/stop/restart, automatic cleanup on shell exitDeployment
nix/server.nix: NixOS module (services.obelisk) with nginx reverse proxy (WebSocket support), ACME/HTTPS, systemd service, firewall rules, domain redirectsmkServerExe: assembles flat deployment directory (backend binary + compressed assets)mkContainerImage: OCI container image viadockerTools.buildLayeredImagefor podman/dockerProject skeleton
skeleton/: minimal obelisk project template with complete directory structure:project.nixwithsource-repository-packagesfrom obelisk, cross-platform shell (ghcjs + wasi32), hooglecabal.projectwith arch conditionals excluding native-only packages from cross-buildsOther
flake.nixexposing lib and docs packagesnix/docs.nix)ghcjs_HOST_OStodefined(ghcjs_HOST_OS) || defined(wasm32_HOST_ARCH)throughout frontend codeI have:
developbranchhlint .(lint found code you did not write can be left alone)$(nix-build -A selftest --no-out-link)nix-build release.nix -A build.x86_64-linux --no-out-link(orx86_64-darwinon macOS)nix-buildusing the new module systemob-run,ob-repl, andob-hooglescripts in dev shelllib/route/test/)