Releases: PurpleKingdomGames/tyrian
0.11.0
Maintenance release
Tyrian has been brought up to date with with Scala.js 1.16.0 and Scala 3.4.1.
When idle, do nothing.
Also in this release is a re-organisation of the code, in support of a change in runtime behaviour. It was reported that Tyrian was quite resource hungry even on idle/static pages, and that this was probably undesirable particularly on such things as mobile devices.
The cause of all this resource hogging was the continuous requestAnimationFrame
loop that the engine used to make use off to, ironically, do less rendering. This behaviour has now changed so that idle pages do indeed stop doing work until something happens.
Tyrian-Tags
Since the runtime and code organisation was under scrutiny anyway, some work was also done to reorganise the modules a bit.
The aim here was to make the modules that were published for the JVM and JS to do the same thing in all cases (where previously the JVM had the same name as the JS module, but had drastically reduced functionality, and hopefully avoid future user confusion.
As a result, the tyrian
module is now only available for Scala.js, and TyrianAppF
has been renamed to simple TyrianApp
. So if you are using TyrianAppF.start()
for some unknown reason, this will affect you (most people won't be).
Anyone using Tyrian for SSR (Serverside Rendering) on the JVM, should now pull in the new tyrian-tags
module instead, which otherwise behaves identically to the way it did before.
The Tyrian Tags module is cross published for JS and the JVM, and contains all the HTML, CSS, and Location data, that works on both platforms.
What's Changed
- Add debouncing example by @DLakomy in #256
- Add Tailwind CSS example by @francosang in #261
- Fixed #259: Upgrade to Scala.js 1.16.0 by @davesmith00000 in #260
- Fixes #257: Upgrade Scala to 3.4.1 by @davesmith00000 in #258
- Reworking the runtime by @davesmith00000 in #263
- Issue #252: Tyrian tags module by @davesmith00000 in #265
New Contributors
- @DLakomy made their first contribution in #256
- @francosang made their first contribution in #261
Full Changelog: v0.10.0...v0.11.0
0.10.0
This release is comprised of two changes by @KristianAN and @JPonte (respectively). Many thanks! π
If you see issues with any aspect of Tyrian, please do not hesitate to report them and all efforts will be made to correct them.
FileReader, read as byte array
You can now use the provided FileReader.readBytes
to read a loaded file as a byte array.
HTMX Support Added
A new module has been published, tyrian-htmx
, that adds syntax support for HTMX to Tyrian's HTML tag DSL. This means you can add HTMX markup to your server-side rendering, allowing you to add interactivity to your web pages in true hypermedia style! πͺ
...handy for all those occasions when a full SPA (written in Tyrian or otherwise...) feels like overkill...
Brief documentation for HTMX can be found under the server-side rendering guide, though you will need to visit the HTMX docs for the full description of how to use it.
What's Changed
- Adding API on cmd.FileReader for reading a file as ArrayBuffer by @KristianAN in #251
- #228 HTMX attributes prototype by @JPonte in #250
Full Changelog: v0.9.0...v0.10.0
0.9.0
What has changed?
Scala.js 1.15.0
All PKG libs are being brought up to date with Scala.js 1.15.0, and Tyrian is no exception.
File reader's now read text and image data correctly
This was a bug fix where Tyrian's built in FileReader.readImage
and FileReader.readText
Cmd
s were both reading the files using readAsDataURL
, when the text read should have used readAsText
.
LocationDetails
shared for JVM+JS
Previously the LocationDetails
types which are part of the frontend routing code, were only available for JS, but they turned out to be more generally useful and so have been published for the JVM too.
Revised raw
constructors
A small API change for making raw
tag constructors only ever use two groups of arguments, i.e. from raw(name)(attributes)(html)
to raw(name, attributes)(html)
or raw(name)(html)
. The feeling was that like the spanish inquisition, no-one ever expects three sets of arguments...
Rethinking HTML rendering (SSR)
Previously, the API for doing SSR style rendering of Tyrian's tags was rather clunky, particularly with regard to doctypes, requiring you to call TyrianSSR.render(includeDocType = true, ...etc...)
.
While trying to implement syntax that allowed you to simply prepend the doctype (as Scalatags does), like this:
"<!DOCTYPE HTML>" + p("Hello, world!")
// <!DOCTYPE HTML><p>"Hello, world!"</p>
It became clear that the simplest thing was just to have the tags nicely render themselves via toString
(or via the .render
extension method, if you prefer). So now the above works as expected in JS and on the JVM.
Cmd.SideEffect can have a non-unit return type
A minor improvement so that you don't need to return unit with Cmd.SideEffect
. Previously:
Cmd.SideEffect {
10
()
}
Now:
Cmd.SideEffect {
10
}
The Tyrian-Indigo Bridge moved to the Indigo repo
This move was to break the circular dependency between Tyrian and Indigo. Indigo now depends on Tyrian in only that direction, which aligns with possible future plans to bring the two closer together.
In practical terms, it means the bridge will move to Indigo's version scheme during the next Indigo release.
TyrianApp
is deprecated
TyrianApp
, the main trait you extend when building a Tyrian... app... has been removed in favour of TyrianIOApp
and TyrianZIOApp
. The idea of having the same class in two different modules was in fact to make things easier - one less thing to learn! Unfortunately it was causing some confusion if both the tyrian-io
and tyrian-zio
libs were included (which they should not be...), and since the signatures were incompatible anyway, the decision was made to make them different in name as well as implementation.
HTML Tags can have key's to guide the Virtual DOM
If you produce code that shuffles child tags around in order to produce some fancy effect, then it's possible to confuse the Virtual DOM, and produce unexpected / undesirable results.
To fix this, you can now optionally assign keys to your tags. These are Snabbdom keys and follow the same rules, i.e. they are strings and must be unique under the the common parent node. Tags now have setKey
, clearKey
, and withKey
methods. Below is an example of keys in action, where the keys are just 1, 2, 3, 4, and 5 stringified:
0.8.0
Release summary
No major changes in this release... but lots of small fixes, a few minor API changes, several improvements, and some new additions by lots of new contributors!
Many thanks! π
What's Changed
- CmdHelper - Small optimisation. by @diesalbla in #213
- Sub - Remove use of Kleisli. by @diesalbla in #215
- TyrianAppF - Remove for comprehensions by @diesalbla in #216
- Sub timeout - remove unused F wrappers. by @diesalbla in #214
- Narrowing Http.send return type by @TonioGela in #225
- docs: Fix typo on routing.md by @vdnhi in #221
- File picker by @hobnob in #219
- Adds capability to download a file by @hobnob in #222
- update dependencies by @ngbinh in #220
- Laika site by @davesmith00000 in #223
- Fix map for Event attributes by @jmcclell in #226
New Contributors
- @diesalbla made their first contribution in #213
- @TonioGela made their first contribution in #225
- @vdnhi made their first contribution in #221
- @hobnob made their first contribution in #219
- @ngbinh made their first contribution in #220
- @jmcclell made their first contribution in #226
Full Changelog: v0.7.1...v0.8.0
0.7.1
Release summary
This is a tiny little release that does two notable things:
- Moves Tyrian onto Scala 3.3.0, which is the LTS version.
- Provides convenient syntax for optional nodes. A somewhat contrived example:
This does not compile in any version, because Option
is not an element:
div(
p("Do we have a colour?"),
Option(Colour.Blue).map(c => p(s"yes, $c"))
)
Previously you would have had to getOrElse
/fold
your way out of it, but you would have needed to provide a tag as there was no way to say "don't render anything".
Now you could do either of the following:
import tyrian.syntax.*
Option(Colour.Blue).map(c => p(s"yes, $c")).orEmpty
Option(Colour.Blue).map(c => p(s"yes, $c")).getOrElse(Empty)
What's Changed
- Upgrade to Scala 3.3.0 by @davesmith00000 in #207
- Fixed #205: Empty Elem + Option.toEmpty by @davesmith00000 in #208
Full Changelog: v0.7.0...v0.7.1
0.7.0
Thanks!
As always I would like to start with a thank you to all those of have contributed to making this release happen, either directly or indirectly. Everyone who engages with the project in anyway makes a difference, and all your support is appreciated. π
Migration example
Doing the most minimal migration to Tyrian 0.7.0 from previous versions is a fairly straightforward process of:
- Providing a
NoOp
message (meaning 'no operation') - Implementing the
router
function that does nothing, returning theNoOp
. - Handling the NoOp in your app update by simply returning
(model, Cmd.None)
when it is encountered.
Examples can be found here.
Summary of key changes
Built-in Frontend Routing
At long last, frontend routing has come to Tyrian! ...whether you like it or not!
All Tyrian apps, along side the usual standard functions, will now need to implement a routing function:
def router: Location => Msg
Aside from organising suitable messages to tell your app what to do when the location changes, this is the only modification you will need to make. All the other plumbing is done for you in the background.
There are a few ways to implement the router
function. Please note that in all cases, you are told if the link is considered internal or external and they are treated separately.
If your needs are simple, then the easiest way to implement the router is to use the helpers in the Routing
module. For example, this one ignores all internal links and just routes external ones. Again, note that you need to provide suitable custom Msg
's:
def router: Location => Msg = Routing.externalOnly(Msg.NoOp, Msg.FollowLink(_))
There are also Routing.basic
(simple string match) and Routing.none
variations.
Alternatively, you can do a full route match, like this:
def router: Location => Msg =
case loc: Location.Internal =>
loc.pathName match
case "/" => Msg.NavigateTo(Page.Page1)
case "/page1" => Msg.NavigateTo(Page.Page1)
case "/page2" => Msg.NavigateTo(Page.Page2)
case "/page3" => Msg.NavigateTo(Page.Page3)
case "/page4" => Msg.NavigateTo(Page.Page4)
case "/page5" => Msg.NavigateTo(Page.Page5)
case "/page6" => Msg.NavigateTo(Page.Page6)
case _ => Msg.NoOp
case loc: Location.External =>
Msg.NavigateToUrl(loc.href)
The Location
type comes with all sorts of information in it for you to decide how to route, it is similar to the JavaScript Location
type.
The expectation is that you could also decide to pull in some fancy url parsing library to build a clever router here, if you feel like it.
Once you've done your routing, you'll want to use some of the new Nav
cmds. For internal links, you can use Nav.pushUrl
to update the address bar (this does not tell the browser to change locations, it just adjusts the history and what is displayed in the address bar), and for external links, you can use Nav.loadUrl
to tell the browser to follow the link.
Please note that the old Navigation
module that provided simple hash based routing has now been deprecated, since the new work makes it redundant.
Happy routing!
Runtime re-write & Performance
TL;DR: Tyrian's start up is much faster. Code defensively when using methods like
getElementById
, i.e. make sure the element exists before you try and use it.
This version of Tyrian moves to Cats Effect 3.5 (still works with both IO
and ZIO
), which thanks to wizardry beyond mortal comprehension, has made Tyrian both lighter in terms of memory consumption and substantially faster in general.
Tyrian's tiny runtime has also been completely re-written to a more idiomatic form, all credit to @armanbilge for this accomplishment.
Taken together, performance in general is theoretically better, although most people won't notice under normal usage. The part that is noticeable is start up time and resource consumption stability: Tyrian apps are now much quicker off the mark than they used to be!
While this is a very welcome development, it does cause a new, high-quality problem. In situations where an app starts up and looks up an element that is dynamically created by the app itself, there is a good chance that the element will not be immediately ready anymore. This didn't used to be a problem because Tyrian was just a bit slower than the renderer, but there is now a suspicion that things were working more by coincidence than design.
Practically, this means we need to code defensively, as we all probably should have been doing anyway. Specifically that means that if you're going to look up an element by ID (for instance), that's fine, but don't just use it, check it exists first. If it doesn't exist, you'll need a simple retry mechanism.
Main method launcher
You can now embed a Tyrian app into a web page without needing to use JavaScript to call it's launch
method.
This is thanks to some excellent work by @stevechy, who has also provided an example of launching two apps at the same time, with data properties.
Here is how the example embeds the two apps, notice that data attributes are being used to supply flags too!
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Main Launcher Example</title>
<script type="module" src="./target/scala-3.2.2/main-launcher-fastopt.js"></script>
</head>
<body>
<h1>Main launcher example</h1>
<div data-tyrian-app="CounterApp" data-tyrian-flag-initial-counter="42"></div>
<div data-tyrian-app="ChatApp" data-tyrian-flag-initial-message="Hello"></div>
</body>
</html>
Better CSS property name generation
A very welcome usability improvement by @krined-dev. All CSS properties are now generated in both camel and kebab case.
For example, you can use CSS.`background-image`
, as you could previously, but now you can also use the more convenient: CSS.backgroundImage
.
What's Changed
- #193 CSS properties in camel-case by @krined-dev in #194
- Fix typo "Tyrian > Architecture & Patterns" by @corem in #196
- Frontend Routing by @davesmith00000 in #197
- Main method launcher #198 by @stevechy in #200
- Take 2: refactor the entire runtime instead ;) by @armanbilge in #171
New Contributors
- @krined-dev made their first contribution in #194
- @corem made their first contribution in #196
- @stevechy made their first contribution in #200
Full Changelog: v0.6.2...v0.7.0
0.6.2
Quick Summary
First off, Many thanks!
- To all those who have contributed to this release, either through code contributions, issues raised, or even just by showing up on our Discord server and asking questions!
- Additionally, a big thank you to our sponsors for helping keep the lights on! Your faith is appreciated. π
There are lots of background changes and fixes in this release, much of it invisible to the end users, such as yet another round of meaty improvements to the WebSocket
implementation, and updates to all the latest versions of Tyrian's various dependencies.
There are a few user facing highlights to mention however:
preventDefault
..by ...default!
In @daniel-ciocirlan's excellent Rite of Passage Rock the JVM course, he has to do some extra work in order to enable javascript's preventDefault
action on the onClick
event of a button he makes.
This has been a known issue for a while, and has now been corrected. Err... sorry @daniel-ciocirlan! π
By default, all events produced by attributes like onClick
, are now sent with preventDefault
, stopPropagation
, and stopImmediatePropagation
enabled by default. This makes sense for a Tyrian based SPA, because in general when you create a Msg
in Tyrian you just want it to move around Tyrian's internals, rather than have the browser do anything.
Having said that, you can also conveniently change these with some new handy syntax, as follows:
onClick((_: Event) => Msg.BeginCountdown).withPreventDefault(false) // Or usePreventDefault, or noPreventDefault
The same syntax pattern works for stopPropagation
and stopImmediatePropagation
too.
Tyrian's built-in Http command now uses fetch
instead of XHR
π
Tyrian's inbuilt Http client has finally received some much needed love, and now offers richer options, better errors, and a more modern implementation.
onChange()
The onChange
attribute now takes a function that allows you to access the value that changed. Previously it was following a more generic attribute archetype that, as it turns out, wasn't very useful!
Cmd.toTask
If you create a Cmd
, you can now convert it to the underlying task type (i.e. IO
or ZIO/Task
). The point of this is that if you want to do something with the runnable entity, like race three Http calls, it's much easier to do that with the task that with the Cmd.
Better support for property types
Previously only value
was being properly exposed as a property rather than an attribute, and this meant that in cases such as checked
and selected
, you would set the initial attribute but not modify the state, leading to strange and unexpected behaviour.
You can now do the following (using checked
as and example):
// set the attribute on page load
input(`type` := "checkbox", checked)
// set / don't set the attribute on page load
input(`type` := "checkbox", checked(true|false))
// set / don't set the property at any time
input(`type` := "checkbox", checked := true|false)
Please note that the value
property has been restricted to String
values as a consequence of this work. This is the accurate type. Previously it accepted more type in order to be 'helpful' but this had to be removed, and that's probably for the best.
What's Changed
- Add LocalRootProject to fix sourcemaps by @zetashift in #167
- Cmd: add Eq & Functor instances + law checks by @gvolpe in #168
- docs: networking by @gvolpe in #170
- mdoc: update to 2.3.7 (fixes gendocs) by @gvolpe in #172
- Use FS2
Channel
forWebSocket
by @armanbilge in #177 - onChange supplies value to msg function by @davesmith00000 in #181
- Remove redundant closing in WebSocket implementation by @armanbilge in #185
- Fixed #173: Added Cmd.toTask by @davesmith00000 in #182
- #127 Replace XHR with fetch by @JPonte in #183
- Issue/178/bug property types by @davesmith00000 in #187
- Fixed #110: Event.preventDefault support by @davesmith00000 in #188
- Upgrading dependencies by @davesmith00000 in #189
Full Changelog: v0.6.1...v0.6.2
0.6.1
What's Changed
- Remove use of NonFatal exception by @davesmith00000 in #150
- Add tags with raw HTML by @daniel-ciocirlan in #156
- Add sourcemaps flag for github paths to commonJsSettings by @zetashift in #158
- Improved Mouse & Keyboard events by @davesmith00000 in #161
- Fixed #159: Adds support for innerHtml syntax by @davesmith00000 in #162
- Websocket drops initial message by @gvolpe & @davesmith00000 in #163
- Upgrade CE3 to 3.4.4 and fs2 to 3.4.0 by @armanbilge & @davesmith00000 in #165
- Sub: add Eq & Functor instances + law checks by @gvolpe in #164
New Contributors
- @daniel-ciocirlan made their first contribution in #156
- @zetashift made their first contribution in #158
Full Changelog: v0.6.0...v0.6.1
0.6.0
Notable changes:
New Build Target Versions:
- Scala.js 1.11.0
- Scala 3.2.0
- Mill 0.10.7
ZIO 2.0 support added
Tyrian is fs2 and Cats Effect 3 all the way down, but thanks to the ZIO interop-cats library you can now use Tyrian with ZIO 2.0.
You will need to add the following to your build in the appropriate places. Please note that zio-interop-cats
is awaiting a release, but the snapshot version works just fine.
"io.indigoengine" %%% "tyrian-zio" % "0.6.0"
Global / resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
"dev.zio" %%% "zio-interop-cats" % "3.3.0+9-bd953aa9-SNAPSHOT"
You then set up your Tyrian project as normal, bring in:
import zio.*
import zio.interop.catz.*
...and use ZIO's Task
in place of CE's IO
.
Create subscriptions from fs2 streams!
A lovely little addition that allows you to use Sub.make(id: String, fs2.Stream[F, A])
to create subscriptions. As with all other Sub
s, the stream will activate as soon as it is in the subscriptions list, and will be cancelled when you take it out again. What could be simpler?
Other Improvements
- Allow
Styles
to be added to style attribute via := - Fixed #133: Added plainText Body helper
- Fixed #132: Added backticked 'for' attribute
- Fixed #135: Allow call to launch with an Element
- Fixed #143: New syntax to allow you to convert your effectful-thing in to a Cmd or your fs2 stream into a Sub with
.toCmd
and.toSub
respectively - Fixed #147: Added standard
animationFrameTick
Sub
(#148) which creates a message onrequestAnimationFrame
- Fixed #98: Generate Aria Attributes
Bug fixes
- Http.send: fix setting headers
- Fixed #131: Missing page elem causes infinite loop
What's Changed
- Http.send: fix setting headers by @chuwy in #120
- Allow Style to be added via := by @JPonte in #125
- Bumped Scala, sbt, Scala.js, CE3, fs2, scalajs-dom by @davesmith00000 in #129
- Improve launch behaviour by @davesmith00000 in #136
- Add support for ZIO 2.0 by @davesmith00000 in #134
- Add helper to make
Sub
fromfs2.Stream
by @armanbilge in #140 - Fixed #143: Extension syntax to make Cmds/Subs by @davesmith00000 in #144
- Fixed #147: Added standard animationFrameTick Sub by @davesmith00000 in #148
Full Changelog: v0.5.1...v0.6.0
0.5.1
A frantic week of people using version 0.5.0
resulted in a couple of release worthy bugs and some new features to boot!
Special thanks to @chuwy and @JPonte both for the work and the discussion. π
Highlights:
- Fixed task ordering defect
- Fixed problem where tasks were not being run concurrently
- Split updates from rendering for smoother visuals
What's Changed
- Added
Cmd.emitAfterDelay(msg, delay)
- Add OptionalChildren TagType by @JPonte in #108
- Fix Body constructors by @chuwy in #109
- Need for accurate speed by @davesmith00000 in #116
- Fixed #104: Add lowercase attribute variations by @davesmith00000 in #118
- Fixed #106: Dom blur/focus variations with no msg by @davesmith00000 in #119
New Contributors
Full Changelog: v0.5.0...v0.5.1