Laminar v0.11.0 & New Website
I am pleased to announce the release of Laminar v0.11.0, Airstream v0.11.0, and our new website.
This release improves transaction scheduling in Airstream, fixes several bugs, and takes away a few obscure footguns from users.
New Website!
👉 laminar.dev
- A flashy marketing home page in the best Scala colors. New to Laminar? Start there.
- Laminar documentation with a nice sidebar for navigation
- Note: documentation is still available in github source, but the file moved to
website/docs/documentation.md
.
- Note: documentation is still available in github source, but the file moved to
- Live, interactive examples, including TodoMVC and Web Components that I brought over from the old laminar-examples repo.
- I will be adding more eventually, and you can add your favourite patterns too! This should become a comprehensive book of Laminar recipes.
- Release announcements and changelogs will now be posted on this blog.
Changelog below. Breaking changes in bold.
Airstream v0.11.0
- New: Improve transaction scheduling to fix undesired Var glitch (#39, #40)
- Consider the following situation:
- Transaction A is currently running. You schedule transactions B and C (e.g. two separate updates to a
Var
, inside astream.foreach
). - Transaction A finishes, transaction B is now executed. So far so good. Now, execution of transaction B causes transactions D and E to be scheduled (same pattern as before, could be other
Var
updates orEventBus
emits). - Transaction B finishes. Under old logic, transaction C would now execute, because it was scheduled earlier than D and E. This would cause the full propagation of transaction B to be incomplete by the time C is executed, as D and E haven't executed yet.
- So with new logic, transaction D would now execute instead of C, because D is considered to be the "child" of transaction B that just finished. And if D created yet more child transactions, they would execute before E.
- Transaction A is currently running. You schedule transactions B and C (e.g. two separate updates to a
- So, transaction scheduling is now following a depth-first hierarchy traversal instead of linear FIFO. The new logic makes more intuitive sense, and also happens to fix a whole class of glitches.
- This can potentially cause breakage if you rely on the pre-v0.11.0 FIFO behaviour, but it's pretty unlikely. Nobody who tried the v0.11.0-M1 release reported any problems.
- As a reminder, new transactions are created when you fire events into Var, EventBus, EventStream.merge, as well as flatMap and flatten methods. Basically whenever you manually fire events, or any functionality that lets you potentially create an observable loop, i.e. an observable that depends on itself.
- Remember that transactions are entirely synchronous, so none of this affects transactions that are scheduled after an asynchronous delay, e.g.
delay(ms)
orjs.timers.setTimeout(ms)
. Those are executed at the same as before because their scheduling is guaranteed to happen when there are zero pending transactions, so they are executed immediately upon being scheduled. - If you used any patterns similar to those described in #39, check those usages at runtime to be sure.
- Consider the following situation:
- Fix:
Var#update
,Var#tryUpdate
,Var.update
, andVar.tryUpdate
methods now evaluate theirmod
input functions only when their transaction has started executing- Previously, if you called
myVar.update(mod)
, Airstream would immediately evaluatemod(myVar.now())
, but if you were inside a transaction, which is usually the case, the propagation of the new Var's value would be scheduled for later, because that requires a new transaction. - As a result, this allowed for a window of time when a previously scheduled transaction could have updated this Var's value, meaning that our
mod
was actually operating on a stale value. This bit us in our soft behinds. - Now,
mod
will only be called when we are ready to propagate its output, nothing else will be able to update this Var in between. Together with the change in transaction scheduling, this can potentially introduce some breakage if you were relying on reading stale values from your Var-s usingupdate
ortryUpdate
.
- Previously, if you called
- Fix: Var.set, EventBus.emit, and similar methods do not accept inputs with duplicate Vars / EventBuses anymore
- That is, you can't do
Var.set(var1 -> foo, var1 -> bar)
. This was never intended to be possible, and will now throw, because doing this violates Airstream basic transaction contract that an observable can emit at most once per transaction.
- That is, you can't do
- Fix: DynamicOwner uses OneTimeOwner now
- Users can no longer accidentally create subscription with owners created by DynamicOwner after DynamicOwner kills those owners. Such usages will now throw.
- Also see Laminar release notes below.
- Fix: hide
Owner#onKilledExternally
method- It was never supposed to be public, you probably aren't using it.
- Naming:
Signal#fold
->Signal#foldLeft
,Signal#foldRecover
->Signal#foldLeftRecover
- New:
Observable#toSignal(initialIfStream)
andObservable#toStreamOrSignalChanges
- Convenience methods to refine Observables into EventStream or Signal, useful to be able to write logic that accepts Observable but still wants to do something useful with it.
Laminar v0.11.0
- New: Upgrade to Airstream v0.11.0 (see above)
- Fix: Laminar-provided Owners are now perma-killed after they are unmounted (#67).
- In Laminar you can manually get an element's current Owner from a method like
onMounCallback
. Previously you could create subscriptions with that owner at any time, including after the element was unmounted. Which would be unwise, because after unmount, Laminar kills and discards the owner, so if you keep adding subscriptions to the owner after that, they would never be killed. - From now on, Laminar elements use
OneTimeOwner
-s which cannot be used after they are killed. So now if you try to create a subscription with a manually obtained Owner after the element you got it from was unmounted, the subscription will be immediately killed, and the subscription creation code will throw with information about which element's owner is at fault. - It is unlikely that you will run into this. This requires very specific, very manual, and very wrong usage.
- In Laminar you can manually get an element's current Owner from a method like
- Fix: ReactiveElement#events no longer ignores
stopPropagation
andpreventDefault
options (#68)- This only applies to syntax like
element.events(onClick, stopPropagation = true) --> ...
, not the more commononClick.stopPropagation --> ...
- If you were using the
events
method with either of those options, this release will actually enable them, which could potentially break the handling of those events for you.
- This only applies to syntax like
- Fix: Filter transformation now applies to subsequent stopPropagation and preventDefault transformations (#58)
- Consider this example:
onKeyPress.filter(_.keyCode == KeyCodes.Enter).preventDefault.mapTo(thisNode.ref.value) --> observer
- As intended,
observer
only fires iffilter
passes. However,preventDefault
would previously fire on all keypress events, even iffilter
didn't pass. Same forstopPropagation
. - Other transformations were properly conditional, and now these two are fixed such that in this example,
preventDefault
would only fire iffilter
passes (because it's to the right of it, not to the left where it would have been unconditional). - Check your usages of
preventDefault
andstopPropagation
that are behind a filter, and move them to be before the filter if you want them to be unconditional. - This only applies to Laminar-provided
preventDefault
andstopPropagation
helpers, not the JS native methods ondom.Event
of the same name.
- Consider this example:
- New:
stopImmediatePropagation
helpers (MDN)- Laminar API similar to that of
stopPropagation
- Laminar API similar to that of
- New:
child.int
receiver for convenience, similar tochild.text
- You can now say
child.int <-- observableOfInt
- You can now say
- New:
intToNode
implicit conversion, similar totextToNode
- You can now print integers without toString:
div(usersCount, " users")
- You can now print integers without toString:
Thank You
Laminar & Airstream development is sponsored by people like you.
GOLD sponsors supporting this release: ✨ Iurii Malchenko
Thank you for supporting me! ❤️
Thanks to Anton for kick-starting the website effort, configuring mdoc and docusaurus! 🚀