Amazon deleted this important seller tool, creating catalog errors and churn
Being an Amazon Seller got harder during the fall of 2020,
when Amazon took away the Check My File feature. And there is no easy
fix in sight.
Feeds were already challenging for even the most experienced of
third-party sellers. Removing access to Check My File has created
massive listings errors for some sellers. And surely it has created
churn for Amazon as well, with hundreds or thousands of Seller Support
cases opened to try and fix the bad listings now being uploaded to the
Amazon catalog.
Previously, before uploading files, sellers used Check My File to
double-check inventory files for warnings or mistakes. Seller Central
would show the seller where errors existed, and those errors would be
corrected. Wash, rinse and repeat until Check My Filed said that you
passed. Then, an error-free feed could be pushed to Amazon.
Now, without the tool:
Errors are uploaded to the catalog, rather than caught during the Check My File process.
Often, these errors “stick” and cannot be resolved with another file upload.
Sometimes, these errors are significant. We saw one client
accidentally break all their variations. Months later, these listings
still are not all fixed.
When sellers mess up the catalog with bad feeds, they must reach out to Seller Support
to fix what they broke. The support is … less than supportive to say
the least. Case after case after case, and the issues still are
not solved. This causes unnecessary frustration for sellers simply
trying to create or update their listings.
The entire scenario is creating time-consuming, painful headaches for
sellers and churn for Amazon. As an experienced seller and consultant, I
honestly don’t understand the endgame. Even owning your company’s brand
registry doesn’t seem to help. Hopefully, Amazon has noticed and will
bring back Check My File.
If Spotify did only sales campaigns in the US and were solely focused
on the number of installs, this wouldn’t be the case. They would’ve
been forced to throw out millions of dollars in sales campaigns to (try
to) make people switch from the already highly competitive music
streaming platforms in India.
But since the branding was proper, people get a unique feeling when they use Spotify and were eager to use it.
Hence only the news articles are enough to bring the change!
PS: obviously there will be social media ads coming at you from Spotify.
CSS is a simple stylesheet language for defining a website or
document’s presentation. However, this simplicity leaves the door open
for many potential issues and technical debt — bloated
code, specificity hell, duplicated code blocks with very little to no
difference, leftover unused selectors, unnecessary hacks, and
workarounds, to name a few.
That kind of technical debt, if not paid on time, can accumulate and
lead to severe issues down the line. Most commonly, it can lead to unexpected side-effects
when adding new UI components and making the codebase difficult to
maintain. You’ve probably worked on a project with a poor CSS codebase
before and thought how you’d written the code differently, given the
opportunity to refactor or rewrite everything from scratch.
Refactoring large parts of CSS code is not an easy
task by any measure. At times, it may seem that it’s just a case of
“deleting the poor quality code, writing better CSS, and deploying the
shiny improved code”. However, there are many other factors to consider,
like the difficulty of refactoring a live codebase, expected duration
and team utilization, establishing refactoring goals, tracking refactor
effectiveness and progress, etc. There is also the matter of convincing
the management or project stakeholders to invest the time and resources
into the refactoring process.
In this three-part series, we are going to go
through the CSS refactor process from the beginning to the end, starting
with knowledge on how to approach it and some general pros and cons of
refactoring, then moving onto the refactoring strategies themselves and
ending with some general best practices on CSS file size and
performance.
For all its flexibility and simplicity, CSS itself has some fundamental issues
that allow developers to write poor-quality code in the first place.
These issues originate from its specificity and inheritance mechanisms,
operating in global scope, source order dependency, etc.
On a team-level level, most of the CSS codebase issues usually originate from the varying skill levels
and CSS knowledge, different preferences and code styles, lack of
understanding of the project structure and existing code and components,
absence of project-level or team-level standards and guidelines, and so
on.
As a result, poor-quality CSS can cause issues that go beyond the
simple visual bugs and can produce various severe side-effects that can
affect the project as a whole. Some such examples include:
Decreasing code quality as more features are added
due to the varying CSS skill levels within a development team and
lacking internal rules, conventions, and best practices.
Adding new features or extending existing selectors causes bugs and unexpected side-effects in other parts of the code (also known as a regression).
Multiple different CSS selectors with duplicated code blocks or chunks of CSS code can be separated into a new selector and extended by variation.
Leftover, unused chunks of code from deleted features. The development team has lost track of which CSS code is used and which can be safely removed.
Inconsistency in the file structure, CSS class naming, overall quality of CSS, etc.
“Specificity hell” where new features are added by overriding instead of existing the CSS codebase.
Undoing CSS where higher-specificity selectors
“reset” the lower-specificity selector style. Developers are writing
more code to have less styling. This results in redundancy and a lot of
waste in code.
In worst-case scenarios, all aforementioned issues combined can result in a large CSS file size, even with the CSS minification applied. This CSS is usually render-blocking,
so the browser won’t even render the website content until it has
finished downloading and parsing the CSS file, resulting in a poor UX
and performance on slower or unreliable networks.
These issues not only affect the end-user but also the development
team and project stakeholders by making the maintenance and feature
development difficult, time-consuming and costly. This is one of the
more useful arguments to bring up when arguing for CSS refactor or
rewrite.
The team at Netlify noted that the reason behind their large-scale CSS refactoring project was the decreasing code quality
and maintainability as the project grew in complexity with more and
more UI components added. They’ve also noticed that the lack of internal
CSS standards and documentation led to the decreasing code quality as
more and more people were working on the CSS codebase.
“(…) what started with organized PostCSS gradually grew to
become a complex and entangled global CSS architecture with a lot of
specificities and overrides. As you might expect, there’s a point where
the added tech debt it introduces makes it difficult to keep shipping
fast without adding any regressions. Besides, as the number of frontend
developers contributing to the codebase also grows, this kind of CSS
architecture becomes even more difficult to work with.”
Refactoring allows developers to gradually and strategically improve
to the existing codebase, without changing its presentation or core
functionality. These improvements are usually small in scope and
limited, and don’t introduce breaking, wide-ranging architectural
changes or add new behavior, features, or functionality to the existing
codebase.
For example, the current codebase features two variations of a card
component — the first one was implemented early in project development
by an experienced developer and the second one was added sometime after
the project was launched by a less experienced developer on a short
deadline, so it features duplicated code and wide-ranging selectors with high-specificity.
A third card variation needs to be added, which shares some styles
from the other two card variations. So in order to avoid bugs,
duplicated code and complex CSS classes, and HTML markup down the line,
the team decides to refactor the card component CSS before implementing a
new variation.
Rewriting allows developers to make substantial changes
to the codebase and assumes that most if not all code from the current
codebase will be changed or replaced. Rewrite allows developers to build
the new codebase from scratch, tackle core issues from the current
codebase that were impossible or expensive to fix, improve the tech
stack and architecture and establish new internal rules and best
practices for the new codebase.
For example, the client is in process of rebranding and the website
needs to be updated with a new design and revamped content. Since this
is a site-wide change out of the box, developers decide
to start from scratch, rewrite the project, and take this opportunity
to address the core issues current CSS codebase has but cannot be solved
with code refactor, update the CSS tech stack, use the newest tools and
features, establish new internal rules and best practices for styling,
etc.
Let’s summarize the pros and cons of each approach.
Refactor
Rewrite
Pros
Incremental and flexible process
Working with a single codebase
The team is not locked by the refactor tasks
Easier to convince the stakeholder and project leaders to do a refactor
Can address core issues; outdated tech stack, naming conventions, architectural decisions, internal rules, and so on.
Independent from the current codebase (existing features and weaknesses...)
Long-term plans for the codebase extensibility and maintainability
Cons
Depends on the current codebase and core architecture
Cannot address core issues
Architectural decisions, existing internal rules and best practices, wide-ranging issues, etc.
May be complicated to execute, depending on the project setup and codebase health
Expensive and time-consuming
Needs to be fully implemented before launch
Maintaining the current codebase while developing new codebase
Harder to convince the stakeholders and project leaders to do a complete rewrite
Refactoring is a recommended approach for incrementally improving the CSS codebase
while maintaining the current look and feel (design). Team members can
work on addressing these codebase issues when there aren’t any higher
priority tasks. By incrementally improving the current codebase user
experience will not be affected directly in most cases, however, a
cleaner and more maintainable codebase will result in easier feature
implementation and fewer unexpected bugs and side-effects.
Project stakeholders will probably agree to invest limited time and
resources into refactoring, but they’ll expect these tasks to be done quickly and will expect the team to be available for the primary tasks.
Refactoring CSS should be done at regular intervals when no
wide-ranging design or content changes aren’t planned for the near
future. Teams should proactively seek the previously mentioned weak
points in the current CSS codebase and work on addressing them whenever
there aren’t higher priority tasks available.
Lead frontend developer or the developer with the most experience
with CSS should raise issues and create refactor tasks to enforce CSS
code quality standards in the codebase.
Rewriting the complete CSS codebase should be done when the CSS codebase has core issues
that cannot be addressed with refactoring or when refactoring is a more
expensive option. Speaking from personal experience, when I’ve started
working for clients that moved from another company and the
aforementioned CSS issues and it was obvious that it’ll be a difficult
job to refactor, I’d start by recommending a full rewrite and see what
the client thinks. In most cases, those clients were dissatisfied with
the state of the codebase and were happy to proceed with the rewrite.
Another reason for full CSS rewrite is when a substantial change is planned
for the website — rebranding, redesign, or any other significant change
that affects most of the website. It’s safe to assume that the project
stakeholders are aware that this is a significant investment and it will
take some time for the rewrite to be complete.
When the development team has agreed on the fact that CSS code needs
to be refactored to either streamline the feature development workflow
or eliminate unexpected CSS side-effects and bugs, the team needs to
bring this suggestion up to the project stakeholders or a project
manager.
It’s a good idea to provide some hard data alongside
the subjective thoughts on the codebase and the general code review.
This will also give the team a measurable goal that they can be aware of
while working on the refactor — target file size, selector specificity,
CSS code complexity, number of media queries…
When doing a CSS audit or preparing for a CSS refactor, I rely on several of many useful tools to get a general overview and useful stats about the CSS codebase.
My personal go-to tool is CSS Stats,
s a free tool that provides a useful overview of the CSS codebase
quality with lots of useful metrics that can help developers catch some
hard-to-spot issues.
Back in 2016, trivago has done a large-scale refactor
for their CSS codebase and used the metrics from CSS Stats to set some
concrete, measurable goals like reducing specificity and reducing the
number of color variations. In just three weeks, they’ve managed to
improve the overall health of the CSS codebase, reduce the CSS file
size, improve render performance on mobile, etc.
“A tool like CSS Stats
can easily help you figure out consistency issues within your codebase.
Indicating what can happen when everybody has different opinions on how
a grey tone should look like, you will end up with 50 shades of grey.
Moreover, Specificity Graph gives you a good overall indication of your CSS base’s health.”
As for CLI tools, Wallace
is a handy tool that provides somewhat basic, but useful CSS stats and
overview which can be used to identify issues related to file size,
number of rules and selectors, selector types and complexity, etc.
Wallace also offers a free analyzer tool
on the Project Wallace Website which uses a seemingly more advanced
version of Wallace in the backend to provide some useful data
visualizations and few more metrics that are not available in the
Wallace CLI.
Project Wallace also offers a complete paid solution for CSS codebase analytics.
It features even more useful features and metrics that can help
developers catch some hard-to-spot issues and keep track of CSS stats
changes on a per-commit basis. Although the paid plan includes more
features, the free plan, and the basic CSS analyzer tool are more than
enough for auditing the CSS codebase quality and getting a general
overview to make plans for refactoring.
We’ve seen how the simplicity and flexibility of the CSS codebase can
cause a lot of issues with code quality, performance, and visual bugs.
There is no silver-bullet automatic tool that will make sure that we
write CSS in the best possible way and avoid all possible architectural
pitfalls along the way.
The best tools that will ensure that we write high-quality CSS code are discipline, attention to detail, and general CSS knowledge and skillset. Developer needs to be constantly aware of the bigger picture and understand what role their CSS plays in that bigger picture.
For example, by overspecifying selectors, a single developer can
severely limit the usability, leading to other developers having to
duplicate the code in order to use it for other, similar components with
different markup. These issues often occur when developers lack
understanding and not leveraging the underlying mechanisms behind CSS
(cascade, inheritance, browser performance, and selector specificity). These early decisions can lead to major repercussions in the future,
so the CSS codebase’s health and maintainability rest on the
developer’s knowledge, skills, and understanding of the CSS
fundamentals.
Automated tools are not aware of the bigger picture or how the
selector is used, so they cannot make these crucial architectural
decisions, besides enforcing some basic, predictable, and rigid rules.
Speaking from personal experience, I’ve found the following helped me to significantly improve how I worked with CSS:
Learning the architectural patterns. CSS Guidelines provide a great
knowledge base and best practices for writing high-quality CSS based on
general programming patterns and architectural principles.
Practice and improve.
Work on personal projects or tackle a challenge from Frontend Mentor
to improve your skills. Start with simple projects (a single component
or a section) and focus on writing the best CSS you can, try out various
approaches, apply various architectural patterns, gradually improve the
code, and learn how to write high-quality CSS efficiently.
Learning from mistakes.
Trust me, you’ll write some really poor-quality CSS when you are
starting out. It will take you a few tries to get it right. Take a
moment and think about what went wrong, analyze the weak spots, think
about what you could have done differently and how, and try to avoid the
same mistakes in the future.
It’s also important to establish rules and internal CSS standards
within a team or even for the whole company. Clearly defined
company-wide standards, code style, and principles can yield many
benefits such as:
Unified and consistent code style and quality
Easier to understand, robust codebase
Streamlined project onboarding
Standardized code reviews that can be done by any team member, not
just the lead frontend developer or the more experienced developers
Kirby Yardley has worked on refactoring the Sundance Institute design system and CSS and has pointed out the importance of establishing internal rules and best practices.
“Without proper rules and strategy, CSS is a language that
lends itself to misuse. Often developers will write styles specific to
one component without thinking critically about how that code could be
reused across other elements (…) After lots of research and deliberation
about how we wanted to approach architecting our CSS, we decided to use
a methodology called ITCSS.“
Going back to the previous example from the team at trivago, establishing internal rules and guidelines proved to be an important step for their refactoring process.
“We introduced a pattern library, started utilizing atomic design in our workflow, created new coding guidelines, and adapted several methodologies like BEM and ITCSS in order to support us in maintaining and developing our CSS/UI on a large scale.”
Not all rules and standards need to be manually checked and enforced. CSS linting tools like Stylelint provide some useful rules
that will help you check for errors and enforce internal standards and
common CSS best practices like disallowing empty CSS code blocks and
comments, disallowing duplicate selectors, limiting units, setting
selector maximum specificity and nesting depth, establishing selector
name pattern, etc.
Before deciding to propose a granular codebase refactor or a full CSS rewrite, we need to understand the issues with the current codebase
so we can avoid them in the future and have measurable data for the
process. CSS codebase may contain lots of complex high-specificity
selectors which cause unexpected side-effects and bugs when adding new
features, maybe the codebase is suffering from lots of repeated code
chunks that can be moved into a separate utility class, or maybe the mix
of various media queries are causing some unexpected conflicts.
Useful tools like CSS Stats and Wallace can provide a general
high-level overview of the CSS codebase and give a detailed insight into
codebase state and health. These tools also provide measurable stats that can be used for setting the goals for the refactoring process and keep track of the refactoring progress.
After determining refactoring goals and scope, it’s important to set internal guidelines and best practices for CSS codebase
— naming convention, architectural principles, file, and folder
structure, etc. This ensures code consistency, establishes a core
foundation within the project which can be documented and which can be
used for onboarding and CSS code review. Using linting tools like
Stylelint can help to enforce some common CSS best practices to
partially automate the code review process.
In the next article from this three-part series, we’re going to dive
into a bulletproof CSS refactoring strategy which ensures a seamless
transition between the current codebase and refactored codebase.
we’ve covered the side effects of low-quality CSS codebase on
end-users, development teams, and management. Maintaining, extending,
and working with the low-quality codebase is difficult and often
requires additional time and resources. Before bringing up the
refactoring proposal to the management and stakeholders, it can be
useful to back up the suggestion with some hard data about the codebase health — not only to convince the management department, but also have a measurable goal for the refactoring process.
Let’s assume that management has approved the CSS refactoring
project. The development team has analyzed and pinpointed the weaknesses
in the CSS codebase and has set target goals for the refactor (file
size, specificity, color definitions, and so on). In this article, we’ll
take a deep dive into the refactoring process itself, and cover
incremental refactoring strategy, visual regression testing, and
maintaining the refactored codebase.
Before starting the refactoring process, the team needs to go over the codebase issues andCSS health audit data
(CSS file size, selector complexity, duplicated properties, and
declarations, etc.) and discuss individual issues about how to approach
them and what challenges to expect. These issues and challenges are
specific to the codebase and can make the refactoring process or testing
difficult. As concluded in the previous article,
it’s important to establish internal rules and codebase standards and
keep them documented to make sure that the team is on the same page and
has a more unified and standardized approach to refactoring.
The team also needs to outline the individual refactoring tasks and
set the deadlines for completing the refactoring project, taking into
account current tasks and making sure that refactoring project doesn’t
prevent the team from addressing urgent tasks or working on planned
features. Before estimating the time duration and workload of the
refactoring project, the team needs to consult with the management about
the short-term plans and adjust their estimates and workload based on
the planned features and regular maintenance procedures.
Unlike regular features and bug fixes, the refactoring process yields
little to no visible and measurable changes on the front end, and
management cannot keep track of the progress on their own. It’s
important to establish transparent communication to
keep the management and other project stakeholders updated on the
refactoring progress and results. Online collaborative workspace tools
like Miro or MURAL
can also be used for effective communication and collaboration between
the team members and management, as well as a quick and simple task
management tool.
Christoph Reinartz pointed out the importance of transparency and clear communication while the team at trivago was working on the CSS refactoring project.
“Communication and clearly making the progress and any
upcoming issues visible to the whole company were our only weapon. We
decided to build up a very simple Kanban board, established a project
stand-up and a project Slack channel, and kept management and the
company up-to-date via our internal social cast network.”
The most crucial element of planning the refactoring process is to
keep the CSS refactoring task scope as small as possible. This makes the
tasks more manageable, and easier to test and integrate.
Harry Roberts refers to these tasks as “refactoring tunnels”.
For example, refactoring the entire codebase to follow the BEM
methodology all at once can yield a massive improvement to the codebase
and the development process. This might seem like a simple
search-and-replace task at first. However, this task affects all
elements on every page (high scope) and the team cannot “see the light
at the end of the tunnel” right away; a lot of things can break in the
process and unexpected issues can slow down the progress and no one can tell when the task is going to be finished.
The team can spend days or weeks working on it and risk hitting a wall,
accumulate additional technical debt, or making the codebase even less
healthy. The team ends up either giving up on the task of starting over,
wasting time and resources in the process.
By contrast, improving just the navigation component CSS is a smaller
scope task and is much more manageable and doable. It is also easier to
test and verify. This task can be done in a few days. Even with
potential issues and challenges that slow down the task, there is a high
chance of success. The team can always “see the end of the tunnel”
while they’re working on the task because they know the task will be
completed once the component has been refactored and all issues related
to the component have been fixed.
Finally, the team needs to agree on the refactoring strategy and
regression testing method. This is where the refactoring process gets
challenging — refactoring should be as streamlined as possible and
shouldn’t introduce any regressions or bugs.
Let’s dive into one of the most effective CSS refactoring strategies and see how we can use it to improve the codebase quickly and effectively.
Refactoring is a challenging process that is much more complex than
simply deleting the legacy code and replacing it with the refactored
code. There is also the matter of integrating the refactored codebase
with the legacy codebase and avoiding regressions, accidental code
deletions, preventing stylesheet conflicts, etc. To avoid these issues, I
would recommend using an incremental (or granular) refactoring
strategy.
In my opinion, this is one of the safest, most logical, and most
recommended CSS refactoring strategies I’ve come across so far. Harry
Roberts has outlined this strategy in 2017. and it has been my personal go-to CSS refactoring strategy since I first heard about it.
Let’s go over this strategy step by step.
Step 1: Pick A Component And Develop It In Isolation #
This strategy relies on individual tasks having low scope, meaning
that we should refactor the project component by component. It’s
recommended to start with low-scope tasks (individual components) and
then move onto higher-scoped tasks (global styles).
Depending on the project structure and CSS selectors, individual
component styles consist of a combination of component (class) styles
and global (wide-ranging element) styles. Both component styles and
global styles can be the source of the codebase issues and might need to
be refactored.
Let’s take a look at the more common CSS codebase issues
which can affect a single component. Component (class) selectors might
be too complex, difficult to reuse, or can have high specificity and
enforce the specific markup or structure. Global (element) selectors
might be greedy and leak unwanted styles into multiple components which
need to be undone with high-specificity component selectors.
After choosing a component to refactor (a lower-scoped task), we need
to develop it in an isolated environment away from the legacy code, its
weaknesses, and conflicting selectors. This is also a good opportunity
to improve the HTML markup, remove unnecessary nestings, use better CSS
class names, use ARIA attributes, etc.
You don’t have to go out of your way to set up a whole build system for this, you can even use CodePen
to rebuild the individual components. To avoid conflicts with the
legacy class names and to separate the refactored code from the legacy
code more clearly, we’ll use an rf- prefix on CSS class name selectors.
Step 2: Merge With The Legacy Codebase And Fix Bugs #
Once we’ve finished rebuilding the component in an isolated
environment, we need to replace the legacy HTML markup with refactored
markup (new HTML structure, class names, attributes, etc.) and add the
refactored component CSS alongside the legacy CSS.
We don’t want to act too hastily and remove legacy styles right away.
By making too many changes simultaneously, we’ll lose track of the
issues that might happen due to the conflicting codebases and multiple
changes. For now, let’s replace the markup and add refactored CSS to the
existing codebase and see what happens. Keep in mind that refactored
CSS should have the .rf- prefix in their class names to prevent conflicts with the legacy codebase.
Legacy component CSS and global styles can cause unexpected
side-effects and leak unwanted styles into the refactored component.
Refactored codebase might be missing the faulty CSS which was required
to undo these side-effects. Due to those styles having a wider reach and
possibly affecting other components, we cannot simply edit the
problematic code directly. We need to use a different approach to tackle
these conflicts.
We need to create a separate CSS file, which we can name overrides.css or defend.css which will contain hacky, high-specificity code that combats the unwanted leaked styles from the legacy codebase.
overrides.css which will contain high-specificity
selectors which make sure that the refactored codebase works with the
legacy codebase. This is only a temporary file and it will be removed
once the legacy code is deleted. For now, add the high-specificity style
overrides to unset the styles applied by legacy styles and test if
everything is working as expected.
If you notice any issues, check if the refactored component is
missing any styles by going back to the isolated environment or if any
other styles are leaking into the component and need to be overridden.
If the component looks and works as expected after adding these
overrides, remove the legacy code for the refactored component and check
if any issues happen. Remove related hacky code from overrides.css and test again.
Depending on the case, you probably won’t be able to remove every
override right away. For example, if the issue lies within a global
element selector which leaks styles into other components that also need
to be refactored. For those cases, we won’t risk expanding the scope of
the task and the pull request but rather wait until all components have been refactored and tackle the high-scope tasks after we’ve removed the same style dependency from all other components.
In a way, you can treat the overrides.css file as your
makeshift TODO list for refactoring greedy and wide-reaching element
selectors. You should also consider updating the task board to include
the newly uncovered issues. Make sure to add useful comments in the overrides.css
file so other team members are on the same page and instantly know why
the override has been applied and in response to which selector.
/* overrides.css *//* Resets dimensions enforced by ".sidebar > div" in "sidebar.css" */.sidebar > .card{min-width: 0;}/* Resets font size enforced by ".hero-container" in "hero.css"*/.card{font-size: 18px;}
Once the refactored component has been successfully integrated with
the legacy codebase, create a Pull Request and run an automated visual
regression test to catch any issues that might have gone unnoticed and
fix them before merging them into one of the main git branches. Visual
regression testing can be treated as the last line of defense before
merging the individual pull requests. We’ll cover visual regression
testing in more detail in one of the upcoming sections of this article.
Now rinse and repeat these three steps until the codebase has been refactored and overrides.css is empty and can be safely removed.
Let’s assume that we have refactored all individual low-scoped components and all that is left in the overrides.css
file are related to global wide-reaching element selectors. This is a
very realistic case, speaking from the experience, as many CSS issues
are caused by wide-reaching element selectors leaking styles into
multiple components.
By tackling the individual components first and shielding them from the global CSS side-effects using overrides.css
file, we’ve made these higher-scoped tasks much more manageable and
less risky to do. We can move onto refactoring global CSS styles more
safely than before and remove duplicated styles from the individual
components and replacing them with general element styles and utilities —
buttons, links, images, containers, inputs, grids, and so on. By doing
so, we’re going to incrementally remove the code from our makeshift TODO
overrides.css file and duplicated code repeated in multiple components.
Let’s apply the same three steps of the incremental refactoring
strategy, starting by developing and testing the styles in isolation.
Next, we need to add the refactored global styles to the codebase. We
might encounter the same issues when merging the two codebases and we
can add the necessary overrides in the overrides.css.
However, this time, we can expect that as we are gradually removing
legacy styles, we will also be able to gradually remove overrides that
we’ve introduced to combat those unwanted side-effects.
The downside of developing components in isolation
can be found in element styles that are shared between multiple
components — style guide elements like buttons, inputs, headings, and so
on. When developing these in isolation from the legacy codebase, we
don’t have access to the legacy style guide. Additionally, we don’t want
to create those dependencies between the legacy codebase and refactored
codebase.
That is why it’s easier to remove the duplicated code blocks and move
these styles into separate, more general style guide components and
selectors later on. It allows us to address these changes right at the
end and with the lower risk as we are working with a much healthier and
consistent codebase, instead of the messy, inconsistent, and buggy
legacy codebase. Of course, any unintended side-effects and bugs can
still happen and these should be caught with the visual regression
testing tools which we’ll cover in one of the following sections of the
article.
When the codebase has been completely refactored and we’ve removed all makeshift TODO items from the overrides.css file, we can safely remove it and we are left with the refactored and improved CSS codebase.
Let’s use the incremental refactoring strategy to refactor a simple
page that consists of a title element and two card components in a grid
component. Each card element consists of an image, title, subtitle,
description, and a button and is placed in a 2-column grid with
horizontal and vertical spacing.
As you can see, we have a suboptimal CSS codebase with various
specificity issues, overrides, duplicated code, and some cases of
undoing styles.
The .card
component also uses high specificity selectors which enforces a
specific HTML structure and allows styles to leak into other elements
inside the card components.
/* Element needs to follow this specific HTML structure to have these styles applied */.card h2 > small{/* ... */}/* These styles will leak into all div elements in a card component */.card div{padding: 2em 1.5em 1em;}/* These styles will leak into all p elements in a card component */.card p{font-size: 0.9em;margin-top: 0;}
We’ll
start with the lowest scoped and topmost children components, so let’s
refactor the card component which is the topmost child of the page, i.e.
the card component is a child of the grid component. We’ll develop the
card component in isolation and use the agreed standards and best
practices.
We’ll use BEM to replace the greedy wide-reaching selectors with
simple class selectors and use CSS custom properties to replace the
inconsistent, hard-coded inline color values. We’ll also add some CSS to
help us develop the component, which we won’t copy to the existing
codebase.
We are using the rf- prefix for the new CSS classes so
we can avoid class name conflicts and differentiate the refactored
styles from legacy styles for better code organization and simpler
debugging. This will also allow us to keep track of the refactoring
progress.
We
are going to replace the legacy markup with the refactored markup, and
add the refactored styles to the CSS codebase. We are not going to
remove the legacy styles just yet. We need to check if any styles from
other legacy selectors have leaked into the refactored component.
Due to the issues with global wide-ranging selectors, we have some
unwanted style overrides leaking into the refactored component — title
font properties have been reset and font size inside the card component
has changed.
We need to add style overrides to overrides.css to remove the
unwanted side-effects from other selectors. We’re also going to comment
on each override so we know which selector has caused the issue.
We now know that we are going to have to refactor the .grid
component and global h1, h2 selector at some point. This is why we can
treat it as a TODO list - these leaked styles can cause issues in other
components, they are objectively faulty, and are applying styles that
are being reset in the majority of use-cases.
Let’s remove the legacy .card styles from the project and see if everything looks alright. We can check if we can remove any styles from overrides.css,
however, we know right away that none of the overrides are related to
legacy card component styles, but other components and element
selectors.
Now that the card component has been refactored, let’s check our
makeshift TODO list and see what to tackle next. We have a choice
between:
Refactor the grid component — lower scope (short tunnel),
Refactor global styles — higher scope (longer tunnel).
We’ll go with the lowest scope component, the grid component in this
case, and apply the same approach. We’ll start by developing the grid
component in isolation. We can use the card component markup for
testing, card component styles won’t affect the grid and won’t help us
develop the component, so we can leave them out of the isolated
component for a simpler CSS markup.
Let’s replace the grid HTML markup with the refactored markup and add
the refactored styles to the CSS codebase and check if we need to add
any styles to overrides.css to mitigate any stylesheet conflicts or leaked styles.
We can see that no new issues appeared, so we can proceed with removing the legacy .grid styles. Let’s also check if overrides.css contains any styles that are applied for the legacy .grid selector.
This
is why it’s useful to document the override rules. We can safely remove
this selector and move it onto the last item in our makeshift TODO list
— heading element selectors. We’re going to go through the same steps
again — we’ll create a refactored markup in isolation, replace the HTML
markup and add the refactored styles to the stylesheet.
.rf-title{font-family: Georgia,"Times New Roman", Times, serif;}.rf-title--size-regular{line-height: 1.3;font-size: 2.5em;}.rf-title--spacing-regular{margin-top: 0;margin-bottom: 0.75em;}
We’ll
check if there are any issues and confirm that no new issues were
introduced by the updated markup and stylesheet and we’ll remove the
legacy h1, h2 selector from the stylesheet. Finally, we’re going to check overrides.css and remove the styles related to the legacy element selector.
The overrides.css is now empty and we’ve refactored the
card, grid, and title components in our project. Our codebase is much
more healthier and consistent compared to the starting point — we can
add elements to the grid component and new title variations without
having to undo the leaked styles.
However, there are a few tweaks we can do to improve our codebase.
As we’ve developed the components in isolation, we’ve probably re-built
the same style guide components multiple times and created some
duplicated code. For example, a button is a style guide component and
we’ve scoped these styles to a card component.
/* Refactored button styles scoped to a component */.rf-card__link{color:var(--color-text-negative);background-color:var(--color-cta);padding: 1em;display: flex;justify-content: center;text-decoration: none;text-transform: uppercase;letter-spacing: 0.05em;font-weight: 600;}
If
any other component is using the same button styles, it means that
we’re going to write the same styles each time we develop them in
isolation. We can refactor these duplicates into a separate .button component and replace the duplicated scoped styles with general style guide styles. However, we already have a legacy .button selector which needs to be refactored, so we’re also able to remove the legacy button selector.
Even though we’re moving onto refactoring higher scoped elements, the
codebase is much healthier and consistent compared to the starting
point, so the risk is lower and the task is much more doable. We also
don’t have to worry that the changes in the topmost child components
will override any changes to the general selector.
We
can use the same incremental approach to the We’re going to rebuild the
button component in isolation, update the markup, and add the
refactored styles to the stylesheet. We’re going to do a quick check for
stylesheet conflicts and bugs, notice that nothing has changed, and
remove the legacy button markup and the component-scope button styles.
/* Before - button styles scoped to a card component */
<a class="rf-card__link" href="#">View gallery</a>
/* After - General styles from a button component */
<a class="rf-button rf-button--regular rf-button--cta" href="#">View gallery</a>
Once the refactoring project has been completed, we can use search-and-replace to clean up the rf-
prefixes in the codebase and treat the existing codebase as the new
standard. That way, extending the codebase won’t introduce naming
inconsistencies or force the rf- prefix naming that could cause issues for any future refactors.
Regardless of the refactor tasks’ scope size and how well the team
follows the incremental refactoring strategy steps, bugs and regressions
can happen. Ideally, We want to avoid introducing issues and
regressions in the codebase — conflicting styles, missing styles,
incorrect markup, leaked styles from other elements, etc.
Using automated visual regression testing tools like Percy or Chromatic on a Pull Request level can speed up testing, allow developers to address regressions quickly and early, and make sure that the bugs don’t end up on the live site.
These tools can take screenshots of individual pages and components and
compare changes in style and layout and notify developers of any visual
changes that can happen due to changes in the markup or CSS.
Since the CSS refactor process shouldn’t result in any visual changes
in usual cases and depending on the task and issues in the legacy
codebase, the visual regression testing process can potentially be
simple as checking if any visual changes happened at all and checking if
these changes were intentional or unintentional.
Depending on the project, testing tools don’t need to be complex or sophisticated to be effective. While working on refactoring the Sundance Institute’s CSS codebase, the development team used a simple static style guide page generated by Jekyll to test the refactored components.
“One unintended consequence of executing the refactor in
abstraction on a Jekyll instance was that we could now publish it to
Github pages as a living style guide. This has become an invaluable resource for our dev team and for external vendors to reference.”
Once the CSS refactor tasks have been completed and the refactored
code is ready for production, the team can also consider doing an A/B test to check the effect of the refactored codebase on users.
For example, if the goal of the refactoring process was to reduce the
overall CSS file size, the A/B test can potentially yield significant
improvements for mobile users, and these results can also be beneficial
to project stakeholders and management. That’s exactly how the team at Trivago approached the deployment of their large-scale refactoring project.
“(…) we were able to release the technical migration as an
A/B Test. We tested the migration for one week, with positive results on
mobile devices where mobile-first paid out and accepted the migration
after only four weeks.”
Kanban board, GitHub issues, GitHub project board, and standard
project management tools can do a great job of keeping track of the
refactoring progress. However, depending on the tools and how the
project is organized, it may be difficult to estimate the progress on a
per-page basis or do a quick check on which components need to be
refactored.
This is where our .rf-prefixed CSS classes come in. Harry Roberts has talked about the benefits of using the prefix in detail. The bottom line is — not only do these classes allow developers to clearly separate the refactored CSS codebase from the legacy codebase, but also to quickly show the progress to the project managers and other project stakeholders on a per-page basis.
For example, management may decide to test the effects of the
refactored codebase early by deploying only the refactored homepage code
and they would want to know when the homepage components will be
refactored and ready for A/B testing.
Instead of wasting some time comparing the homepage components with
the available tasks on the Kanban board, developers can just temporarily
add the following styles to highlight the refactored components which
have the rf- prefix in their class names, and the
components that need to be refactored. That way, they can reorganize the
tasks and prioritize refactoring homepage components.
/* Highlights all refactored components */[class*="rf-"]{outline: 5px solid green;}/* Highlights all components that havent been refactored */body *:not([class]){outline: 5px solid red;}
After the refactoring project has been completed, the team needs to
make sure to maintain the codebase health for the foreseeable future —
new features will be developed, some new features might even be rushed
and produce technical debt, various bugfixes will also be developed,
etc. All in all, the development team needs to make sure that the
codebase health remains stable as long as they’re in charge of the
codebase.
Technical debt which can result in potentially faulty CSS code should be isolated, documented, and implemented in a separate CSS file which is often named as shame.css.
It’s important to document the rules and best practices
that were established and applied during the refactoring projects.
Having those rules in writing allows for standardized code reviews,
faster project onboarding for new team members, easier project handoff,
etc.
Some of the rules and best practices can also be enforced and documented with automated code-checking tools like stylelint.
Andrey Sitnik, the author of widely-used CSS development tools like
PostCSS and Autoprefixer, has noted how automatic linting tools can make
code reviews and onboarding easier and less stressful.
“However, automatic linting is not the only reason to adopt
Stylelint in your project. It can be extremely helpful for onboarding
new developers on the team: a lot of time (and nerves!) are wasted on
code reviews until junior developers are fully aware of accepted code
standards and best practices. Stylelint can make this process much less
stressful for everyone.”
Additionally, the team can create a Pull Request template
and include the checklist of standards and best practices and a link to
the project code rules document so that the developers making the Pull
Request can check the code themselves and make sure that it follows the
agreed standards and best practices.
Incremental refactoring strategy is one of the safest and most
recommended approaches when it comes to refactoring CSS. The development
team needs to refactor the codebase component by component to ensure
that the tasks have a low scope and are manageable. Individual
components need to be then developed in isolation — away from the faulty
code — and then merged with the existing codebase. The issues that may
come up from the conflicting codebases can be solved by adding a
temporary CSS file that contains all the necessary overrides to remove the conflicts in CSS styles.
After that, legacy code for the target component can be removed and the
process continues until all components have been refactored and until
the temporary CSS file which contains the overrides is empty.
Visual regression testing tools like Percy and Chromatic can be used
for testing and to detect any regressions and unwanted changes on the
Pull Request level, so developers can fix these issues before the
refactored code is deployed to the live site.
Developers can use A/B testing and use monitoring tools to make sure
that the refactoring doesn’t negatively affect performance and user
experience before finally launching the refactored project on a live
site. Developers will also need to ensure that the agreed standards and
best practices are used on the project continues to maintain the
codebase health and quality in the future.