Categories
Articles Thoughts

JS private class fields considered harmful

Reading Time: 2 minutes

Today I mourn. What am I mourning? Encapsulation. At least in my projects.

As a library author, I’ve decided to avoid private class fields from now on and gradually refactor them out of my existing libraries.

Why did I make such a drastic decision?

It all started a few days ago, when I was building a Vue 3 app that used Color.js Color objects. For context, Vue 3 uses proxies to implement its reactivity system, just like Mavo did back in 2016 (the first one to do so as far as I’m aware). I was getting several errors and upon tracking them down I had a very sad realization: instances of classes that use private fields cannot be proxied.

I will let that sink in for a bit. Private fields, proxies, pick one, you can’t have both. Here is a reduced testcase illustrating the problem.

Basically, because a Proxy creates a different object, it breaks both strict equality (obj1 === obj2), as well as private properties. MDN even has a whole section on this. Unfortunately, the workaround proposed is no help when proxies are used to implement reactivity, so when I tried to report this as a Vue bug, it was (rightly) closed as wontfix. It would not be possible to fix this in Mavo either, for the same reason.

I joined TC39 fairly recently, so I was not aware of the background when proxies or private class fields were designed. Several fellow TC39 members filled me in on the discussions from back then. A lot of the background is in this super long thread, some interesting tl;drs as replies to my tweet:

After a lot of back and forth, I decided I cannot justify using private properties going forwards. The tradeoff is simply not worth it. There is no real workaround for proxy-ability, whereas for private fields there is always private-by-convention. Does it suck? Absolutely. However, a sucky workaround is better than a nonexistent workaround.

Also, I control the internal implementation of my classes, whereas proxying happens by other parties. As a library user, it must be incredibly confusing to have to deal with errors about access to private fields in a class you did not write.

This was one of the saddest PRs I have ever written https://github.com/LeaVerou/color.js/pull/306. It feels like such a huge step backwards. I’ve waited years for private fields to be supported everywhere and relished when they got there. I was among the first library authors to adopt them in library code, before a lot of tooling even parsed them properly (and some still don’t).

Sure, they were kind of annoying to use (you usually want protected, i.e. visible to subclasses, not actually private), but they were better than nothing. I was not joking in the first paragraph; I am literally grieving.

I may still use private fields on a case by case basis, where I cannot imagine objects being proxied being very useful, for example in web components. But from now on I will not reach to them without thought, like I have been for the past couple of years.

Categories
Original Thoughts

On ratings and meters

Reading Time: 3 minutes

I always thought that the semantically appropriate way to represent a rating (e.g. a star rating) is a <meter> element. They essentially convey the same type of information, the star rating is just a different presentation.

An example of a star rating widget, from Amazon

However, trying to style a <meter> element to look like a star rating is …tricky at best. Not to mention that this approach won’t even work in Shadow trees (unless you include the CSS in every single shadow tree).

So, I set out to create a proper web component for star ratings. The first conundrum was, how does this relate to a <meter> element?

  • Option 1: Should it extend <meter> using builtin extends?
  • Option 2: Should it use a web component with a <meter> in Shadow DOM?
  • Option 3: Should it be an entirely separate web component that just uses a meter ARIA Role and related ARIA attributes?
Categories
Releases Speaking

Introducing Rety: live coding, without the stress

Reading Time: 3 minutes

I recently spoke at CSS Day in Amsterdam. It was only my second f2f talk after the pandemic. It went down really well, both in person, and recently that the video was released:

Here is a sample of tweets about it that made me particularly warm and fuzzy inside:

There’s a lot more where these came from too.

This was not just my second post-pandemic talk, but my first talk using Rety, which is what this post is about.

As you may know, I love live coding as a teaching tool, and over the years it has become part of my trademark speaking style.

When combined with some kind of interactive preview, it allows the speaker to demonstrate not only the final state of a coding snippet, but also how you get there, and what the intermediate results are. Live coding is to programming what a blackboard is to math or physics.

But it does create a unique challenge: My live coded slides don’t make sense without me. This may be acceptable for a conference talk, which is usually recorded, but not in other contexts, such as teaching a university course, where all instructors need to be able to teach all lectures, and students need to be able to quickly refer to examples shown.

Back in the fall of 2021, when we were preparing for the second iteration of our course, Design for the Web: Languages and User Interfaces, this came up as a pressing issue. The current state of the course required me to be there to teach my lectures, and this may well be the last year I teach it, since I’m finishing up my PhD soon.

