Skip to content

1.14.0

Latest

Choose a tag to compare

@dmjio dmjio released this 13 Sep 19:07
· 24 commits to master since this release
5e3d64c

🍜 1.14.0

This release introduces the VContext, VProps and VModel DSL terms, along with their smart constructors vcontext, vprops and vmodel, for ambient access to the context, props and model from anywhere in a View.

It also introduces two breaking changes: the view function now takes fewer arguments (only the model), and the View type takes more type parameters (View context props model action) β€” View now has type parameter symmetry with Component context props model action and Effect context props model action. See the migration notes below.

✨ Special thanks

✨ Highlights

  • Ambient accessors: vcontext / vprops / vmodel (synonyms withContext / withProps / withModel). Any part of a view tree can read the app-global context, the enclosing component's props, or its model without an extra argument. They are not nodes: each wraps a function that is applied β€” and the wrapper discarded β€” when the tree is built or rendered, so they never appear in the virtual DOM the runtime diffs. props and model are resolved through the type system (a mismatch is a compile error); context against the app's single cell.
  • ⚠️ view takes only the model. view :: model -> View context props model action. Read context/props with the accessors instead. Migration below.
  • ⚠️ View gained a props type parameter. View context model action β†’ View context props model action, matching Component context props model action.
  • toHtmlWith β€” toHtmlWith :: context -> props -> model -> View context props model action -> ByteString renders a component's view on the server; it supplies the values the accessors resolve against. Plain toHtml is for bare static markup (context/props/model all ()). ⚠️ setContext is gone with it.
  • One context cell per app. The globalContext top-level IORef is gone; the app owns the cell, every component holds a reference to it, and modifyContext is a single atomic update rather than a rewrite of every component.
  • Static mounts. SomeStaticComponent now holds the Component itself (plus its dictionaries); mountStatic works for components with or without props (mountStaticWithProps is a deprecated alias until 1.15). A static mount now uses its StaticKey as the diff key, so swapping vcomp_ (static (mountStatic A)) for …B replaces the child instead of running B's diffProps against A.
  • Lifecycle hooks. New onDestroyedWith, and onDestroyedWith / onBeforeDestroyedWith now actually receive the element's DOMRef (it was always undefined).
  • Miso.Lens fixes. preview / preuse no longer loop forever; _Nothing is now Prism (Maybe a) ().
  • Hydration. Events raised before the vtree is mounted are dropped instead of dispatched against nothing (a startup race); toHtml collapses adjacent text nodes inside fragments, across a [View], and through the ambient accessors, so the server's markup matches the client's hydration walk instead of silently falling back to a full re-render.
  • Native (Lynx). svgWith_ β€” inline SVG content that reads context / props / model ambiently; content_ takes all three ahead of the content View.
  • Nix / CI. A real, callCabal2nix-capable wasm32-wasi Haskell package set (pkgs.wasmPkgs, pkgs.wasmWebBundle; nix-build -A miso-wasm-ghc9141, sample-app-wasm-ghc9141, …) built on ghc-wasm-meta, pinned to flake.lock; the WASM integration tests are nix-native; overlays.default is exported for downstream flakes; everything moved to GHC 9.14.1; stale GitHub Actions bumped; cabal check-clean upper bounds.

⚠️ Breaking changes & migration

1. view takes only the model. Drop the context and props arguments; a view that ignored them just loses two wildcards:

-- 1.13
viewModel :: () -> () -> Model -> View () Model Action
viewModel _ _ (Model x) = …

-- 1.14
viewModel :: Model -> View () () Model Action
viewModel (Model x) = …

A view that used them reads them at the point of use instead (see the snippets below). component / startApp / startAppWithContext / misoWithContext / prerenderWithContext are otherwise unchanged.

2. View has a props parameter. Insert a props variable after context in your signatures; code that leaves it polymorphic needs nothing else:

header :: View context model action          -- 1.13
header :: View context props model action    -- 1.14

Mount combinators (mount_, mountWithProps, (+>), vcomp, …) forget the child's props at the boundary exactly as they forget its model and action.

3. Server-side rendering. ToHtml (View …) and ToHtml [View …] now require context ~ (), props ~ () and model ~ () β€” a bare View is static markup. A component's view is rendered with toHtmlWith, which replaces the removed setContext:

toHtml (div_ [] [ "static markup" ])              -- bare View
toHtmlWith ctx props model (view comp model)      -- a component's view

