How I managed to use SCSS inside Web Components
Tags: showdev, css, webdev, sass
Published: 2020-05-01 00:32:17

A couple of years ago, I remember rushing to learn the fourth version of Angular to get up to speed with "moderns" web frameworks. About when I got okay-ish with it, we started writing more and more React at work, so I switched onto it. Then emerged Vue.js, svelte, and many others. Modern frameworks keep being easier to implement, to use, and being lighter. At some extend, the more they evolve, the closer they get to barebone Javascript. During this time, another concept is making its way quietly: Web Components. Why are we so reticent to start daily drive this nice technology? I asked myself the question, and tried it for a while. Let me walk through my road blocks, and how I managed to get over each one of them.

For a while now, I have been reading quite a bit on Web Components and even though it's still at early stages (let's be honest), the potential of this concept truly is insane. Like any developer, I quickly dropped my in-progress project to play around with Web Components and realized how amazing they are. Soon enough I stumbled upon three deal breakers (or challenges, you pick!) before making Web Components my go-to technology:
* It's not supported by every browser
* I miss Typescript
* Reusing CSS is pretty much impossible in the Shadow DOM

So I started digging, and you might also probably know, but it is pretty trivial to make web components supported in every modern browsers with Polyfills, and adding Typescript to Web Components is very possible (my personal stack to transpile to CommonJS is `babel-loader` inside webpack).

The part I scratched my head a bit more is to reuse CSS to keep my code as DRY (Don't Repeat Yourself) as possible. Plus, working without SCSS would be too much of a bummer for me to move forward with Web Components. There had to be a way.

## What exactly is the issue?
We need a proper example here. When you create a Web Component with a Shadow Root, you can't just hook it with a `.scss` file like in React or Angular. You need to pass the *actual* plain boring CSS to a created `style` element.

```ts
class CatViewer extends HTMLElement {
  constructor() {
    super();

    const style = document.createElement('style');
    style.textContent = `
      p {
        color: blue;
      }
    `;
    const shadowRoot = this.attachShadow({mode: 'open'});
    const paragraph = document.createElement('p');
    paragraph.textContent = 'Here is some blue text.';

    shadowRoot.appendChild(style);
    shadowRoot.appendChild(paragraph);
  }
}
```
**Note that you can attach a stylesheet to a Shadow DOM, but that implies your stylesheet won't be bundled in your component. This is exactly why this solution does not work for me**

So after hours of googling, I could not find much, except having something more less like CSS-in-JS, bleeding [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties), or the deprecated [@apply rule](http://tabatkins.github.io/specs/css-apply-rule/). _There had to be a way_.

## Ever heard of shower thoughts?

To be honest, I am not sure if I was about to give up, or ready to put this Web Component thing on the ice again, but I had a shower thought: `What if there was a way of compiling a SCSS file, and retrieve its result as a javascript string?` Easy enough! To do so, a simple webpack loader could do the trick just fine.

That's how I ended up creating my first webpack loader: [sass-to-string](https://github.com/m4thieulavoie/sass-to-string) and started dogfooding it just to realize how convenient it was (for me at least) to use SCSS inside plain Web Components.

### Installation

First off, install the dependency

```
npm install sass-to-string --save-dev
```

Then, all you need to do is add this webpack config
```js
module.exports = {
  module: {
    rules: [
      {
        test: /\.styles.scss$/,
        exclude: /node_modules/,
        use: [
          "sass-to-string",
          {
            loader: "sass-loader",
            options: {
              sassOptions: {
                outputStyle: "compressed",
              },
            },
          },
        ],
      },
    ],
  },
};
```

If you already had an existing `.scss` config, you'll want to specify an `exclude` statement in it

```js
module.exports = {
module: {
  rules: [
      {
          // Your new shiny sass-to-string config
      },
    {
      test: /\.(scss|css)$/,
      // Excluding the `.styles.scss` extension
      exclude: [/\.styles.scss$/, /node_modules/],
      use: [
        "style-loader",
        MiniCssExtractPlugin.loader,
        "css-loader",
        {
          loader: "sass-loader",
        },
      ],
    },
  ];
}
}
```
And that's pretty much it!

Once this is done, the usage is pretty simple : you create a `*.styles.scss` file alongside your component (or wherever you want, who am I to judge?) and import it, like such

```ts
import styles from './CatViewer.styles.scss';
```

This will give you a string containing the compiled CSS. Then, all you need to do is to inject it inside a style.

Let's assume `CatViewer.styles.scss` contains the following:

```scss
p {
  color: blue;
  
  span {
    color: pink;
  }
}
```

The variable `styles` would end up with the following value: 
```css
p {
  color: blue;
}
p span {
  color: pink;
}
```
I know this is a stupid example, but you get the point! Let's take this into our component above.

```ts
import styles from './CatViewer.styles.scss';

class CatViewer extends HTMLElement {
  constructor() {
    super();

    const style = document.createElement('style');
    style.textContent = styles;
    const shadowRoot = this.attachShadow({mode: 'open'});
    const paragraph = document.createElement('p');
    paragraph.innerHTML = 'Here is some blue text with some <span>pink</span>.';

    shadowRoot.appendChild(style);
    shadowRoot.appendChild(paragraph);
  }
}
```

And bam! We just took a shiny `.scss` file, imported it as plain CSS in a Web Component class. If you did not notice already, since `sass-to-string` relies on Webpack, it supports HMR (Hot Module Reload)!

At the moment of writing those lines, I've been using this tool almost daily since about a month both at home and at work and it's been great so far. This simple loader added a lot more weight into the consideration of moving our future code into Web Components at work. Being able to re-use CSS in our components is a key for having a nice and clean CSS codebase. Not only that, but it also allows us to move away all the CSS code in a separate file, helping the separation of concepts at the same time.

I really hope this article was helpful, or at least interesting. I spent many hours reading and messing around with Web Components and they are totally worth a shot. I know this article sounds like I'm trying to sell my package, but that's not it at all. I want to share my thought process on how I went from "Yeah impossible to do it" to "It's actually fairly easy to do!". We, developers, sometimes tend to forget our job is to create things from scratch, literally.

---
Cover by [Ryan Quintal](https://unsplash.com/@ryanquintal?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash