Here it is! And for comparison, here’s the old version that used React. As intended, it looks the same! (except for the “swap to use big-endian” option, which I don’t think makes a lot of sense)
This is the latest project on my framework free kick – you can read previous entries here. This was the biggest project that I’ve converted from React to Web Components; the diff is here and the main code is the 855 lines of public/index.js.
The floating point to hex converter is the most popular page on my website (just ahead of my MLB Triple Crown tracker, which will start dropping off any day now) so making big changes to it is scary! Especially since I don’t really have any sort of reporting that would let me know if something was broken. I did port over the existing tests to something based on Open Web Components’ testing framework, which lets me actually create components in the DOM, click buttons, etc. But if you see anything wrong, please drop me a line 🙂
One of the fringe benefits of moving away from React was to get improved performance, more out of principle than any real need. But I did think that having a “pure HTML” page instead of pulling in React would make the page smaller and perform better. And, while it’s smaller, the performance results are more mixed: here’s the PageSpeed Insights for the new site compared with the old site. Unfortunately, I’m not an expert here; some things seem better and some seem worse. Oh well!
As this was my biggest Web Components project, and I wrote it without the help of LLMs (with one irritating exception; see below), I learned a lot:
- HTML attributes have to be lowercase – Web Components generally store their values in attributes, and at once point I was setting an attribute listed in the
observedAttributeslist, but the call toattributeChangedCallback()wasn’t happening. And you can’t set a breakpoint on a call that isn’t happening, and I couldn’t figure out what could be going wrong, as this was something I’d done in all my previous Web Components projects. I started feverishly reverting changes with commit titles like “flailing about” and “aaa what is happening”. Finally, after being stuck for hours I finally gave in and asked Claude, and while it misdiagnosed the issue it did literally mention “as a sidenote” that HTML attributes are lowercase and if they’re ever set in HTML (as opposed to Javascript) trying to use the mixed-case version would cause problems. And lo and behold, that fixed the problem and I learned a valuable lesson I’ll probably never forget! - I still still miss JSX – Not much to add over my previous thoughts, but I felt it even more this time because the components were more complicated and had more attributes. Since components were nested a bit, passing attribute values down was also more irritating. If I work on a more complicated project maybe I will finally look into something like WebJSX to make this easier.
- React uses
className, HTML usesclass– I’m impressed at my ability to keep getting this wrong. - Elements that get slotted (inserted in a
<slot>) still exist in their original DOM position – In a component you can include a<slot>placeholder, which is where child elements of the original component get put. But the HTML structure doesn’t actually change, so if you need to query the DOM to find the child elements they aren’t actually in the<slot>. In retrospect I think this makes some sense, but it confused me for a while!
Questions I didn’t get satisfactory answers to:
- Is there no way to set multiple attributes at the same time? Since my components have a lot of different attributes, there were a number of places that look like this, where the property setters are setting or removing attributes. Each time that happens the
attributeChangedCallback()gets called, which means everything gets updated, so this effectively causes 5 updates to happen, most with intermediate values that are thrown away. This is madness! I thought of two options to fix this, but they’re both pretty bad. Is there something I’m missing?- Only call
update()when the “last” attribute changes, which means callers would have to know the right order to set those attributes in, which is a terrible API. - Have a property that callers can set to pass a bunch of relevant attributes at once, separated by delimiters or something. This is possibly worse. Although I guess I could add a method on the class that does something like this, so callers could just call that method and wouldn’t have to know about the magic property. Hmm, this may be the best option.
- Only call
- How does JSDoc work in Visual Studio Code? Since I wanted to just write a plain JS file and avoid a build step, I skipped Typescript and was hoping to use JSDoc annotations to specify types. However, in all but the simplest cases I didn’t see any type errors flagged by Visual Studio Code, which was a bummer. It would be nice to have some sort of type checking while still writing plain JS!
- Is there a better way to reset an animation when it’s done? To get the satisfying yellow flash when a conversion is done, I use a CSS transition, and then add a class to an element to trigger it. But after the animation is done, I have to remove that class so that the next time I can add it back to trigger the animation, and the only way I could figure out to do that was to parse the CSS to get the animation duration (which a pretty cool thing to be able to do!) and remove the class after that time elapses. Not the worst thing in the world, but it does feel klunky; maybe there’s a better way?
But when all is said and done, it’s really nice to have a plain HTML and JS file that I can build and deploy by hitting “Save” in an editor. (well, OK, and pushing to GitHub. But still!) And it’s similarly nice to have a non-minified JS file that’s easy to inspect. While I’m a bit skeptical about using Web Components for larger projects because of the questions above, the immediate feedback and nice clean files are really satisfying, so I’ll be on the lookout for more projects that I can convert!
