Aphrodite to CSS Modules codemod
I wanted to convert our React project from Aphrodite to CSS Modules. The biggest impetus was that Aphrodite isn't supported by the new Next.js v13 app
directory feature, which I'm excited to try. I like styled-components, but my co-worker likes CSS Modules and it's hard to go wrong with CSS Modules. CSS Modules also has built-in support in Next.js and it looks pretty good in this graphic from the State of CSS survey.
To ease the conversion, I wrote a jscodeshift codemod to automate most of the process. The codemod is on github here: aphrodite-to-css-modules-codemod
. An example is below.
The codemod worked well for my 200 Aphrodite files. I did spend time manually converting JS constants into CSS variables. I also manually handled CSS precedence issues since Aphrodite handles precedence more nicely than CSS. But overall I was pretty happy with the results. (It was certainly more successful than my attempt at a reactstrap-to-react-bootstrap codemod which I never used.)
Before¶
./example/src/MyComponent.tsx
:
import { css, StyleSheet } from "aphrodite";
import classNames from "classnames";
import React from "react";
import { colors } from "./constants";
import { hexToRgbA } from "./utils";
export default function MyComponent() {
const isSomething = true;
const isSomethingElse = false;
return (
<div
className={css(
isSomethingElse ? myStyles.containerGrid : myStyles.containerFlex,
)}
style={{}}
>
<div className={css(myStyles.header, myStyles.content)}>header</div>
<div className={classNames(css(myStyles.content), "another-class")}>
<div>Lorem ipsum</div>
</div>
<span className={css(isSomething && myStyles.warning)}></span>
</div>
);
}
// comment I
export const myStyles = StyleSheet.create({
containerGrid: {
backgroundColor: "white",
// comment 1
/* comment 2 */ display: "grid" /* comment 4 */, // comment 5
gridTemplate: `
"sourceselect . reviewbutton" auto
"pagination filters filters " auto
"rowcount filters filters " 20px
/ 2fr 1fr 2fr
`,
width: 200,
},
containerFlex: {
display: "flex",
},
content: {
lineHeight: 1.5,
},
header: {
backgroundColor: "#ccc",
color: hexToRgbA(colors.danger, 0.8),
display: "inline-block",
":hover": {
color: colors.primary,
borderColor: `${colors.info} !important`,
},
},
// comment a
warning: {
fontWeight: 700,
color: colors.warning,
opacity: 0,
} /* comment b */, // comment c
});
After¶
./example/src/MyComponent.tsx
:
import myStyles from "./MyComponent.module.css";
import classNames from "classnames";
import React from "react";
import { colors } from "./constants";
import { hexToRgbA } from "./utils";
export default function MyComponent() {
const isSomething = true;
const isSomethingElse = false;
return (
<div
className={
isSomethingElse ? myStyles.containerGrid : myStyles.containerFlex
}
style={{}}
>
<div
className={
// TODO: check CSS precedence
classNames(myStyles.header, myStyles.content)
}
>
header
</div>
<div className={classNames(myStyles.content, "another-class")}>
<div>Lorem ipsum</div>
</div>
<span className={classNames(isSomething && myStyles.warning)}></span>
</div>
);
}
export { myStyles };
./example/src/MyComponent.module.css
:
/* comment I */
.containerGrid {
background-color: white;
/* comment 1 */
/* comment 2 */
display: grid; /* comment 4 */ /* comment 5 */
grid-template:
"sourceselect . reviewbutton" auto
"pagination filters filters " auto
"rowcount filters filters " 20px
/ 2fr 1fr 2fr
;
width: 200px;
}
.containerFlex {
display: flex;
}
.content {
line-height: 1.5;
}
.header {
background-color: #ccc;
color: var(--bs-danger-alpha80);
display: inline-block;
}
.header:hover {
color: var(--bs-primary);
border-color: var(--bs-info) !important;
}
/* comment a */
.warning {
font-weight: 700;
color: var(--bs-warning);
opacity: 0;
} /* comment b */ /* comment c */
JS context file¶
The expressions in the styles object (e.g. colors.danger
, hexToRgbA(colors.danger, 0.8)
, etc.) were evaluated using the following "context" file.
./context.example.js
:
const colors = {
danger: "var(--bs-danger)",
info: "var(--bs-info)",
primary: "var(--bs-primary)",
warning: "var(--bs-warning)",
};
function hexToRgbA(hex, alpha) {
return hex.replace(/\)$/, `-alpha${alpha * 100})`);
}
Related posts
- How to use ast-grep with GraphQL — posted 2024-09-24
- Next.js App Router (RSC) projects w/ open source code — posted 2024-07-30
- Next.js Relay GraphQL Pokemon example — posted 2024-05-22
- Example Node.js Passport.js SAML app using OneLogin — posted 2024-05-10
- Simple codemod example with jscodeshift — posted 2021-05-03