Supporting SASS in your TS React project using TSC and esbuild
Tags: react, typescript, css, esbuild
Published: 2022-12-30 09:44:14

What a mouthful, right? “Supporting SASS in your TS React project using TSC and esbuild”... and this is not even the full title which tells what will soon happen here. It’s because it’s a little bit complicated, though the implementation is rather simple. Let me explain…

We all know [SASS](https://sass-lang.com/) and its benefits, and until “native” CSS will support [nesting](https://www.w3.org/TR/css-nesting-1/), to say the least, I don’t see it going anywhere (and SASS has a lot more to offer).

My [Components package](https://github.com/mbarzeev/pedalboard/tree/master/packages/components) currently supports regular plain-old CSS (not that there’s anything wrong with it), and I thought it was a good time to introduce SASS to it, but the package is not a _ordinary_ “Webpack-build-that-s#!t-for-me”. I’m using TSC ([TypeScript Compiler](https://www.typescriptlang.org/docs/handbook/compiler-options.html)) to generate the artifacts - 
What it means is that TSC is compiling 2 versions of the component, ESM and CJS. Once we have these, we’re taking the ESM outcome and bundling it using esbuild. You can read more about it [here](https://dev.to/mbarzeev/hybrid-npm-package-through-typescript-compiler-tsc-150c), but if to put it visually:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xssqwgw91loepyf2ult.png)

You can argue that this does not make much sense for a components package and you'll be right, but we can take this package as an example for a React project written in TS with a build process which results in a JS bundle and a CSS file.

The build script looks like this:

```json
"build": "tsc --project tsconfig.esm.json & tsc --project tsconfig.cjs.json && yarn bundle",
"bundle": "node ../../esbuild.config.js",
```

And the `esbuild.config.js` looks like this:

```javascript
const cssPlugin = require('./esbuild.css.plugin');
 
require('esbuild')
   .build({
       entryPoints: ['dist/esm/index.js'],
       bundle: true,
       minify: true,
       sourcemap: true,
       outfile: 'dist/main/index.js',
       plugins: [cssPlugin],
   })
   .catch(() => process.exit(1));
```

What is this “cssPlugin”?
The CSS plugin here helps us “convert” the CSS files paths, since TSC does not deal with them and therefore they are not to be found under the `dist/esm` directory, so what this custom plugin does is identifying the import and redirect it to where the file is at. This saves us the "copy" of `.css` files to the `dist/esm` directory, but come to think of it, I realize I need to copy these anyhow. not relevant for now ;) 

It’s always good to define our goals before jumping into coding, and in this case:
* Have our component’s style as a SASS file
* Make sure that Storybook (yes, Storybook) is still working as it used to
* Have the esbuild bundling process create the final CSS file from the SASS file, and place it with the JS bundle under the `dist/main` directory  

Before we start, know that you can find all the code under this [GitHub repo](https://github.com/mbarzeev/pedalboard/tree/master/packages/components).

Here we go :)

***

First we rename the `index.css` file into a `index.scss` one. We also change the import in the component accordingly:

```javascript
. . .
import './index.scss';
```

## Storybook
Because I like tormenting myself, I run Storybook to make sure that we’re still getting our CSS styles and sure enough we’re getting an error:

```bash
ModuleParseError: Module parse failed: Unexpected token (8:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|  */
| 
> .selected {
```

Why? Because Storybook does not “know” how to handle this SASS file. In order for it to handle SASS we need to use a plugin called “storybook-addon-sass-postcss”. You can read more details on how to install and use it [here](https://storybook.js.org/addons/storybook-addon-sass-postcss). Once installed and set in the Storybook addons, it appears that Storybook is working as expected.

Now what about the bundling?

## esbuild bundling
For bundling we need to have esbuild take care of our SASS file. If I run the build now, this is the result I’m getting:

```bash
✘ [ERROR] Could not resolve "./index.scss"

    dist/esm/src/Pagination/index.js:4:7:
      4 │ import './index.scss';
        ╵        ~~~~~~~~~~~~~~
```

The plugin I’ve mentioned earlier, which takes care of redirecting the paths for imported CSS, is only looking for `.css`, but we would like it to look for `.scss` as well. I will change the regex for that. Here is the result plugin:

```javascript
const path = require('path');
 
module.exports = {
   name: 'css',
   setup(build) {
       // Redirect all paths css, scss or sass
       build.onResolve({filter: /.\.s[ac]ss$/}, (args) => {
           const path1 = args.resolveDir.replace('/dist/esm', '');
           return {path: path.join(path1, args.path)};
       });
   },
};
```

Running the “build” again and we’re getting another error, but this time it’s encouraging. The error claims that there is no “Loader” to deal with the `.scss` file:

```bash
✘ [ERROR] No loader is configured for ".scss" files: src/Pagination/index.scss

    dist/esm/src/Pagination/index.js:4:7:
      4 │ import './index.scss';
        ╵        ~~~~~~~~~~~~~~
```

Great news :)
For that we need to install a loader for esbuild that can take care of the `.scss` file. We’re going to use the [esbuild-sass-plugin](https://www.npmjs.com/package/esbuild-sass-plugin) to do that. I’m installing it with `yarn add -D esbuild-sass-plugin` and add it to my `esbuild.config.js` like so:

```javascript
const cssPlugin = require('./esbuild.css.plugin');
const {sassPlugin} = require('esbuild-sass-plugin');
 
require('esbuild')
   .build({
       entryPoints: ['dist/esm/index.js'],
       bundle: true,
       minify: true,
       sourcemap: true,
       outfile: 'dist/main/index.js',
       plugins: [cssPlugin, sassPlugin()],
   })
   .catch(() => process.exit(1));
```

Running the build now and Boom! We have it - the `dist/main` directory holds a processed CSS file generated from the `.scss` file. Nice :)

Not that complicated after all. I’m messing a bit with the SASS file, just to make sure the nesting works as expected and indeed it works well:

```javascript
.pagination {
    font-size: large;

    .selected {
        font-weight: bolder;
        color: blue;
    }

    button {
        border: none;
        background-color: aqua;
        border-radius: 5px;
        padding: 10px;

        &:hover {
            background-color: darkmagenta;
        }

        &:active {
            background-color: aqua;
        }

        &:disabled {
            background-color: lightgray;
        }
    }
}
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjfhuthlqtnkub2jbcbh.png)

As mentioned before, you can find all the code under this [GitHub repo](https://github.com/mbarzeev/pedalboard/tree/master/packages/components), and as always, if you have any questions or comments please leave them in the comments section below so that we can all learn from them :)

*Hey! for more content like the one you've just read check out <a href="https://twitter.com/mattibarzeev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false">@mattibarzeev</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> on Twitter* :beers:

<small><small><small>Photo by <a href="https://unsplash.com/@karim_manjra?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Karim MANJRA</a> on <a href="https://unsplash.com/photos/BauovfUhLhY?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></small></small></small>