4. SomeStaticComponent. Its payload is a Component, not a props -> SomeComponent function; mountStatic now accepts components with props, mountStaticWithProps is deprecated (removed in 1.15). Call sites like vcomp_ (static (mountStatic comp)) are unchanged. On Lynx, Miso.Native.X.Element.Svg.Property.content_ takes context, props and model ahead of the content View (or use svgWith_).

5. _Nothing :: Prism (Maybe a) () (was Prism (Maybe a) a, which could never match).

πŸ“– Using vcontext, vprops and vmodel

All three are drop-in Views: put them anywhere a node goes. The function you pass is applied when the tree is built or rendered.

data Theme = Light | Dark deriving Eq
data Props = Props { title :: MisoString } deriving Eq
data Model = Model { count :: Int } deriving Eq

view :: Model -> View Theme Props Model Action
view Model { count } =
  div_ []
    [ -- the app-global context, read where it is needed
      vcontext $ \theme ->
        span_ [ class_ (themeClass theme) ] [ "Counter" ]
      -- the props this component was mounted with
    , vprops $ \Props { title } ->
        h1_ [] [ text title ]
      -- the model β€” also available ambiently, for helpers it isn't passed to
    , vmodel $ \Model { count = c } ->
        span_ [] [ text (ms c) ]
    , button_ [ onClick AddOne ] [ "+" ]
    , text (ms count)
    ]

Because props and model are type parameters of View, vprops sees the props of the component whose view contains it and vmodel its model β€” statically. A child mounted with mountWithProps / vcomp sees its own props and model, never its parent's.

The model is still handed to view purely for convenience; view _ = withModel $ \m -> … is equivalent. Where the accessors earn their keep is a helper deep in the tree that would otherwise need the value threaded down through every call:

-- Themed button usable from any component in the app, no `theme` argument.
themedButton :: MisoString -> action -> View Theme props model action
themedButton label act =
  withContext $ \theme ->
    button_ [ class_ (buttonClass theme), onClick act ] [ text label ]

Redraw semantics are unchanged β€” the accessors add no redraw logic of their own:

  • vcontext does not opt a component into context-driven redraws; that is still governed by useContext = True. It sees a fresh context whenever the surrounding view is rebuilt for any reason.
  • vprops is re-resolved when the parent passes different props; vmodel when the model changes after update.

On the server, toHtmlWith ctx props model is what they resolve against; a vmodel nested inside a mounted child component sees that child's initial (or hydrated) model.

What's Changed

  • fix: Prism preview/preuse infinite loop, _Nothing match by @dmjio in #1632
  • refactor: drop the liveMode IORef, thread live explicitly by @dmjio in #1633
  • fix: Drop events raised before the vtree is mounted (hydration race) by @dmjio in #1635
  • fix: pass domRef to onDestroyed / onBeforeDestroyed hooks by @dmjio in #1636
  • feat: VContext by @ners in #1637
  • feat: VProps by @dmjio in #1638
  • refactor: SomeStaticComponent holds Component by @dmjio in #1641
  • fix: use the StaticKey as a static mount's diff key by @dmjio in #1642
  • feat: VModel by @dmjio in #1639
  • fix: Drop unused _componentEvents by @dmjio in #1644
  • refactor: replace globalContext with one app-owned context cell by @dmjio in #1643
  • refactor: view takes only model by @dmjio in #1645
  • docs: fix stale view/View Haddocks and context/props ambient-read examples by @dmjio in #1646
  • release: bump to 1.14.0.0 by @dmjio in #1647
  • flake: expose overlays.default by @dmjio in #1648
  • ci: bump stale GitHub Actions versions, drop dead security opt-in by @dmjio in #1651
  • nix: wasm32-wasi Haskell package set via ghc-wasm-meta by @dmjio in #1650
  • nix: fix deprecated pkgs.system in wasm32-wasi package set by @dmjio in #1653
  • fix: pre-release regressions found reviewing 1.13.0 -> 1.14.0.0 by @dmjio in #1654
  • ci: pin ghc-wasm-meta to flake.lock everywhere by @dmjio in #1655
  • docs: title the CHANGELOG section for 1.14.0.0 by @dmjio in #1656
  • chore: add upper bounds for ghcjs-base, ghcjs-prim and ghc-experimental by @dmjio in #1657

Full Changelog: 1.13.0...1.14.0