How to Migrate a React Application to CSS Modules with Backwards Compatibility
November 4, 2022
With any type of code, if you’re not constantly getting better, you’re getting worse.
My team at Smarty was reminded of this recently when it was time for us to update our web development framework. Unfortunately, we were unable to install the update because the way we were compiling CSS used a dependency that was not compatible with the next version.
Putting off the update caused us to miss out on features we had been waiting for and some great opportunities within the code. On the other hand, in order to take advantage of the updates, we needed to migrate our entire codebase to CSS Modules, which we knew would take weeks of work and would halt all of our other code production. We had hundreds of files to switch over, and we could not justify the time commitment in rewriting our entire code unless we could keep our build working while migrated.
We finally landed on the solution––we made it backwards compatible. Before we made the switch, we prepped the code, ensuring it could work with both CSS and CSS Modules. That way the old code was still there, working seamlessly as we continued to develop our site.
How we did it
Because each CSS module is tied to a specific file (unless you use the global file) you can't directly reference a class unless you import the styles object on the page you are using. That being said, you really don't want to import the same “styles” object into multiple js files. I'll give a bad example and a good example of what I'm talking about.
Here we have a Component wrapped inside of a Wrapper
Pay attention to the class names in this file and the CSS file imported at the top.
Also, pay attention to the class names here and notice that there is no CSS file imported at the top.
The class names inside of the red box are the trouble makers.
You can see here in this example that the wrapper.css file is being imported in the Wrapper.js component. This wouldn't work with modules because of the styles for Component.js inside of the wrapper.css file. You would need to pull those styles out into their own CSS file and import the styles in your Component.js file.
Fixing class styles that were bleeding over from one file into other components was one of the largest challenges we had.
If you do have some class names that are shared over multiple components it's okay, you don't have to create a billion files for that. CSS modules allow you to have a global stylesheet where you can add those styles.
Here’s an example of what our code looked like before the migration to CSS Modules:
During the transition, it looked like this:
And finally, after cleanup phase, we ended up with this version, ready for CSS Modules:
2 Helpful Tips:
Passing down styles
When converting your codebase, you will likely run into instances where you want to pass in a class from a parent down into a child. You can still do that in the same way that you would with string class names. Using the example with the <LoadMaster4000> above:
This gives you the dynamic ability to still control styles of components from parent components.
Warning: When classes are passed down to override styles of another component, things can get tricky. We ran into instances where styles that were higher on the specificity order weren’t overriding styles under it. This made it seem like the styles that we were passing down into the component weren’t being applied at all. This was because of the order at which we were importing our style sheets. Make sure to import the CSS file under all of your other imports to prevent this order specificity bug.
Here you can see the styles imported at the bottom of our import list.
There are some perks to having a styles object instead of just string references when it comes to class names.
Switching over to CSS Modules and upgrading our framework solved a lot of issues for us. It saved us from a confusing and long class naming convention. Since we went from having everything be global to now having modular CSS files, we don’t have to worry about same name classes interfering with each other. Instead of having the name of the file or component then describe the specific class, we can use the same “title” class for multiple elements across multiple components without having the collide with each other. The main win for our team was the incredible amount of time that we are saving.
Previously, we had to review our full site every time we updated a single page to ensure that nothing had broken as a result. We no longer have to go through that review process because we are more confident with the congruence of our local development and production environment. The way it looks in development is the way it looks in production. This saves hours of work each week, and, if there are bugs, it saves our team up to a full day because it is easy to identify and fix the problem.
Migrating our full code base seemed out of reach, but as bugs were creeping into the code and the upgrades for our framework were increasingly valuable, finding a way to make it work became critical. Through backwards compatibility, we were able to make the switch in far less time with fewer disruptions, take advantage of updates, and create a more secure and flexible site.
About the author:
Alex has experience in tech support and technical experience with JS, HTML, CSS, and React. Alex puts the customer's needs at the forefront of all frontend solutions and is always looking for ways to make processes more smooth.
Ready to take charge of your dev career?
Join Europe's leading job platform for software developers!