Imagine, if you will, that we’re part of a team working together to produce some software. Our team knows that in six months, our minimum viable product (MVP) needs to be ready for its release. We also know that after the initial release, our team will continue to iterate on our product: we’ll fix bugs, we’ll add new features, we’ll patch security holes, and someday, we may even deprecate or remove parts of our product.
Our product could be any kind of software, really, but for the rest of this article, the product I’ll be describing is a design system, made up of many individual components. As the producers of the design system, we’ll refer to ourselves as the publishers maintaining the system for our subscribers.
Project vs. Product
For the vast majority of my career, I’ve been involved in project work at agencies, and those projects have largely revolved around websites and webapps. Those projects typically had an end, which was the launch date. Even when my colleagues and I have partnered with other teams to provide long-term support and maintenance, that type of work differs from product work with its planning, roadmaps, and audience concerns.
This is especially true in the context of a design system, where the design system team’s role is publisher, and the audience’s role is subscriber. Nathan Curtis talks about changing from a project to product mindset, noting that a system is “the origin story of a living and evolving product that’ll serve other products.”
To me, the intriguing part of this shift in mindset is that many—if not all of us—are already used to playing one of the roles I mentioned. Whether we realize it or not, we’re all subscribers.
Think about it: did you update an Android or iOS app recently? Did you install a new version of firmware or software for an IoT device at your apartment or house? Did you update to the latest version of Windows or MacOS? I’m thinking it’s highly likely that you did. Congratulations, you’re a subscriber to a piece (or multiple pieces) of software.
Now, think about the other side of this. If you were responsible for creating and maintaining one of the pieces of software that I mentioned above, how would you indicate to your subscribers that your team had built new features, fixed bugs, or patched security holes in the software? What kinds of documentation about your changes would you need to provide to your subscribers? What kind of workflow would you need to make the publisher-to-subscriber process happen?
What I’m hinting at here is a release strategy which is made up of three concepts: 1. Maintaining a version number scheme for either our entire design system or for each of our individual components. 2. Creating a running tally of what’s changed—a changelog—so our subscribers know what is different in this release vs. the last. 3. Publishing the new, updated versions of our system or its components to a place where our subscribers can consume it.
Let’s dig into each of those concepts a bit more.
Our team has worked hard over the past six months, and our MVP is ready to be released to the world! Congratulations! We’ve finally hit version 1.0! But what does that version number signify, and where did it come from? And what version number comes next when we inevitably make some change to our product?
Semantic Versioning, or Semver, is a popular specification that our industry uses to describe the changes between releases of a public interface. Each release is tied directly to a version number, which is built from three numbers separated by periods. Those three numbers signify major, minor, and patch (for instance: 3.10.2), and each of those numbers has a series of ordered versions. The semantic differences between major, minor, and patch are summarized on the Semver website as such:
“Given a version number MAJOR.MINOR.PATCH, increment the: 1. MAJOR version when you make incompatible API changes, 2. MINOR version when you add functionality in a backwards compatible manner, and 3. PATCH version when you make backwards compatible bug fixes.”
I’m not aiming to be exhaustive about Semver and its intricacies in this article, but knowing about major, minor, and patch is important—especially within the context of Node.js, because Semver is built into the way npm manages its packages. Check out the package.json for our Bouncy Ball project for a good exercise to get comfortable with understanding how a project manages its packages. As you look over the dependencies and devDependencies listed there, open up this cheatsheet and take a look at how our package.json refers to versions and ranges.
Now that we’ve discussed some of the meaning behind versions and version numbers, let’s discuss what, exactly, we’re versioning. At the start of this article, we imagined that our team was hard at work building out components for our design system. When we kicked off our project, we decided to use Git to contain all of our design system code in one repository.
At that kickoff point, our team needed to make a decision about its role as a publisher: how should our subscribers consume our components? Should the design system be its own single package that subscribers bring into their projects, wholesale? Or should the design system be a container of many individually subscribable packages, letting our consumers choose which packages they want and when those packages are updated?
There are pros and cons that come along with both strategies, and in my opinion, most of those pros and cons revolve around planning and where and how publishers and subscribers should spend their time and effort.
For instance, if your design system is managed and released as a single package, your team may find that it’s advantageous to develop a release cadence—perhaps monthly or quarterly. And subsequently, the team may find that it can predict the amount of features, fixes, and other work that goes into each release. Not only does this predictability help the publishing team, but it can help the subscriber teams with their planning and roadmaps.
Conversely, maybe your subscribers don’t want to update to the latest version of the whole design system—or maybe they can’t. Maybe there’s a package or a group of packages that they’re unable to update, and they’re now at risk of falling behind until they’re able to prioritize getting up-to-date. Perhaps if the design system published its packages individually, the subscribers would be able to update to those newer packages on their own terms.
Whichever management strategy your team chooses—single package vs. many, individual packages—it’s important to focus on your subscribers. Consider how you’d want to have updates and releases communicated to you if you were a subscriber. Consider what it would take for your subscribers to stay up-to-date and what it would take for them to update or migrate to your latest release. Try to think about the downstream effects of each of your updates, whether they’re a single package or multiple packages. Ultimately, your team’s stakeholders are its subscribers.
Don’t let this overwhelm your team, though. Take solace in the fact that your teams are made up of smart folks who are used to being subscribers. Encourage them to think about the last time they updated a Rails or an npm dependency in one of their other projects and what that experience was like.
I just discussed what teams will be versioning, but I also want to mention how teams can make the process around versioning easier and more accurate.
First, you may or may not be aware that a repository that contains many packages is often referred to as a monorepo. A few examples of popular projects using the monorepo approach include Babel, React, Meteor, and Jest. Given that our design system project is made up of many packages (our components), it makes sense that our design system could be called a monorepo too.
One trait that monorepos tend to share, including the popular ones I listed above, is that they take advantage of automation to manage their version numbers. Regardless of whether your design system is managed as a single package or as many individual packages, I think we can all agree that our teams should be spending their time building amazing components and solving problems for our subscribers—not getting bogged down in figuring out how packages need to be bumped to their next versions, what those versions are, and so forth. Frankly, not automating this process sounds like a recipe for disorganization and eventual disaster.
Luckily, a tool called Lerna exists to help with the workflow of managing monorepos with Git and npm. Lerna initially started as the tool for managing the monorepo in Babel before it was extracted out to be its own standalone tool.
Lerna will handle your versioning for you, but it really shines when you dig into some of its other features, like combining it with conventional commits to automatically generate changelog files and configuring it to automatically publish your packages to a registry, which are the next two concepts we’ll be discussing.
We’ve talked about writing release notes in the past, but maintaining release notes or a changelog for all components within a design system is critical for your subscribers, and it can be daunting. Luckily, there are tools that will automate this process for you.
I mentioned above that you can use Lerna to automatically generate changelog files, but if you’re not using Lerna, another tool to investigate is semantic-release. This tool will automate the whole release workflow, including determining the next version number, generating the release notes, and publishing the package or packages.
Both Lerna and semantic-release use your repository’s commit messages to not only understand the type of version increment to carry out, but also to compile the changelog that gets generated with each release. This is really powerful for your team because as long as they’re sticking to a commit specification known as Conventional Commits, the automated process for changelog generation—and version number incrementing—will just handle itself. The only part that’s left is actually getting your newly updated and documented packages out to your subscribers.
Publishing To A Registry
We’ve incremented our version numbers, we’ve created changelogs to describe our changes, and now it’s time for us to send our packages to a place where our subscribers can get them—most commonly referred to as a package registry.
In the context of Node.js, npm is the most popular registry—especially for publicly available Node packages. GitHub, which now owns npm, has its own package registry, which allows for both public and private publishing, and JFrog Artifactory also allows for private publishing.
Package publication is another part of our workflow that can be daunting if done manually. If you’re thinking that this is yet another situation in which I’m going to advocate for an automated process, then you’re right! Luckily, the two tools I’ve already mentioned above, Lerna and semantic-release, are built to automate package publication for you.
To publish packages, both tools go through a similar process to determine if a package with a corresponding version number exists in the registry. The package will only be published if the version number that corresponds to it doesn’t already exist in the registry. This prevents your packages from overwriting themselves and prevents new changes from polluting already published packages. Your subscribers will know that any incremented version number indicates that some new changes have been made to the packages they’re attempting to update.
Freed through Automation
As developers, we know the advantages of automating our processes and workflows. Automating the process behind your design system’s releases (managing version numbers, compiling changelogs, and handling package publication) allows your team to focus on what’s really important: being excellent partners to your subscribers by building and maintaining great software.