Every time I start a new project, I organize the code I’m looking at into three types, or categories if you like. And I think these types can be applied to any codebase, any language, any technology or open source project. Whether I’m writing HTML or CSS or building a React component, thinking about these different categories has helped me figure out what to refactor and prioritize, and what to leave alone for now.
Those categories: Boring Code, Salt Mine Code, and Radioactive Code.
Let me explain.
Boring Code
Boring code is when it makes perfect sense when you read it. There's no need to refactor it, and it performs its function in a way that doesn’t make you want to throw yourself into a river. Boring code is good code. It doesn’t do a kick-flip and it’s not trying to impress you. You can use it without having to write even more code or engineer hacks on top of it. Boring code does exactly what it says on the tin and never causes any surprises.
This function makes sense, this prop is clearly named, this React component is straightforward. There are no loops within loops, no mental gymnastics required here.
However, boring code is near impossible to write because our understanding of it is almost always incomplete when we start tackling a problem. Just look at how many considerations can go into a styling a simple paragraph for contrast. To write boring code, we must be diligent, we must endlessly refactor, and we must care for the codebase beyond a paycheck at the end of the month.
Boring code is good because boring code is kind.
Salt Mine Code
This is the type of code that’s bonkers and makes not a lick of sense. It’s the sort of code that we can barely read but it’s buried so deep in the codebase that it’s near impossible to change anyway. However! It’s not leaking into other parts of our code, so we can mostly ignore it. It might not be pretty, and we probably don’t want to ever look at it so long as we live, but it’s not actively causing any damage.
It’s this type of code that we can mostly forget about,. It's the type of code that is dangerous if opened up and tampered with, but for now, everything is okay.
The trouble is buried deep.
Radioactive Code
Radioactive code is the real problem at the heart of every engineering team. It’s the let’s-not-go-to-work-today sort of code. It’s the stuff that is not only bad but is actively poisoning our codebase and making everything worse over time. Imagine a codebase as a nuclear reactor; radioactive code is the stuff that’s breached the container and is now leaking into every part of our codebase.
An example? For us at Gusto and on the design systems team, I would consider our form components to be radioactive. Each component causes more problems because we can never use the component as is; we have to hack it to get what we want. Each time anyone uses this code they have to write even more code on top of it, making things worse over time, and it encourages everyone on the team to do the same.
In our design system, when we want to add a class name to the div that wraps a form element, we must use the formFieldClass prop in one component, and wrapperClass in another. There is a propType called isDefaultLayout and everyone sets it to false and writes custom CSS classes on top of it. In other words, not only does radioactive code make it hard for us to understand all this nonsense code, it makes it increasingly difficult to understand other parts of the codebase, too. Because the file we’re looking at right now has dependencies on eight different things that we cannot see. The result of removing this radioactive code means changing everything else that depends upon it.
In other words, radioactive code — like our form components — makes it impossible for the codebase to be trusted.
Radioactive code is not only bad for us and our codebase, but it is also bad for our team. It encourages bad habits, cruelty in Slack threads, not to mention that it causes friction between team members that is hard to measure. Radioactive code also encourages other teams in a company to go rogue and introduce new technologies into a codebase when the problem of radioactive code is not the tech itself. Anyone can write this type of code, regardless of the language or the system or the linting when they’re not paying enough attention to the problem. Or when they’re trying to be a little too smart. Or when they’re trying to impress someone.
How do we fix radioactive code? Well, we must draw a circle around it and contain the madness that’s leaking into other parts of the codebase. Then we must do something utterly heroic: we must make it boring.
There's been news about Chrome freezing their User-Agent string (and all other major browsers are on board). That means they'll still have a User-Agent (UA) string (that comes across in headers and is available in JavaScript as navigator.userAgent. By freezing it, it will be less useful over time in detecting the browser/platform/version, although the quoted reason for doing it is more about privacy and stopping fingerprinting rather than developer concerns.
In the front-end world, the general advice is: you shouldn't be doing UA sniffing. The main problem is that so many sites get it wrong, and the changes they make with that information ends up hurting more than it helps. And the general advice for avoiding it is: you should test based on the reality of what you are trying to do instead.
Are you trying to test if a browser supports a particular feature? Then test for that feature, rather than the abstracted idea of a particular browser that is supposed to support that feature.
In JavaScript, sometimes features are very easy to test because you test for the presence of their APIs:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
console.warn("Geolocation not supported");
}
That is exposed in JavaScript via an API that returns a boolean answer:
CSS.supports("display: flex");
Not everything on the web platform is this easy to test, but it's generally possible without doing UA sniffing. If you're in a difficult position, it's always worth checking to see if Modernizr has a test for it, which is kinda the gold-standard of feature testing as chances are it has been battle-tested and has dealt with edge cases in a way you might not foresee. If you actually use the library, it gives you clean logical breaks:
What if you just really need to know the browser type, platform, and version? Well, apparently that information is still possible to get, via a new thing called User-Agent Client Hints (UA-CH).
Wanna know the platform? You set a header on the request called Sec-CH-Platform and theoretically, you'll get that information back in the response. You have to essentially ask for it, which is apparently enough to prevent the problematic privacy fingerprinting stuff. It appears there are headers like Sec-CH-Mobile for mobile too, which is a little curious. Who is deciding what a "mobile" device is? What decisions are we expected to make with that?
Knowing information about the browser, platform and version at the server level if often desirable as well (sending different code in different situations) — just as much as it is client-side, but without the benefit of being able to do tests. Presumably, the frozen UA strings will be useful for long enough that server-side situations can port over to using UA-CH.
Professionally, I’ve been hands on with the mobile web space and seen it develop for more than 15 years and I know that many, big and small, websites rely on device detection based on the User-Agent header. From Google's perspective it may seem easy to switch to the alternative UA-CH, but this is where the team pushing this change doesn’t understand the impact:
Functionality based on device detection is critical, widespread and not only in front end code. Huge software systems with backend code rely on device detection, as well as entire infrastructure stacks.
In my most major codebase, we do a smidge of server-side UA detection. We use a Rails gem called Browser that exposes UA-derived info in a nice API. I can write:
if browser.safari?
end
We also expose information from that gem on the client-side so it can be used there as well. There is only a handful of instances of usage for both front and back, none of which look like they would be particularly difficult to handle in some other way.
In the past it's been kinda tricky to relay front-end information back to the server in such a way that's useful on the first page load (since the UA doesn't know stuff like viewport size). I remember some pretty fancy dancing I've done where I load up a skeleton page that executes a tiny bit of JavaScript that did things like measure the viewport width and screen size, then set a cookie and force-refreshed the page. If the cookie was present, the server had what it needed and didn't load the skeleton page at all on those requests.
Tricky stuff, but then the server has information about the viewport width on the server-side, which is useful for things, like sending small-screen assets (e.g.different HTML), which was otherwise impossible.
I mention that because UA-CH stuff is not to be confused with regular ol' Client Hints. We're supposed to be able to configure our servers to send an Accept-CH header and then have our client-side code whitelist stuff to send back, like:
That means a server can have information from the client about these things on subsequent page loads. That's a nice API, but Firefox and Safari don't support it. I wonder if it will get a bump if both of those browsers are signaling interest in UA-CH because of this frozen UA string stuff.
Have you ever needed a countdown timer on a project? For something like that, it might be natural to reach for a plugin, but it’s actually a lot more straightforward to make one than you might think and only requires the trifecta of HTML, CSS and JavaScript. Let’s make one together!
This is what we’re aiming for:
Here are a few things the timer does that we’ll be covering in this post:
Displays the initial time remaining
Converts the time value to a MM:SS format
Calculates the difference between the initial time remaining and how much time has passed
Changes color as the time remaining nears zero
Displays the progress of time remaining as an animated ring
OK, that’s what we want, so let’s make it happen!
Step 1: Start with the basic markup and styles
Let’s start with creating a basic template for our timer. We will add an svg with a circle element inside to draw a timer ring that will indicate the passing time and add a span to show the remaining time value. Note that we’re writing the HTML in JavaScript and injecting into the DOM by targeting the #app element. Sure, we could move a lot of it into an HTML file, if that's more your thing.
Now that we have some markup to work with, let’s style it up a bit so we have a good visual to start with. Specifically, we’re going to:
Set the timer’s size
Remove the fill and stroke from the circle wrapper element so we get the shape but let the elapsed time show through
Set the ring’s width and color
/* Sets the containers height and width */
.base-timer {
position: relative;
height: 300px;
width: 300px;
}
/* Removes SVG styling that would hide the time label */
.base-timer__circle {
fill: none;
stroke: none;
}
/* The SVG path that displays the timer's progress */
.base-timer__path-elapsed {
stroke-width: 7px;
stroke: grey;
}
Having that done we end up with a basic template that looks like this.
Step 2: Setting up the time label
As you probably noticed, the template includes an empty <span> that’s going to hold the time remaining. We will fill that place with a proper value. We said earlier that the time will be in MM:SS format. To do that we will create a method called formatTimeLeft:
function formatTimeLeft(time) {
// The largest round integer less than or equal to the result of time divided being by 60.
const minutes = Math.floor(time / 60);
// Seconds are the remainder of the time divided by 60 (modulus operator)
let seconds = time % 60;
// If the value of seconds is less than 10, then display seconds with a leading zero
if (seconds < 10) {
seconds = `0${seconds}`;
}
// The output in MM:SS format
return `${minutes}:${seconds}`;
}
To show the value inside the ring we need to update our styles a bit.
.base-timer__label {
position: absolute;
/* Size should match the parent container */
width: 300px;
height: 300px;
/* Keep the label aligned to the top */
top: 0;
/* Create a flexible box that centers content vertically and horizontally */
display: flex;
align-items: center;
justify-content: center;
/* Sort of an arbitrary number; adjust to your liking */
font-size: 48px;
}
OK, we are ready to play with the timeLeft value, but the value doesn’t exist yet. Let’s create it and set the initial value to our time limit.
// Start with an initial value of 20 seconds
const TIME_LIMIT = 20;
// Initially, no time has passed, but this will count up
// and subtract from the TIME_LIMIT
let timePassed = 0;
let timeLeft = TIME_LIMIT;
And we are one step closer.
Right on! Now we have a timer that starts at 20 seconds… but it doesn't do any counting just yet. Let’s bring it to life so it counts down to zero seconds.
Step 3: Counting down
Let’s think about what we need to count down the time. Right now, we have a timeLimit value that represents our initial time, and a timePassed value that indicates how much time has passed once the countdown starts.
What we need to do is increase the value of timePassed by one unit per second and recompute the timeLeft value based on the new timePassed value. We can achieve that using the setInterval function.
Let’s implement a method called startTimer that will:
Set counter interval
Increment the timePassed value each second
Recompute the new value of timeLeft
Update the label value in the template
We also need to keep the reference to that interval object to clear it when needed — that’s why we will create a timerInterval variable.
let timerInterval = null;
document.getElementById("app").innerHTML = `...`
function startTimer() {
timerInterval = setInterval(() => {
// The amount of time passed increments by one
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
// The time left label is updated
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
}, 1000);
}
We have a method that starts the timer but we do not call it anywhere. Let’s start our timer immediately on load.
That’s it! Our timer will now count down the time. While that’s great and all, it would be nicer if we could add some color to the ring around the time label and change the color at different time values.
Step 4: Cover the timer ring with another ring
To visualize time passing, we need to add a second layer to our ring that handles the animation. What we’re doing is essentially stacking a new green ring on top of the original gray ring so that the green ring animates to reveal the gray ring as time passes, like a progress bar.
Let’s first add a path element in our SVG element.
Finally, let’s add few styles to make the circular path look like our original gray ring. The important thing here is to make sure the stroke-width is the same size as the original ring and that the duration of the transition is set to one second so that it animates smoothly and corresponds with the time remaining in the time label.
.base-timer__path-remaining {
/* Just as thick as the original ring */
stroke-width: 7px;
/* Rounds the line endings to create a seamless circle */
stroke-linecap: round;
/* Makes sure the animation starts at the top of the circle */
transform: rotate(90deg);
transform-origin: center;
/* One second aligns with the speed of the countdown timer */
transition: 1s linear all;
/* Allows the ring to change color when the color value updates */
stroke: currentColor;
}
.base-timer__svg {
/* Flips the svg and makes the animation to move left-to-right */
transform: scaleX(-1);
}
This will output a stroke that covers the timer ring like it should, but it doesn’t animate just yet to reveal the timer ring as time passes.
To animate the length of the remaining time line we are going to use the stroke-dasharray property. Chris explains how it’s used to create the illusion of an element “drawing” itself. And there’s more detail about the property and examples of it in the CSS-Tricks almanac.
Step 5: Animate the progress ring
Let’s see how our ring will look like with different stroke-dasharray values:
What we can see is that the value of stroke-dasharray is actually cutting our remaining time ring into equal-length sections, where the length is the time remaining value. That is happening when we set the value of stroke-dasharray to a single-digit number (i.e. 1-9).
The name dasharray suggests that we can set multiple values as an array. Let’s see how it will behave if we set two numbers instead of one; in this case, those values are 10 and 30.
stroke-dasharray: 10 30
That sets the first section (remaining time) length to 10 and the second section (passed time) to 30. We can use that in our timer with a little trick. What we need initially is for the ring to cover the full length of the circle, meaning the remaining time equals the length of our ring.
What’s that length? Get out your old geometry textbook, because we can calculate the length an arc with some math:
Length = 2πr = 2 * π * 45 = 282,6
That’s the value we want to use when the ring initially mounted. Let’s see how it looks.
stroke-dasharray: 283 283
That works!
OK, the first value in the array is our remaining time, and the second marks how much time has passed. What we need to do now is to manipulate the first value. Let’s see below what we can expect when we change the first value.
We will create two methods, one responsible for calculating what fraction of the initial time is left, and one responsible for calculating the stroke-dasharray value and updating the <path> element that represents our remaining time.
// Divides time left by the defined time limit.
function calculateTimeFraction() {
return timeLeft / TIME_LIMIT;
}
// Update the dasharray value as time passes, starting with 283
function setCircleDasharray() {
const circleDasharray = `${(
calculateTimeFraction() * FULL_DASH_ARRAY
).toFixed(0)} 283`;
document
.getElementById("base-timer-path-remaining")
.setAttribute("stroke-dasharray", circleDasharray);
}
We also need to update our path each second that passes. That means we need to call the newly created setCircleDasharray method inside our timerInterval.
Woohoo, it works… but… look closely, especially at the end. It looks like our animation is lagging by one second. When we reach 0 a small piece of the ring is still visible.
This is due to the animation’s duration being set to one second. When the value of remaining time is set to zero, it still takes one second to actually animate the ring to zero. We can get rid of that by reducing the length of the ring gradually during the countdown. We do that in our calculateTimeFraction method.
Oops… there is one more thing. We said we wanted to change the color of the progress indicator when when the time remaining reaches certain points — sort of like letting the user know that time is almost up.
Step 6: Change the progress color at certain points of time
First, we need to add two thresholds that will indicate when we should change to the warning and alert states and add colors for each of that states. We’re starting with green, then go to orange as a warning, followed by red when time is nearly up.
Now, let’s create a method that’s responsible for checking if the threshold exceeded and changing the progress color when that happens.
function setRemainingPathColor(timeLeft) {
const { alert, warning, info } = COLOR_CODES;
// If the remaining time is less than or equal to 5, remove the "warning" class and apply the "alert" class.
if (timeLeft <= alert.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(warning.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(alert.color);
// If the remaining time is less than or equal to 10, remove the base color and apply the "warning" class.
} else if (timeLeft <= warning.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(info.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(warning.color);
}
}
So, we’re basically removing one CSS class when the timer reaches a point and adding another one in its place. We’re going to need to define those classes.
Dan Wilson has also been following this tech for quite a while and points out why the sudden surge of interest in this:
With the release of Firefox 72 on January 7, 2020, CSS Motion Path is now in Firefox, new Edge (slated for a January 15, 2020 stable release), Chrome, and Opera (and other Blink-based browsers). That means each of these browsers supports a common baseline of offset-path: path(), offset-distance, and offset-rotate.
Dan's post does a great job of covering the basics, including some things you might not think of, like the fact that the path itself can be animated.
... the little voice in your head says ... “I should know all of this. Do I even know what I'm doing?” Why do web developers the world over feel like this?
The overall vibe is that of catharsis in that, hopefully, none of this matters as much as it seems like it might. I'd like to think we try to deliver that, through a bit of levity, on ShopTalk Show as well.
Oh hey and Panic started a podcast too, a must-subscribe from me as a long-time fan of all their interesting work.
Every so often, the fruits of innovation bear fruit in the form of improvements to the foundational layers of the web. In 2015, HTTP/2 became a published standard in an effort to update an aging protocol. This was was both necessary and overdue, as HTTP/1 rendered web performance as an arcane sort of discipline in the form of strange workarounds of its limitations. Though HTTP/2 proliferation isn’t absolute — and there are kinks yet to be worked out — I don’t think it’s a stretch to say the web is better off because of it.
Unfortunately, the rollout of HTTP/2 has presided over a 102% median increase of bytes transferred over mobile the last four years. If we look at the 90th percentile of that same dataset — because it’s really the long tail of performance we need to optimize for — we see an increase of 239%. From 2016 (PDF warning) to 2019, the average mobile download speed in the U.S. has increased by 73%. In Brazil and India, average mobile download speeds increased by 75% and 28%, respectively, in that same period of time.
While page weight alone doesn’t necessarily tell the whole story of the user experience, it is, at the very least, a loosely related phenomenon which threatens the collective user experience. The story that HTTPArchive tells through data acquired from the Chrome User Experience Export (CrUX) can be interpreted a number of different ways, but this one fact is steadfast and unrelenting: most metrics gleaned from CrUX over the last couple of years show little, if any improvement despite various improvements in browsers, the HTTP protocol, and the network itself.
Given these trends, all that can be said of the impact of these improvements at this point is that it has helped to stem the tide of our excesses, but precious little to reduce them. Despite every significant improvement to the underpinnings of the web and the networks we access it through, we continue to build for it in ways that suggest we’re content with the never-ending Jevons paradox in which we toil.
If we’re to make progress in making a faster web for everyone, we must recognize some of the impediments to that goal:
The relentless desire to monetize every square inch of the web, as well as the army of third party vendors which fuel the research mandated by such fevered efforts.
Workplace cultures that favor unrestrained feature-driven development. This practice adds to — but rarely takes away from — what we cram down the wire to users.
Developer conveniences that make the job of the developer easier, but can place an increasing cost on the client.
Counter-intuitively, owners of mature codebases which embody some or all of these traits continue to take the same unsustainable path to profitability they always have. They do this at their own peril, rather than acknowledge the repeatedly established fact that performance-first development practices will do as much — or more — for their bottom line and the user experience.
It’s with this understanding that I’ve come to accept that our current approach to remedy poor performance largely consists of engineering techniques that stem from the ill effects of our business, product management, and engineering practices. We’re good at applying tourniquets, but not so good at sewing up deep wounds.
It’s becoming increasingly clear that web performance isn’t solely an engineering problem, but a problem of people. This is an unappealing assessment in part because technical solutions are comparably inarguable. Content compression works. Minification works. Tree shaking works. Code splitting works. They’re undeniably effective solutions to what may seem like entirely technical problems.
The intersection of web performance and people, on the other hand, is messy and inconvenient. Unlike a technical solution as clearly beneficial as HTTP/2, how do we qualify what successful performance cultures look like? How do we qualify successful approaches to get there? I don’t know exactly what that looks like, but I believe a good template is the following marriage of cultural and engineering tenets:
An organization can’t be successful in prioritizing performance if it can’t secure the support of its leaders. Without that crucial element, it becomes extremely difficult for organizations to create a culture in which performance is the primary feature of their product.
Even with leadership support, performance can’t be effectively prioritized if the telemetry isn’t in place to measure it. Without measurement, it becomes impossible to explain how product development affects performance. If you don’t have the numbers, no one will care about performance until it becomes an apparent crisis.
When you have the support of leadership to make performance a priority and the telemetry in place to measure it, you still can’t get there unless your entire organization understands web performance. This is the time at which you develop and roll out training, documentation, best practices, and standards the organization can embrace. In some ways, this is the space which organizations have already spent a lot of time in, but the challenging work is in establishing feedback loops to assess how well they understand and have applied that knowledge.
When all of the other pieces are finally in place, you can start to create accountability in the organization around performance. Accountability doesn’t come in the form of reprisals when your telemetry tells you performance has suffered over time, but rather in the form of guard rails put in place in the deployment process to alert you when thresholds have been crossed.
Now comes the kicker: even if all of these things come together in your workplace, good outcomes aren’t guaranteed. Barring some regulation that forces us to address the poorly performing websites in our charge — akin to how the ADA keeps us on our toes with regard to accessibility — it’s going to take continuing evangelism and pressure to ensure performance remains a priority. Like so much of the work we do on the web, the work of maintaining a good user experience in evolving codebases is never done. I hope 2020 is the year that we meaningfully recognize that performance is about people, and adapt accordingly.
As technological innovations such as HTTP/3 and 5G emerge, we must take care not to rest on our laurels and simply assume they will heal our ills once and for all. If we do, we’ll certainly be having this discussion again when the successors to those technologies loom. Innovation alone can’t keep the web fast because making the web fast — and keeping it that way — is the hard work we can only accomplish by working together.
Short story: Philip Walton has a clever idea for using service workers to cache the top and bottom of HTML files, reducing a lot of network weight.
Longer thoughts: When you're building a really simple website, you can get away with literally writing raw HTML. It doesn't take long to need a bit more abstraction than that. Even if you're building a three-page site, that's three HTML files, and your programmer's mind will be looking for ways to not repeat yourself. You'll probably find a way to "include" all the stuff at the top and bottom of the HTML, and just change the content in the middle.
I have tended to reach for PHP for that sort of thing in the past (<?php include('header.php); ?>), although these days I'm feeling much more jamstacky and I'd probably do it with Eleventy and Nunjucks.
Or, you could go down the SPA (Single Page App) route just for this basic abstraction if you want. Next and Nuxt are perhaps a little heavy-handed for a few includes, but hey, at least they are easy to work with and the result is a nice static site. The thing about these JavaScript-powered SPA frameworks (Gatsby is in here, too), is that they "hydrate" from static sites into SPAs as the JavaScript loads. Part of the reason for that is speed. No longer does the browser need to reload and request a whole big HTML page again to render; it just asks for whatever smaller amount of data it needs and replaces it on the fly.
So in a sense, you might build a SPA because you have a common header and footer and just want to replace the guts, for efficiencies sake.
Here's Phil:
In a traditional client-server setup, the server always needs to send a full HTML page to the client for every request (otherwise the response would be invalid). But when you think about it, that’s pretty wasteful. Most sites on the internet have a lot of repetition in their HTML payloads because their pages share a lot of common elements (e.g. the <head>, navigation bars, banners, sidebars, footers etc.). But in an ideal world, you wouldn’t have to send so much of the same HTML, over and over again, with every single page request.
With service workers, there’s a solution to this problem. A service worker can request just the bare minimum of data it needs from the server (e.g. an HTML content partial, a Markdown file, JSON data, etc.), and then it can programmatically transform that data into a full HTML document.
So rather than PHP, Eleventy, a JavaScript framework, or any other solution, Phil's idea is that a service worker (a native browser technology) can save a cache of a site's header and footer. Then server requests only need to be made for the "guts" while the full HTML document can be created on the fly.
It's a super fancy idea, and no joke to implement, but the fact that it could be done with less tooling might be appealing to some. On Phil's site:
on this site over the past 30 days, page loads from a service worker had a 47.6% smaller network payloads, and a median First Contentful Paint (FCP) that was 52.3% faster than page loads without a service worker (416ms vs. 851ms).
Aside from configuring a service worker, I'd think the most finicky part is having to configure your server/API to deliver a content-only version of your stuff or build two flat file versions of everything.