I didn’t want to completely remove live coding from my slides, as I truly believe it is the perfect implementation of the “show, don’t tell” teaching adage for certain things, so I thought instead: what if I could record my live coding, and make it replayable?

Doing so manually seemed like cruel and unusual punishment. And thus, Rety was born (pronounced like the “rety” in “retype”).

While originally the plan was for me to still live code, and have the Rety functionality there for students and future instructors, I ended up using it during my own lectures as well, as I concluded that a well crafted Rety script was strictly superior to me doing the live coding:

  • Same progressive development as a live demo
  • It still affords unplanned demonstrations (e.g. to answer a question), since Rety still works with the same editors, and I could always pause it and take over if needed.
  • I could record myself and edit the script to maximize education value and minimize typos, delays, fumbling etc.
  • People can consume typed text far faster than people can type text. This is why most video tutorials speed up the typing. With Rety, typing speed is adjustable, and doesn’t need to match mine.

After test driving it for our course the entire spring 2022 semester, it went through the ultimate test in June 2022: I used it for my CSSDay conference talk. You can watch the talk here (first live demo at 7:15).

Right now Rety is just a set of two classes: Recorder and Replayer, which are used entirely independently. The exact UI is left up to the Rety user. E.g. to use it in my slides, I integrated it with the Live Demo plugin of Inspire.js (it is automatically included if a <script class="demo-script" type="application/json"> is found in a live demo slide).

The library could use more docs and some tests and I have doubts about the API, but I figured I should release it it earlier rather than later (it’s already been sitting in a repo for 7 months). After all, what best time to release it than when the first Rety talk is still making the rounds?

My vision is to ultimately evolve and standardize the Rety script format, so that it can be used to describe a coding interaction across a variety of tools. There are so many possibilities!

  • Wouldn’t it be cool if CodePen and similar playgrounds supported embedding a Rety script into a pen?
  • What if you could store Rety scripts in a repo and editors like VS Code recognized them and let you replay them?

Enjoy: Rety

Categories
Original Releases

Releasing Color.js: A library that takes color seriously

Reading Time: 3 minutes

Related: Chris’ blog post for the release of Color.js

This post has been long overdue: Chris and I started working on Color.js in 2020, over 2 years ago! It was shortly after I had finished the Color lecture for the class I was teaching at MIT and I was appalled by the lack of color libraries that did the things I needed for the demos in my slides. I asked Chris, “Hey, what if we make a Color library? You will bring your Color Science knowledge and I will bring my JS and API design knowledge. Wouldn’t this be the coolest color library ever?”. There was also a fair bit of discussion in the CSS WG about a native Color object for the Web Platform, and we needed to play around with JS for a while before we could work on an API that would be baked into browsers.

We had a prototype ready in a few months and presented it to the CSS WG. People loved it and some started using it despite it not being “officially” released. There was even a library that used Color.js as a dependency!

Once we got some experience from this usage, we worked on a draft specification for a Color API for the Web. In July 2021 we presented it again in a CSS WG Color breakout and everyone agreed to incubate it in WICG, where it lives now.

Why can’t we just standardize the API in Color.js? While one is influenced by the other, a Web Platform API has different constraints and needs to follow more restricted design principles compared to a JS library, which can be more flexible. E.g. exotic properties (things like color.lch.l) are very common in JS libraries, but are now considered an antipattern in Web Platform APIs.

Work on Color.js as well as the Color API continued, on and off as time permitted, but no release. There were always things to do and bugs to fix before more eyes would look at it. Because eyes were looking at it anyway, we even slapped a big fat warning on the homepage:

Eventually a few days ago, I discovered that the Color.js package we had published on npm somehow has over 6000 downloads per week, nearly all of them direct. I would not bat an eyelid at those numbers if we had released Color.js into the wild, but for a library we actively avoided mentioning to anyone outside of standards groups, it was rather odd.

How did this happen? Maybe it was the HTTP 203 episode that mentioned it in passing? Regardless, it gave us hope that it’s filling a very real need in the pretty crowded space of color manipulation libraries and it gave us a push to finally get it out there.

So here we are, releasing Color.js into the wild. So what’s cool about it?

  • Completely color space agnostic, each Color object just has a reference to a color space, a list of coordinates,, and optionally an alpha.
  • Supports a large variety of color spaces including all color spaces from CSS Color 4, as well as the unofficial CSS Color HDR draft.
  • Supports interpolation as defined in CSS Color 4
  • Doesn’t skimp on color science: does actual gamut mapping instead of naïve clipping, and actual chromatic adaptation when converting between color spaces with different white points.
  • Multiple DeltaE methods for calculating color difference (2000, CMC, 76, Jz, OK etc)
  • The library itself is written to be very modular and ESM-first (with CJS and IIFE bundles) and provides a tree-shakeable API as well.

