DEV Community

Cover image for How I managed to use SCSS inside Web Components
Mathieu Lavoie
Mathieu Lavoie

Posted on

How I managed to use SCSS inside Web Components

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.

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, or the deprecated @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 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

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

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

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:

p {
  color: blue;

  span {
    color: pink;
  }
}

The variable styles would end up with the following value:

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.

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 on Unsplash

Top comments (3)

Collapse
 
elyanuki profile image
Yanik Kendler

i might have missed something here, but when adding the import styles from '../../styles/components/button.styles.scss' line in my ts file it shows an error because it cannot find the module.. is there a way to fix this other than to ts supress it?

Collapse
 
richardr91 profile image
richardr91

Finally!!!🙌🏾 Been looking for a way to do this since my most recent interest in the web components api. Thanks!!

Collapse
 
mateusdiasdev profile image
Mateus Dias

Amazing! This the solution that every one needs