Elf M. Sternberg

Full Stack Web Developer

Where one teaches, two learn.

Blog

A quarter-century of code experience

Professionalism

MOUNTAINS AND MOLEHILLS: FIXING TREE-SITTER-SCSS

In my last adventure, I complained mightily about how a tiny bug in tree-sitter-scss was all that stood between me and my next heroic work accomplishment. Although I didn't mention it at the time, I had little faith that my submitted issue would be addressed anytime soon, since when looking at the tree-sitter grammar repository I could see that the SCSS parser had last been updated in early April of 2024 and had been idle for eight months.

I took matters into my own hands.

Continue Reading

Professionalism

WHEN YOU CAN SEE THE PROMISED LAND, BUT YOU CAN'T GET THERE FROM HERE

Update: Contrary to my whining down below, I spent the weekend playing with tree-sitter, found the bug, and have submitted a PR.

When you've got twenty years of developer experience, there is one source of frustration at work that can be greater than any other: when you can see the promised land but you know you can't get there and, worst of all, the wall between you and there is barely ankle high.

I am not, by any stretch of the imagination, an expert on parsers and scanners. I've written a few DSLs in my time and always leaned on something commonly available, usually either s-expressions, a readily available configuration language like YAML or TOML, or just a cut-down version of the host language with specificity for my DSL needs.

So let's talk about Shatterfly.

Continue Reading

Webdriverio

TIP: DELETE LIT'S HIDDEN CACHE WHEN USING WEBDRIVERIO TO TEST LIT-ELEMENT COMPONENTS.

I recently had a horrible experience where I could not get Lit to run the same test twice using the WebdriverIO Component Testing Guidelines for Lit.

The test is simple: Before each test, render the component, click on it, see if its internal state is what I expect, and then remove the component at the end of the test. I expected that if I ran the exact same test twice, it would work (or fail) twice.

It turns out Lit is being passive-aggressive about caching what you've done, won't re-render cached content preferring to re-connect it instead, and won't help you figure out that it doesn't exist anymore and can't be re-connected.

Continue Reading

Async rust

ASYNC RUST: SERVER-SIDE EVENTS WITH A REMOTE HEARTBEAT

This project took me forever to figure out, so I'm going to document it as much as possible. What I've been trying to get to is a simple, Async Rust codebase that would allow me to plug in web-based server-side events or web socket events and then send them over to one or more clients as needed. A lot of this code comes from a variety of examples that I stitched together, and getting them to work well was a headache, so now you get to share in it.

Continue Reading

Chat

PEOPLE SERIOUSLY USE CHATGPT TO WRITE CODE? I DON'T BELIEVE IT.

Today's experience with ChatGPT-4o convinces me that this codebase is absolutely not ready for use, and programmers depending on it will be in deep, deep trouble. I opened a file today with less than ten lines of code in a clear and well-documented pattern, and asked ChatGPT to help me finish it. It did not go well.

Continue Reading

Society

NORMAL PEOPLE HAVE WEIRD IDEAS ABOUT COMPUTERS

Normal people have very weird ideas about how computers work under the covers.

Omaha and I visit a cafe near our home nearly every day. Today, I got into a weird conversation with someone there about computers and, of all things, lists. I said a list was one dimensional, and she got angry at me. "A list is two dimensional. It goes down the paper and across the paper." I objected that that's not how it works inside a computer; it's just a single line, from beginning to end, of numbers. A list is just a line of numbers, in pairs: the address where to find each item on the list, and how much to read in until you've seen the whole item. The items themselves are somewhere else along the line. There's no multi-dimensionality at all to memory (or hard drive) accessing; it's just one dimension.

"But that's still two dimensional, right?" she insisted. "You have an address for each item, like, going down the page, but each item goes across it." I said that was an artifact of human interpretation; computer addressing was a single number, one for each place in RAM or on a disk.

She refused to believe that.

Continue Reading

Web components

ASYNCHRONOUS DEPENDENCY INJECTION AND PENDING EVENT MANAGEMENT WITH WEB COMPONENTS

The DevFest Nantes 2019 presentation of Building Complex Applications with Web Components is probably one of the most important to Lit developers; everyone references Justin Fagnani's presentation of how to do routing, lazy loading, and context management. The presentation includes a section on dependency injection: how an object can request its dependencies from other components higher up in the DOM tree.

One of the problems I have with the code as presented is that it fails to handle the case where the dependency supplier must perform some asynchronous work before sending the dependency to the requester. I'll show you how to extend the example to handle that, and what capabilities that extension gives you.

Continue Reading

Web components

DON'T USE CUSTOMEVENT IN JAVASCRIPT. INHERIT FROM EVENT INSTEAD.

It's commonplace in Web Components to use custom events. The custom event type is an inheritor of the Event class, but it includes a new field, detail: any, that allows you to attach data to the Event, which is useful for passing data up to the listener, right?

Wrong. It's a trap. You could, theoretically, inherit from CustomEvent and narrow the content of detail to what you specifically want, and I've seen lots of code where people do exactly that.

But there's a better way: just inherit from Event. Skip the CustomEvent class and just create your own events. I'll show you how.

Continue Reading

Esbuild

FROM 50 SECONDS TO LESS THAN 1: HOW WE IMPROVED BUILD SPEED AT AUTHENTIK

At authentik, we have six individual applications for supporting our operations. Users have their traditional "here are you accesses" list; Administrators have the AdminUI; our actual SSO handling is done with a special app named "Flow," plus we have extra apps for when you're not logged in, when you're waiting to get logged in, and when you just want to interact with the public API.

Our build tool of choice was Rollup. Rollup is faster than Webpack, but more limited. And still, with that, our modestly large (~50K lines) application repository took 50 seconds to build on a modern developer's laptop. 50 seconds is a terrible delay; it's just enough for your typical ADHD developer like myself to wander off and think about something else. Given that we're a scrappy start-up, it's also a terrible delay when demoing the product for potential customers and showing them how easy it is to make changes.

I decided it had to go faster. This is how we did it, getting our build time down from 50 seconds to less than one second.

Continue Reading

Lit

MANAGING MULTIPLE CONTEXTS IN LIT USING REACTIVE CONTROLLERS

This blog post teaches an elegant way to isolate a complex Lit ContextProvider object in a ReactiveController, so you can compose it into multiple applications, and you can compose multiple different contexts into one application, without duplicate code or visual clutter. We tap into the host's lifecycle to correctly manage creating and populating and providing event listeners to handle context update requests.

It also shows how to write a Mixin to access that context without having to spam your entire app with context changes or force your developers to hand-write consumer code.

As a bonus, it includes both an elegant way to declare a base class with multiple mixins, and a nifty trick for creating truly private fieldnames without the Private Fieldname syntax, because the latter has a known performance problem.

Continue Reading

Programming

UNDERSTANDING CONTINUATIONS: JAVASCRIPT TRY/CATCH AND RUST RESULT

"You are responsible for explicitly understanding the rest of the program." A sentence like this appears in every tutorial explaining first-class continuations, a programming language construct that appears in Lisp, Scheme, and Haskell, and if you look at people discussing them they find such constructs "scary."