Enjoy: Color.js

There is also an entire (buggy, but usable) script in the website for realtime editable color demos that we call “Color Notebook”. It looks like this:

And you can create and share your own documents with live Color.js demos. You log in with GitHub and the app saves in GitHub Gists.

Color spaces presently supported by Color.js
Categories
Articles

Mass function overloading: why and how?

Reading Time: 4 minutes

One of the things I’ve been doing for the past few months (on and off—more off than on TBH) is rewriting Bliss to use ESM 1. Since Bliss v1 was not using a modular architecture at all, this introduced some interesting challenges.

Categories
Tutorials

Writable getters

Reading Time: 5 minutes
Setters removing themselves are reminiscent of Ouroboros, the serpent eating its own tail, an ancient symbol. Media credit

A pattern that has come up a few times in my code is the following: an object has a property which defaults to an expression based on its other properties unless it’s explicitly set, in which case it functions like a normal property. Essentially, the expression functions as a default value.

Categories
Tips

Introspecting CSS via the CSS OM: Getting supported properties, shorthands, longhands

Reading Time: 4 minutes

For some of the statistics we are going to study for this year’s Web Almanac we may end up needing a list of CSS shorthands and their longhands. Now this is typically done by maintaining a data structure by hand or guessing based on property name structure. But I knew that if we were going to do it by hand, it’s very easy to miss a few of the less popular ones, and the naming rule where shorthands are a prefix of their longhands has failed to get standardized and now has even more exceptions than it used to. And even if we do an incredibly thorough job, next year the data structure will be inaccurate, because CSS and its implementations evolve fast. The browser knows what the shorthands are, surely we should be able to get the information from it …right? Then we could use it directly if this is a client-side library, or in the case of the Almanac, where code needs to be fast because it will run on millions of websites, paste the precomputed result into whatever script we run.

Categories
Tips

Import non-ESM libraries in ES Modules, with client-side vanilla JS

Reading Time: 5 minutes

In case you haven’t heard, ECMAScript modules (ESM) are now supported everywhere!

While I do have some gripes with them, it’s too late for any of these things to change, so I’m embracing the good parts and have cautiously started using them in new projects. I do quite like that I can just use import statements and dynamic import() for dependencies with URLs right from my JS, without module loaders, extra <script> tags in my HTML, or hacks with dynamic <script> tags and load events (in fact, Bliss has had a helper for this very thing that I’ve used extensively in older projects). I love that I don’t need any libraries for this, and I can use it client-side, anywhere, even in my codepens.

Once you start using ESM, you realize that most libraries out there are not written in ESM, nor do they include ESM builds. Many are still using globals, and those that target Node.js use CommonJS (CJS). What can we do in that case? Unfortunately, ES Modules are not really designed with any import (pun intended) mechanism for these syntaxes, but, there are some strategies we could employ.

Categories
Articles

Refactoring optional chaining into a large codebase: lessons learned

Reading Time: 6 minutes

Chinese translation by Coink Wang

Now that optional chaining is supported across the board, I decided to finally refactor Mavo to use it (yes, yes, we do provide a transpiled version as well for older browsers, settle down). This is a moment I have been waiting for a long time, as I think optional chaining is the single most substantial JS syntax improvement since arrow functions and template strings. Yes, I think it’s more significant than async/await, just because of the mere frequency of code it improves. Property access is literally everywhere.

Categories
Tips Tutorials

Hybrid positioning with CSS variables and max()

Reading Time: 4 minutes
Notice how the navigation on the left behaves wrt scrolling: It’s like absolute at first that becomes fixed once the header scrolls out of the viewport.

One of my side projects these days is a color space agnostic color conversion & manipulation library, which I’m developing together with my husband, Chris Lilley (you can see a sneak peek of its docs above). He brings his color science expertise to the table, and I bring my JS & API design experience, so it’s a great match and I’m really excited about it! (if you’re serious about color and you’re building a tool or demo that would benefit from it contact me, we need as much early feedback on the API as we can get! )

For the documentation, I wanted to have the page navigation on the side (when there is enough space), right under the header when scrolled all the way to the top, but I wanted it to scroll with the page (as if it was absolutely positioned) until the header is out of view, and then stay at the top for the rest of the scrolling (as if it used fixed positioning).