-
Notifications
You must be signed in to change notification settings - Fork 1
Add migration guide from styled-components to vanilla-extract #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| { | ||
| "tabWidth": 4, | ||
| "printWidth": 140, | ||
| "singleQuote": true, | ||
| "proseWrap": "always", | ||
| "trailingComma": "all" | ||
| "useTabs": true, | ||
| "tabWidth": 4, | ||
| "printWidth": 140, | ||
| "singleQuote": true, | ||
| "proseWrap": "always", | ||
| "trailingComma": "all" | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -24,9 +24,9 @@ export const categories = [ | |||||
| description: '', | ||||||
| }, | ||||||
| { | ||||||
| id: CategoryId.REACT, | ||||||
| title: 'React Ecosystem', | ||||||
| description: 'Thoughts about the React ecosystem, mostly on building declarative UIs and handling state.', | ||||||
| id: CategoryId.FRONTEND, | ||||||
| title: 'Frontend Web Development', | ||||||
| description: 'Thoughts about the frontend web development, including the React ecosystem, CSS libraries and everything in between.', | ||||||
|
||||||
| description: 'Thoughts about the frontend web development, including the React ecosystem, CSS libraries and everything in between.', | |
| description: 'Thoughts about frontend web development, including the React ecosystem, CSS libraries and everything in between.', |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| --- | ||
| title: Migrating from styled-components to vanilla-extract | ||
| date: 2023-06-04 | ||
| spoiler: Some learnings after migrating a codebase from styled-components into vanilla-extract. | ||
| category: frontend | ||
| --- | ||
|
|
||
| Here are some learnings from migrating a codebase from [styled-components](https://styled-components.com/) to | ||
| [vanilla-extract](https://vanilla-extract.style/). Some of the migration steps were not super clear in the official docs, so hopefully | ||
| you'll find this useful. | ||
|
|
||
| The main motivation for this migration was to move from a model in which styles were generated at runtime to one where they were generated | ||
| during build time. For a small and simple codebase this change might not be noticeable, but for a large scale enterprise web application | ||
| this could potentially be a significant performance improvement. | ||
|
|
||
| ## Configuring vanilla-extract to work with Gatsby and Jest | ||
|
|
||
| The first step was to configure Gatsby to work with vanilla-extract, which involves installing and adding `gatsby-plugin-vanilla-extract` to | ||
| the collection of plugins in the `gatsby-config.js` file. | ||
|
|
||
| These are the dependencies I had to install to get this to work: | ||
|
|
||
| ``` | ||
| yarn add @vanilla-extract/css @vanilla-extract/jest-transform @vanilla-extract/webpack-plugin gatsby-plugin-vanilla-extract | ||
| ``` | ||
|
|
||
| This is all you need to get the tool to work with Gatsby. However, if you try to run your unit tests (in my case, powered by Jest and | ||
| Enzyme) they will fail. To fix this you'll need to | ||
| [configure vanilla-extract's Babel transformer](https://vanilla-extract.style/documentation/test-environments/#jest) on all `*.css.ts` | ||
| files, and make sure this transformer gets called before `babel-jest` as this does not know anything about vanilla-extract, so you need to | ||
| transform all style definitions first before moving on to the rest of the code. | ||
|
|
||
| You'll also need to | ||
| [remove any stubbing of `*.css` files](https://vanilla-extract.style/documentation/test-environments/#remove-style-mocking) as part of the | ||
| `moduleNameMapper` map, as this clashes with vanilla-extract because Jest can't differentiate between `.css` and `.css.ts` imports. | ||
|
|
||
| This is what the `jest.config.js` file looks like after these changes: | ||
|
|
||
| ```js {diff} | ||
| module.exports = { | ||
| transform: { | ||
| + '\\.css\\.ts$': '@vanilla-extract/jest-transform', | ||
| '^.+\\.[jt]sx?$': 'babel-jest', | ||
| }, | ||
| moduleNameMapper: { | ||
| - '.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`, | ||
| '.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `<rootDir>/__mocks__/file-mock.js`, | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| There's something else you need to consider regarding whether you need styles to be available while running your tests. While sometimes you | ||
| might need to test against your actual styles, it can potentially be a major slowdown. If your tests don't require styles to be available, | ||
| you can | ||
| [import `disableRuntimeStyles` to prevent all style creation](https://vanilla-extract.style/documentation/test-environments/#disabling-runtime-styles). | ||
|
|
||
| ```js | ||
| import '@vanilla-extract/css/disableRuntimeStyles'; | ||
| ``` | ||
|
|
||
| Make sure to run your tests and also the `gatsby build` to make sure everything is still working. | ||
|
|
||
| Here's the [link to the first commit](https://github.com/fed/blog/commit/69e3b183dca75471a63ad1ecf740f56c4bfa86bb) where I set up the tool. | ||
|
|
||
| ## Migrating a styled-component with different variants | ||
|
|
||
| ```ts | ||
| export const Lozenge = styled.span<{ children: ReactNode; $type?: LozengeType }>` | ||
| border-radius: ${borderRadius.default}; | ||
| color: ${colors.white}; | ||
| display: inline-block; | ||
| font-family: ${fontFamilies.sansSerif}; | ||
| font-size: ${fontSizes.xxs}; | ||
| line-height: ${lineHeights.sm}; | ||
| padding: ${0.375 * gridSize}px ${0.625 * gridSize}px; | ||
| text-transform: uppercase; | ||
| white-space: nowrap; | ||
|
|
||
| ${(props) => { | ||
| switch (props.$type) { | ||
| case 'primary': | ||
| return css` | ||
| background-color: ${colors.blue}; | ||
| `; | ||
| case 'success': | ||
| return css` | ||
| background-color: ${colors.green}; | ||
| `; | ||
| case 'error': | ||
| return css` | ||
| background-color: ${colors.red}; | ||
| `; | ||
| case 'warning': | ||
| return css` | ||
| background-color: ${colors.yellow}; | ||
| color: ${colors.brown}; | ||
| `; | ||
| case 'info': | ||
| return css` | ||
| background-color: ${colors.purple}; | ||
| `; | ||
| case 'default': | ||
| default: | ||
| return css` | ||
| background-color: ${colors.grayMedium}; | ||
| `; | ||
| } | ||
| }}} | ||
| `; | ||
|
Comment on lines
+102
to
+109
|
||
| ``` | ||
|
|
||
| into the following, which reads nicely: | ||
|
|
||
| ```ts | ||
| import { style, styleVariants } from '@vanilla-extract/css'; | ||
| import { borderRadius, colors, fontFamilies, fontSizes, gridSize, lineHeights } from '../styles/constants'; | ||
|
|
||
| const base = style({ | ||
| borderRadius: borderRadius.default, | ||
| color: colors.white, | ||
| display: 'inline-block', | ||
| fontFamily: fontFamilies.sansSerif, | ||
| fontSize: fontSizes.xxs, | ||
| lineHeight: lineHeights.sm, | ||
| padding: `${0.375 * gridSize}px ${0.625 * gridSize}px`, | ||
| textTransform: 'uppercase', | ||
| whiteSpace: 'nowrap', | ||
| }); | ||
|
|
||
| export const lozenge = styleVariants({ | ||
| default: [base, { backgroundColor: colors.grayMedium }], | ||
| primary: [base, { backgroundColor: colors.blue }], | ||
| success: [base, { backgroundColor: colors.green }], | ||
| error: [base, { backgroundColor: colors.red }], | ||
| warning: [base, { backgroundColor: colors.yellow, color: colors.brown }], | ||
| info: [base, { backgroundColor: colors.purple }], | ||
| }); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,12 @@ | ||
| import { style, globalStyle } from '@vanilla-extract/css'; | ||
|
|
||
| import { | ||
| baseParagraphStyleImpl, | ||
| baseHeadingStyleImpl, | ||
| baseLinkStyleImpl, | ||
| baseLinkHoverStyleImpl, | ||
| baseFocusStyleImpl, | ||
| baseFocusHoverStyleImpl, | ||
| baseParagraphStyleImpl, | ||
| baseHeadingStyleImpl, | ||
| baseLinkStyleImpl, | ||
| baseLinkHoverStyleImpl, | ||
| baseFocusStyleImpl, | ||
| baseFocusHoverStyleImpl, | ||
| } from '../styles/common.css'; | ||
| import { fontFamilies, fontSizes, lineHeights, fontWeights, gridSize, borderRadius } from '../styles/constants'; | ||
|
|
||
|
|
@@ -23,55 +23,56 @@ globalStyle(`${markdownStyle} a:focus:hover`, baseFocusHoverStyleImpl); | |
|
|
||
| // Headings | ||
| globalStyle( | ||
| `${markdownStyle} h1, ${markdownStyle} h2, ${markdownStyle} h3, ${markdownStyle} h4, ${markdownStyle} h5, ${markdownStyle} h6`, | ||
| { | ||
| ...baseHeadingStyleImpl, | ||
| fontWeight: fontWeights.bold, | ||
| margin: `${6.25 * gridSize}px 0 0`, | ||
| }, | ||
| `${markdownStyle} h1, ${markdownStyle} h2, ${markdownStyle} h3, ${markdownStyle} h4, ${markdownStyle} h5, ${markdownStyle} h6`, | ||
| { | ||
| ...baseHeadingStyleImpl, | ||
| fontWeight: fontWeights.bold, | ||
| margin: `${6.25 * gridSize}px 0 0`, | ||
| }, | ||
| ); | ||
|
|
||
| // Bold text | ||
| globalStyle(`${markdownStyle} b, ${markdownStyle} strong`, { | ||
| fontWeight: fontWeights.bold, | ||
| fontWeight: fontWeights.bold, | ||
| }); | ||
|
|
||
| // Images | ||
| globalStyle(`${markdownStyle} img`, { | ||
| maxWidth: '100%', | ||
| maxWidth: '100%', | ||
| }); | ||
|
|
||
| // Add some vertical spacing to all images | ||
| globalStyle(`${markdownStyle} .gatsby-resp-image-figure, ${markdownStyle} p > .gatsby-resp-image-wrapper`, { | ||
| marginBottom: `${6 * gridSize}px`, | ||
| marginTop: `${6 * gridSize}px`, | ||
| marginBottom: `${6 * gridSize}px`, | ||
| marginTop: `${6 * gridSize}px`, | ||
| }); | ||
|
|
||
| // Figure captions | ||
| globalStyle(`${markdownStyle} .gatsby-resp-image-figcaption`, { | ||
| ...baseParagraphStyleImpl, | ||
| fontSize: fontSizes.xs, | ||
| marginTop: `${gridSize}px`, | ||
| textAlign: 'center', | ||
| ...baseParagraphStyleImpl, | ||
| fontSize: fontSizes.xs, | ||
| marginTop: `${gridSize}px`, | ||
| textAlign: 'center', | ||
| }); | ||
|
|
||
| // All code | ||
| globalStyle(`${markdownStyle} pre, ${markdownStyle} code, ${markdownStyle} kbd, ${markdownStyle} samp`, { | ||
| fontFamily: fontFamilies.monospace, | ||
| fontFamily: fontFamilies.monospace, | ||
| }); | ||
|
|
||
| // Inline code | ||
| globalStyle(`${markdownStyle} code:not([class="grvsc-code"])`, { | ||
| backgroundColor: 'rgba(27, 31, 35, 0.05)', | ||
| borderRadius: borderRadius.default, | ||
| fontSize: '80%', | ||
| margin: 0, | ||
| padding: `${0.375 * gridSize}px ${0.75 * gridSize}px`, | ||
| whiteSpace: 'normal', | ||
| fontWeight: fontWeights.normal, | ||
| backgroundColor: 'rgba(27, 31, 35, 0.05)', | ||
| borderRadius: borderRadius.default, | ||
| fontSize: '80%', | ||
| margin: 0, | ||
| padding: `${0.375 * gridSize}px ${0.75 * gridSize}px`, | ||
| whiteSpace: 'nowrap', | ||
| fontWeight: fontWeights.normal, | ||
|
Comment on lines
64
to
+71
|
||
| }); | ||
|
|
||
| // Code blocks | ||
| globalStyle(`${markdownStyle} pre.grvsc-container`, { | ||
| lineHeight: lineHeights.lg, | ||
| lineHeight: lineHeights.lg, | ||
| tabSize: 4, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ export enum CategoryId { | |||||||
| JAVASCRIPT = 'javascript', | ||||||||
| TESTING = 'testing', | ||||||||
| ACCESSIBILITY = 'accessibility', | ||||||||
|
||||||||
| ACCESSIBILITY = 'accessibility', | |
| ACCESSIBILITY = 'accessibility', | |
| REACT = 'react', |
Copilot
AI
Mar 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renaming the category id from 'react' to 'frontend' also requires updating test fixtures that assert on the serialized categoryId string (e.g. src/templates/index.test.tsx currently expects categoryId: 'react'). Without updating those, the Jest suite will fail.
| FRONTEND = 'frontend', | |
| FRONTEND = 'react', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR changes formatting defaults repo-wide (
useTabs: true). That’s a broad, potentially high-churn change that isn’t implied by the PR title (migration guide) and will affect every future formatted file. If the intent is only to fix a single file’s formatting, consider reverting this config change or scoping it to specific filetypes via overrides.