One of the things I did many, many moons ago was write a couple of small Lisp interpreters. (It was long enough ago that I'm sorry to say they were written in CoffeeScript.) One thing that I did finally understand while building these was how continuations could be used to replace almost all flow control, that if, while, and even function calls were all just specialized versions of continuations.

The problem with continuations is that they do look scary. But while watching Alexis King's awesome presentation Demystifying Delimited Continuations, I'm glad to say that I finally wrapped my head around the notation for continuations in a way that clarifies that terrifying sentence you find in every explanation of continuations, "A continuation is the code you write that will handle the rest of the program."

Continue Reading

Monorepo

MONOREPOS WITH MICROSOFT LAGE: LEARNINGS THIS WEEK

I recently chose Lage to help refactor a monolith web application into a monorepo. Here's what I learned.

In my day job, I'm responsible for a fairly large web application of about 52,600 lines of TypeScript. In reality, that one web application is four independent major single-page applications and two minor ones, all of which rest on a classic collection of component library, server interface, and localization. It's getting a bit unwieldy to work in, and any edit cycle now takes about 25 seconds to rebuild the whole thing while working on it.

That's just a little too long in the era of hot module reloading. Plus it's hard to know where the boundaries are, and the guy I inherited it from cut a few corners here and there, stealing useful code across the hierarchy rather than re-arranging the hierarchy. I managed to disentangle the first-level dependencies a few months ago, so that no SPA is dependent upon something within another, and now the time has come to break it up into multiple workspaces. After looking through the list of possible tools, I picked Lage.

Continue Reading

Javascript

NOTIFICATION WEB COMPONENT WITH INTRINSIC SCALING

I set out to reproduce the effect from "The CSS Hack You Need To Know", which is all about using the attr() CSS function to extract an attribute's value from the tag being styled so you can apply it to a ::after entry that will display the attribute. The idea is that one could then set:

<div class="notification-bell-container" current-count=5><svg ...></svg> </div>

And get back something that looks like this:

Image of the Bell Icon

As I was playing with the source code, I realized that it had one major shortcoming: it didn't scale well. For every different size, if you wanted the notification circle to be even approximately in the right place you would need to hand-code the location of the counter and have breakpoint-oriented versions for phone, tablet, desktop, extra-wide, and ten-foot displays.

Can we do better than that? With web components we can do anything.

Continue Reading

Programming

AND THEN IT ENDS: THE IMPORTANCE OF UNDERSTANDING SUBROUTINES IN MODERN WEB DEVELOPMENT

One of the most important lessons I've ever learned about Javascript is that no Javascript application is, by itself, actually a single program. Every Javascript application of even modest size is several different programs standing together in a trench coat and trying very hard to look like a single thing.

Let's look at the single most simple web application, the classic "push a button to increment a counter" that is the centerpiece of every "introduction to React / Vue / Angular / Lit / Svelte / etc." blogpost you've ever read. This application is just a button and a number, and every time you click the button the number increments. And let's talk about how it's very different from classical programming.

Continue Reading

Programming

TABLE DRIVEN WEB DEVELOPMENT

I've occasionally made reference, in the course of my blogging about Web Component development (and this applies to React as well), to "table driven development," and I had an opportunity to explain it in more detail this week. Table Driven Development is nothing more than identifying what is the minimum amount of syntax you need to express the data you use in your web page?

Let's start with a real-life example. This is a tab-based control from the Patternfly library, taken from a real project:

Continue Reading

Programming

MOST CONVERSATION ABOUT ABSTRACTION IS REALLY ABOUT LEGIBILITY

In my previous post, I talked about how using a CSS library cluttered the semantics of what a web component does, and after another week of living in this codebase I realized that there's a conversation to be had about the difference between abstraction and legibility. Because most of the time when we're talking about abstraction? We're actually talking about legibility. For example, I recently read the introduction to an OCaml textbook that said, "You will improve at abstraction, which is the practice of avoiding repetition by factoring out commonality."

That is absolutely not abstraction. That is legibility.

To quote James Koppel, an abstraction is an encapsulation of an idea, such as "sanitized" or "authorized" or "connected to a server," and locking the details behind an abstraction barrier so than not only don't you have to worry about the details, you shouldn't. That's not what my previous example did. Instead, it made the code's purpose and function legible.

So let's talk about legibility with another example.

Continue Reading

Web components

WEB COMPONENTS AND CSS LIBRARIES: AN AWKWARD FIT

In my new job, I've been working a lot with an existing codebase that uses web components (via Lit) for the HTML and Patternfly for the CSS, and I've discovered that Patternfly, as well as other CSS Libraries such as Tailwind or Bootstrap, are awkward fits for developing web components. You usually end up importing too much per component or sacrificing code readability on the altar of code size.

Let me give you an example:

Continue Reading