Laminar v0.8.0 Release Notes
Release Date: 2020-03-01 // about 4 years ago-
๐ This release is a significant improvement to both usability and safety of Laminar. We overhauled the lifecycle event system, and reworked how Laminar makes use of Airstream ownership, fixing longstanding design flaws in the process. We also simplified many things e.g. we eliminated the whole
Scala DOM Builder
layer. Below is a comprehensive list of changes with migration tips sprinkled throughout.- โ Addons
- New: Waypoint โ efficient URL router for Laminar
- ๐ Documentation
- The entire documentation was of course updated for v0.8.0. For those already familiar with Laminar v0.7 API, the following sections contain new or significantly updated conceptual material:
- Manual Application
- Reusing Elements
- Efficiency
- Binding Observables
- Ownership
- Memory Management
- Element Lifecycle Hooks
- See also the Changelog for Airstream v0.8.0, and the new Dynamic Ownership section in Airstream docs.
- New blog post expanding on the rationale behind Laminar: My Four Year Quest For Perfect Scala.js UI Development
- ๐ New: Ownership & Lifecycle Events overhaul
- This is the flagship feature of this release.
- Ownership
- Laminar now uses Airstream's new
DynamicOwner
andDynamicSubscription
features:ReactiveElement
subscriptions are now activated every time the element is mounted, not when it is first created. This solves a long standing design flaw (#33). - You can now re-mount previously unmounted elements: their subscriptions will be re-created and will work again.
- Lifecycle Event system
- Eliminate
mountEvents
andparentChangeEvents
streams,maybeParentSignal
signal,MountEvent
type, and all related machinery - New:
onMountCallback
/onMountBind
/onMountInsert
/onUnmountCallback
/onMountUnmountCallback
modifiers - Mount events are now propagated differently down the tree, which has minor timing differences:
- Order of unmount events changed between child / parent components. If a subtree is being unmounted, the unmount hooks on children will fire before they fire on their parents.
- Previously mount events had to wait for the current Airstream Transaction to finish when propagating to child nodes (similar to how EventBus always emits events in a new Transaction). This is not the case anymore. In practical terms this is unlikely to affect you, except that it fixes #47.
- Migration: Read the new docs and choose an appropriate onMount* hook instead of the old streams. Don't rush to use
onMountCallback
, check out all of the hooks first.
- ๐ API: Merge
EventPropEmitter
andEventPropSetter
intoEventPropBinder
)- Unlike the old
EventPropSetter
,EventPropBinder
adds and removes event listeners on mount, not on instantiation. This should not be a problem as event listeners generally don't fire on detached nodes in the first place. - Migration: Replace usages of
EventPropEmitter
withEventPropBinder
. If you were usingEventPropEmitter
as anEventPropTransformation
, those methods will not be available anymore asEventPropBinder
does not extendEventPropTransformation
, so you will need to rewrite those calls differently, more manually. This usage wasn't explicitly documented, so I hope no one actually runs into this.
- Unlike the old
- API: RootNode no longer automatically mounts itself when the provided
containerElement
is itself unmounted.- This improves ease of integration if you want to e.g. render a Laminar element in a React component.
- Migration: this probably does not affect you. If it does, you'll need to call
rootNode.mount()
when appropriate. See the new "What Is Mounting?" section in docs.
- API: Replace
ReactiveEventProp.config(useCapture: Boolean)
withEventPropTransformation.useCapture
andEventPropTransformation.useBubbleMode
- Migration example:
onClick.config(useCapture = true)
-->onClick.useCapture
- Practically this means that you can set
useCapture
anywhere you have anEventPropTransformation
, not just where you have the originalReactiveEventProp
- Migration example:
- API: New modifier subtypes:
Setter
,Binder
andInserter
(oldSetter
renamed toKeySetter
)- The behaviour is the same except for subscription lifecycle as explained in ownership, but you need to know the distinction between the new types to use the new lifecycle hooks.
- API: Eliminate dependency on Scala DOM Builder
- DOM Builder is capable of supporting different DOM backends and even JVM rendering. We have no plans to use either of these in Laminar, so the indirection required by these abstractions is not pulling its weight.
DomApi
- Remove the old
DomApi
trait and companion object.- Combine
domapi.*Api
traits into a singleDomApi
object - Use the new
DomApi
object directly instead of passing implicitdomapi.*Api
parameters.
- Combine
- Move
Setter
andEventPropSetter
into Laminar, simplify type signatures and rename toKeySetter
andEventPropBinder
- Merge into relevant Laminar subtypes:
Node
->ReactiveNode
(addRef
type param),Comment
->ReactiveComment
,Text
->TextNode
,ParentNode
->ParentNode
,ChildNode
->ChildNode
,Root
->RootNode
,TagSyntax
->HtmlTag
&SvgTag
- Merge
EventfulNode
trait intoReactiveElement
(split members between the trait and the companion object) - Change type of
maybeEventListeners
to haveList
instead ofmutable.Buffer
, it was never intended to be mutable - Migration: If you reference any of the affected types directly, you will need to import them from Laminar, or use their corresponding Laminar replacements listed above. Other than that, everything should just work.
- API: Limit access / hide / obscure
- Hide
willSetParent
andsetParent
methods. Use Laminar'sParentNode.appendChild(parent, childNode)
or similar. - Move
appendChild
and other similar methods from ParentNode instance to companion object. - Those low-level methods are still available to you as a user, but generally you should avoid them in favor of a reactive approach (various
<--
and-->
methods). - ReactiveElement.maybeChildren is not mutable anymore (was never intended to be)
- subscribe* methods renamed and moved to ReactiveElement companion object
- Migration: use the new
observable --> observer
modifier, or the new onMount* hooks, and/or the newelement.amend
method. - Make sure to document
nodeToInserter
@nc
- Hide
- ๐ฆ API: eliminate auxiliary syntax
myElement <-- child <-- childSignal
- Use the new
amend
method instead:myElement.amend(child <-- childSignal)
- Use the new
- ๐ API: Remove
ChildNode.isParentMounted
method. Use a similarChildNode.isNodeMounted
instead. - ๐ฆ API: Move
ChildrenCommand
out of the poorly namedcollection
package - API: Rename types:
ReactiveHtmlBuilders
->HtmlBuilders
,ReactiveSvgBuilders
->SvgBuilders
,ReactiveRoot
->RootNode
,ReactiveComment
->CommentNode
,ReactiveText
->TextNode
,ReactiveChildNode
->ChildNode
- ๐ New:
ReactiveElement.events(ept: EventPropTransformation)
, works the same asReactiveElement.events(p: ReactiveEventProp)
, returning a stream of events- Example:
div(...).events(onClick.useCapture.preventDefault)
- Useful to combine with the new
observable --> observer
method.
- Example:
- ๐ New:
element.amend
method - ๐ New:
onMountFocus
modifier - focus an element every time it's mounted - API: New alias for inContext:
forthis
- API:
ReactiveElement
and other node types that take type params now havetype Base
defined on their companion objects containing the most generic version of that type, e.g.ReactiveElement[dom.Element]
forReactiveElement
. - ๐ Build: Note that this release is version
0.8.0
, not0.8
as I would have named it before.
- โ Addons