Laminar & Scala.js Full Stack Demo & More
I built a full stack Laminar & Scala.js showcase, made some useful Vite plugins on the way, and more.
Laminar is a Scala.js library for building web application interfaces and managing UI state. Learn more in a few short examples or one long video.
As I'm trying to get more Laminar examples and learning materials out there, I wanted a better platform for hosting the examples. Both this website, which used mdoc, and my old laminar-examples repo, are not great for authoring examples, and are certainly not great for people who are trying to run the examples, or transfer them into a more realistic, full stack app environment.
I also wanted to make starting with Scala.js even easier, to reduce the amount of rediscovery every new developer needs to do when it comes to sbt and vite build config, Scala.js facades, shared code & datastructures, JSON data sharing, URL routing, packaging for production, etc.
And so, say hello to ✨ demo.laminar.dev ✨. Here's what it is:
- A set of Laminar examples, from basic hello world including to a full stack weather app.
- A fully working client + server, dev + prod setup, that you can immediately run, start experimenting with, and deploy to the cloud for free, without worrying about build or server setup.
- A new documentation system letting me write long narratives like this while showing relevant snippets of source code of the very application that the user is looking at, with links to full sources on Github.
- Some opinionated ramblings that badly need further editing, that I nevertheless hope will be useful to backend developers venturing into the frontend.
For more details, see the project's Features listing.
New Vite Plugins for Scala.js
I wanted to replicate a good CSS organization strategy from the Javascript world, where you have one CSS file per component, and you just import "./ComponentName.css"
from ComponentName.js
. Turns out, we can't readily do this particular pattern from Scala.js, because:
Scala.js can't import resources for their side effects only, it needs to assign the imported object to a variable, which Vite, the module bundler, disagrees with when it comes to CSS.
Scala.js can't import resources from relative paths, or to be more precise, the relative paths will resolve relative to
scalaJSOutputDirectory
, instead of resolving relative to the source file in which the imports appear, as is the case in JS.
So, I wrote two Vite plugins to work around these limitations.
First, the import-side-effect plugin lets you import CSS resources in Vite without double-loading in production builds, and without warnings. It also provides an optional more concise syntax for such imports.
Then, the glob-resolver plugin lets you import relative paths, or to be more precise, it lets you import a file in your project matching a glob pattern. It throws a graceful error if it finds more than one matching file, so you can just say JSImport("@find/**/ComponentName.css")
, and it will find ComponentName.css
wherever it is in your project, and if there happens to be more than one such file, you can just make the glob pattern more specific to disambiguate, e.g. JSImport("@find/**/foo/ComponentName.css")
.
For what I want to do, I need both of these plugins working together, so I say JSImportSideEffect("@find/**/ComponentName.css")
to get a nice clean import "/absolute/path/to/ComponentName.css"
in the JS output, which Vite is happy to oblige. The source maps are preserved, everything works both in dev and prod builds.
Currently, these plugins live in this Laminar demo project as source files that you can copy-paste into your own project, but if there's enough interest, I could publish them to npm, or if the Scala.js team sees fit, they could be included into the official scalajs-vite-plugin.
Update: I've published my vite plugins to npm, and updated the import-side-effect plugin with a new (better) approach.
Forward-Looking Statements
This new demo project is a solid MVP, but it could be so much more.
I want to make a comprehensive collection of Laminar mini-tutorials, showcasing various features, techniques, patterns, etc. Some of this is technically covered by our extensive documentation, but not the more complex stuff (such as nested state management). And people have been asking me to create more bite-sized/example/tutorial-style learning materials over and over, since forever. I do what I can with the time that I have, but I'm hoping that with this new platform, adding more examples over time will be easier.
I also want to create more Scala.js <> JS and Scala.js <> JVM integration examples. Everything from making facades for more JS libraries manually to show how it's done, to showing patterns for sanely dealing with complex types that are incompatible between JS and JVM, such as DateTime.
Last but not least, I recently played around with Shoelace web components, and I must say that I like them quite a bit more than SAP UI5. Both libraries are solid, respectable, and well documented, but Shoelace has a nicer (IMO) modern-web look to it, and crucially, is much more flexible when it comes to theming and customization. I made Laminar bindings for a few Shoelace components that you can see at the link above, and I think it would be great to cover their entire collection of components, and publish it for people to use. Personally I like to create my own bespoke components because I'm very particular about the UIs that I build, but I understand that most people and most business applications will be better off with high-quality premade components, like what Shoelace offers.
Sustainability Report
My continued work on Laminar and Laminar things is made possible by 21 sponsors. Their support lets me spend more time doing what I love – designing and building useful tools for people to enjoy – and for that, I am eternally grateful.
Laminar sponsorships have been gradually growing for a long time, thanks both to the new people and companies coming in (Cheers Aurinko! HeartAI!), and the same diehards who've been with us since day one (Iurii! Eason! Anton!, Binh!, Paul-Henri!), and everyone in between.
Recently, HeartAI have upgraded their Laminar sponsorship to an extent that I had to create a new tier for such a level of support. Frankly, this one event is what prompted me to create this new Laminar demo project. I have wanted to do it for a long time, but up until now it has always been an unattainable nice-to-have, buried in the ever-growing todo list. But as my support base has grown to a new level, so has my ability to work on things that will help grow our community even further.
DIAMOND sponsor:
GOLD sponsors: