|
| 1 | +--- |
| 2 | +id: css-codemods |
| 3 | +title: CSS codemods via PostCSS |
| 4 | +slug: /css-codemods |
| 5 | +--- |
| 6 | + |
| 7 | +In some cases, it's possible that you may need to write a codemod that applies changes across different programming languages JS, CSS, etc. |
| 8 | +It could be because the package you're writing a codemod for has an API that spans across both JS and CSS, for example a Design System or CSS-in-JS library. Where some of your consumers may be using the JS interface and some the CSS interface. |
| 9 | + |
| 10 | +In this scenario, it's possible to repurpose JSCodeshift to handle this by treating JSCodeshift purely as a "Runner", or in otherwords, as the entrypoint to the files you're looking to modify and substitute a transformation library of your choice. |
| 11 | +For example [PostCSS](https://postcss.org/), [Babel](https://babeljs.io/), etc. |
| 12 | + |
| 13 | +However, this does come with drawbacks, you will no longer have access to JSCodeshift parsers and transformation API. This guide will explain how to handle these yourself. |
| 14 | + |
| 15 | +As an example, We'll take the JS/CSS use-case and use the popular [PostCSS](https://postcss.org/) library as our substitute transformation library. |
| 16 | + |
| 17 | +## Step 1: Installing dependencies |
| 18 | + |
| 19 | +Get started by creating a new Codeshift package with `npx @codeshift/cli init --package-name css-codemod --preset update-css-api .`. |
| 20 | + |
| 21 | +This will create a new Codeshift package with a configuration file and empty transform file for the preset you specified. |
| 22 | + |
| 23 | +Navigate into your new directory with `cd css-codemod` and install the relevant dependencies: `npm install -s postcss` |
| 24 | + |
| 25 | +## Step 2: Parsing |
| 26 | + |
| 27 | +Now navigate to your transformer file, which should look something like this: |
| 28 | + |
| 29 | +```js |
| 30 | +export default function transformer(file, { jscodeshift: j }, options) { |
| 31 | + const source = j(file.source); |
| 32 | + |
| 33 | + // Transformation goes here |
| 34 | + |
| 35 | + return source.toSource(options.printOptions); |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +You can remove the following lines: |
| 40 | + |
| 41 | +```diff |
| 42 | +export default function transformer( |
| 43 | + file, |
| 44 | +- { jscodeshift: j }, |
| 45 | +- options |
| 46 | +) { |
| 47 | +- const source = j(file.source); |
| 48 | + |
| 49 | + // Transformation goes here |
| 50 | + |
| 51 | +- return source.toSource(options.printOptions); |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +Now that we've removed JSCodeshift's parsing and output API, we can substitute PostCSS: |
| 56 | + |
| 57 | +```diff |
| 58 | ++ import postcss from 'postcss'; |
| 59 | + |
| 60 | +export default function transformer(file) { |
| 61 | ++ return await postcss([plugin()]).process(file.source).css; |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +Here we set up PostCSS with a `plugin()` (more on that later) and pass in the raw file `file.source` for processing. |
| 66 | +Once processing is complete we return the raw file back to JSCodeshift for output via `.css`. |
| 67 | + |
| 68 | +## Step 3: Transformation |
| 69 | + |
| 70 | +Now that we've setup parsing we can turn our attention to transformation. The way that can be done in PostCSS is via [their plugin system](https://github.com/postcss/postcss/blob/main/docs/writing-a-plugin.md). |
| 71 | + |
| 72 | +For example purposes, our transformation will simply reverse the names of CSS declarations like so: |
| 73 | + |
| 74 | +```diff |
| 75 | +.my-class { |
| 76 | +- background: red; |
| 77 | ++ dnuorgkcab: red; |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +Very useful. Let's create the `plugin()` function that does this now. |
| 82 | + |
| 83 | +To assist with writing PostCSS plugins, you can use [astexplorer.net](https://astexplorer.net/#/2uBU1BLuJ1). |
| 84 | + |
| 85 | +```js |
| 86 | +const plugin = (): Plugin => { |
| 87 | + const processed = Symbol('processed'); |
| 88 | + |
| 89 | + return { |
| 90 | + postcssPlugin: 'UsingTokens', |
| 91 | + Declaration: decl => { |
| 92 | + if (decl[processed]) return; |
| 93 | + |
| 94 | + decl.prop = decl.prop |
| 95 | + .split('') |
| 96 | + .reverse() |
| 97 | + .join(''); |
| 98 | + |
| 99 | + decl[processed] = true; |
| 100 | + }, |
| 101 | + }; |
| 102 | +}; |
| 103 | +``` |
| 104 | + |
| 105 | +## Step 4: Running |
| 106 | + |
| 107 | +You've created your very first CSS codemod, nice work! We can now run it against some code to verify that's it's working correctly. |
| 108 | + |
| 109 | +``` |
| 110 | +npx @codeshift/cli -t css-codemod/src/update-css-api.ts -e css path/to/src |
| 111 | +``` |
0 commit comments