How we migrated entirely to CSS Modules using codemods and Sourcegraph Code Insights
How our Frontend Platform team used codemods to automate a challenging global migration to CSS modules, and Code Insights to track and communicate progress.

How our Frontend Platform team used codemods to automate a challenging global migration to CSS modules, and Code Insights to track and communicate progress.

In Spring 2021, the Sourcegraph team overhauled the design system and UI of our web application. It was an ambitious project because, along with improving the UX, we strove to make web interfaces more consistent, which required the collaboration of most product teams. The task was also challenging from a technical perspective because the application styles were implemented using global CSS rules with Bootstrap as a foundation. Our UI components were typically styled in three different ways:
It was hard to change individual UI components because engineers needed to keep in mind the whole stylesheet to avoid colliding styles in the global scope:
To overcome these issues, we decided that, before diving into the redesign, we should find a way to embrace scoped styles instead of adding new styles to the global scope. At the same time, we needed to ensure that this solution could be integrated with the current approach effectively. The definition of success was a combination of the following outcomes:
We researched popular solutions available in the open source universe: trade-offs between CSS in JS, regular CSS, and hybrid solutions. Some offered more powerful features, but we decided to use a solution that we knew we could easily adopt—CSS modules.
CSS modules are great because they help you write reusable components with isolated styles. According to the repo, CSS modules are:
"CSS files in which all class names and animation names are scoped locally by default."
With CSS modules, CSS classes should be referenced in the JavaScript file via explicit binding to the styles file:
import styles from './styles.css' const Title = () => <h1 class={styles.title}>Heading!</h1>
The compiler would update the CSS file during the build step by replacing the CSS selector class referenced in the markup with a unique character set. And the JavaScript file would be updated by replacing the CSS class with the new inlined string. The final HTML markup might look like this:
<h1 className="module__title__2QcnY">Heading!</h1>
This approach is designed to fix the problem of the global scope in CSS. Engineers can happily name their CSS selectors whatever they want, without worrying about unintended consequences in other areas of the code. Creating a CSS module is ultimately very similar to creating a typical CSS file. Maintaining this flow ensured we could easily start adopting this approach without interfering with our developer experience too much. After we updated documentation on how to use CSS modules, teams adopted this approach for new features immediately.
The Frontend Platform team started the migration by manually converting global styles to CSS modules for a single, recently developed feature. We quickly noticed that despite some initial progress, we had some questions that slowed us down:
We knew we could search the codebase manually for relevant files and make conclusions based on that, or we could write a script to do it for us. But an ideal solution would give a clear, at-a-glance indication of where we were at the moment, and be easy for us to maintain. Thankfully, another product team at Sourcegraph had developed the exact solution to our problem!
"Code Insights reveals high-level information about your codebase, based on both how your code changes over time and its current state." – source.
Code Insights entered Beta in August 2021, and we happily started using it to track the migration progress. As of today, Code Insights is now Generally Available.

We used a few simple search queries to create the code insight, and immediately got answers to all the questions important for migration tracking in one picture. It was a crucial tool in communicating where we were with the migration progress to the engineering organization going forward. Code Insights can be especially helpful for platform teams like ours, which do a lot of invisible work and can struggle to make the case for dedicating time to initiatives like tackling tech debt. Being able to communicate progress visually to stakeholders outside engineering or in leadership is really persuasive. Teams that typically manage large parts of a codebase can find it difficult to get insight into what is happening, and Code Insights makes that really easy.
The migration that we started working on didn't have any hard deadlines, so getting sidetracked with new shiny initiatives was easy. Also, multiple modules were a bit harder to rewrite, and we subconsciously delayed refactoring them. These factors contributed to slowing down, but having a code insight as a map of planned work with a clear destination was motivating to push the migration to 100% completion. It kept reminding us that we were close to our goal and helped us close this chapter without any leftover work.
Global migrations are challenging for platform teams. After spending time to migrate another feature to CSS Modules, we discovered some execution obstacles in our way:
A codemod is an automated change to source code, which helps platform teams execute global refactorings faster and with higher confidence. Because codemods are usually written in the same language as the project, they can understand its subtleties and complexities. It makes them perfect for executing large-scale migration where find-and-replace functionality is insufficient. A codemod generally includes the following steps:
On the technical side: most of the changes required for CSS Modules migration are mechanical and can be automated with the information available in CSS and Typescript abstract syntax trees. But most importantly, the codemod approach allows us to overcome all the challenges we encountered working on the migration manually:
To programmatically migrate global styles to CSS modules, codemod script does three major things:
Finding a React component file with the corresponding global-styles file is easy in the Sourcegraph codebase because React components have colocated styles with a predictable filename. E.g., styles for Button.tsx are defined in Button.scss in the same folder.
It's a multistep operation:
.insights-dashboard {
flex: none;
&__wrapper {
display: flex;
}
}
.insights-dashboard {
flex: none;
}
.wrapper {
display: flex;
}
const classMapping = { 'insights-dashboard': 'insights-dashboard', 'insights-dashboard__wrapper': 'wrapper',}
.module.scss to the filename required for build-tools to interpret this file as a CSS Module.To manipulate Typescript AST, we used ts-morph — TypeScript Compiler API wrapper for static analysis and programmatic code changes. Relying on this package API, the codemod script iterates over all string literals in the React component AST and searches for global classes processed in the previous step.
Here's the AST generated for our small example. Explore it yourself using AST Explorer.

Codemod replaces every string literal match with reference to the corresponding CSS Module class.
<div className="insights-dashboard__wrapper" />
<div className={styles.wrapper} />
At this point, the transformation is complete, and the script outputs information about CSS classes not used in the React component. It allowed us to remove dead code in the migration process.
The codemod script can be used as a CLI tool by supplying a list of files to transform:
$ yarn transform --transform ./globalCssToCssModule.ts ./sourcegraph/**/*.tsx
We developed the proof of concept codemod for the migration in a separate repo at the end of September 2021 and spent a couple of days applying it to the codebase. The code insights created to track the migration readily showed a spike in the migration progress that we happily shared with other teams in Slack:

Another challenge we faced was that we kept breaking our own rules: In quite a few places, we found we were using styles scoped outside of the respective component. Handling those cases required manual intervention from the engineers after applying the codemod.
To keep executing the migration behind the scenes, we started working with contractors to help us handle cases that the codemod could not address. It was another productivity lever akin to the codemod that immensely helped us to focus on critical problems while keeping the migration process going in the background.
Nothing like using @sourcegraph to build @sourcegraph—we're migrating from global CSS to CSS Modules and our frontend platform team is using Code Insights to track migration progress: pic.twitter.com/1lRqYjLiwz— Beyang Liu (@beyang) October 31, 2021
We successfully migrated to CSS Modules behind the scenes, delivering on the "frictionless developer experience" goal set for our team:

We're relying on the same combination of Code Insights and codemods in our subsequent significant migration from global Bootstrap classes to our new Wildcard design system. We find this approach is more than just automating some simple tasks and speeding up the development work, as it helps keep engineers happy by reducing manual labor when upgrading their projects' dependencies, refactoring legacy patterns, or fixing bugs if the next version of a public API has breaking changes. As a next step, we plan to make codemods useful for engineers outside of the Frontend Platform team by developing a higher-level toolkit for creating codemods that will eventually make it as simple as writing a regex find-and-replace.
We hope you found this account of our migration useful and will consider trying codemods and Code Insights in your work.

With Sourcegraph, the code understanding platform for enterprise.
Schedule a demo