Skip to content

Releases: PurpleKingdomGames/tyrian

0.11.0

14 Apr 14:00
Compare
Choose a tag to compare

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

New Contributors

Full Changelog: v0.10.0...v0.11.0

0.10.0

24 Jan 00:31
Compare
Choose a tag to compare

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

Full Changelog: v0.9.0...v0.10.0

0.9.0

03 Jan 23:30
Compare
Choose a tag to compare

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 Cmds 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:

Without keys:
tyrian_without_key

With keys:
tyrian_with_key

0.8.0

26 Sep 20:39
Compare
Choose a tag to compare

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

New Contributors

Full Changelog: v0.7.1...v0.8.0

0.7.1

11 Jun 21:13
Compare
Choose a tag to compare

Release summary

This is a tiny little release that does two notable things:

  1. Moves Tyrian onto Scala 3.3.0, which is the LTS version.
  2. 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

Full Changelog: v0.7.0...v0.7.1

0.7.0

20 May 10:23
Compare
Choose a tag to compare

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:

  1. Providing a NoOp message (meaning 'no operation')
  2. Implementing the router function that does nothing, returning the NoOp.
  3. 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

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

02 Apr 21:20
Compare
Choose a tag to compare

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

Full Changelog: v0.6.1...v0.6.2

0.6.1

16 Jan 20:04
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v0.6.0...v0.6.1

0.6.0

25 Sep 13:57
Compare
Choose a tag to compare

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 Subs, 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 on requestAnimationFrame
  • Fixed #98: Generate Aria Attributes

Bug fixes

  • Http.send: fix setting headers
  • Fixed #131: Missing page elem causes infinite loop

What's Changed

Full Changelog: v0.5.1...v0.6.0

0.5.1

09 Jun 23:53
Compare
Choose a tag to compare

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

New Contributors

Full Changelog: v0.5.0...v0.5.1