Solid Sapper setup with PostCSS and Tailwind
Tags: svelte, javascript, webdev, tailwindcss
Published: 2020-09-08 06:49:17

---
title: Solid Sapper setup with PostCSS and Tailwind
published: true
date: 2020-09-06 00:00:00 UTC
tags: svelte,javascript,webdev,tailwindcss
canonical_url: https://codechips.me/sapper-with-postcss-and-tailwind/
---
While it's straight forward to integrate PostCSS and Tailwind into plain Svelte projects, Sapper is a totally different beast. There are many moving parts. Rollup configuration is super complex, code is generated. It can be hard to grasp what's going on.

## The Problem

I needed to integrate [PostCSS](https://postcss.org/) together with [Sapper](https://sapper.svelte.dev/). Meanwhile it's not so hard to integrate plain [Tailwind CSS](https://tailwindcss.com/) into Sapper, turns out integrating PostCSS together with TailwindCSS requires a little more work. After trying a few different approaches I finally landed on something that works for me.

## Why PostCSS?

Plain CSS can take you far, but I often prefer to use Tailwind CSS. I find it really nice to work with declarative CSS instead of writing everything from scratch. I like Tailwind as it is, but I often use a few other PostCSS plugins too that help me work with Tailwind CSS more efficiently. Maybe a better word would be "augment" and not "help."

## How Sapper manages CSS

Sapper has an internal router built-in. Which is helpful. The router intercepts all link clicks and fetches each page individually when you visit it. When you click on the link that leads to another page in your app, Sapper will fetch the page in the background and replace the content in your Sapper app.

It will actually put the content into the `slot` in the `src/routes/_layout.svelte` page. That's how it's setup in the official boilerplate at least.

Sapper injects the styles for different components and pages when you navigate between the pages. When you visit a page, Sapper will fetch that page and also inject the style for that page and for the components it uses into the head tag of the document.

Sapper and Svelte scope CSS classes defined in the components to the components themselves, reducing the risk of overriding the CSS.

To understand more read the blog post [The zen of Just Writing CSS](https://svelte.dev/blog/the-zen-of-just-writing-css).

It's actually a really nice feature that you get out of the box in Svelte! You can see that by inspecting elements in the dev tools console. Each styled element will have a `svelte-[hash]` class defined on it.

## The Solution

After wrestling with [rollup-plugin-postcss](https://github.com/egoist/rollup-plugin-postcss) for some time, I gave up and went with the simplest setup possible.

Instead of trying to integrate PostCSS into Rollup itself, I moved PostCSS processing outside of Rollup's pipeline. It's fast too, because the processing is done outside Rollup.

Here is how I did it.

### Create a Sapper project

In order to fully understand what's needed, we will start from scratch by creating a [standard Sapper project](https://github.com/sveltejs/sapper-template).

```text
$ npx degit sveltejs/sapper-template#rollup sapper-with-postcss
$ cd sapper-with-postcss && npm i
```
You can now start the app by running `npm run dev`.

## Setting up Tailwind

Let's add Tailwind and Tailwind's [typography](https://github.com/tailwindlabs/tailwindcss-typography) plugin that we will use to style the blog posts.

```text
$ npm add -D tailwindcss @tailwindcss/typography
$ npx tailwindcss init
```

We now need to replace Tailwind's configuration file with this.

```js
// tailwind.config.js

module.exports = {
  future: {
    removeDeprecatedGapUtilities: true,
  },
  experimental: {
    uniformColorPalette: true,
    extendedFontSizeScale: true,
    // currently Sapper dev server chokes on this
    // applyComplexClasses: true,
  },
  purge: {
    // needs to be set if we want to purge all unused
    // @tailwind/typography styles
    mode: 'all',
    content: ['./src/**/*.svelte', './src/**/*.html'],
  },
  theme: {
    container: {
      center: true,
    },
    extend: {},
  },
  variants: {},
  plugins: [require('@tailwindcss/typography')],
};
```

Next thing we need to do is to create Tailwind's base file. We will put it in `src/assets` folder, that you need to create first, and we will name it `global.pcss`.

We are using `.pcss` extension just to distinguish that it's a PostCSS file. It's not something you have to do. Plain `.css` extension works just a good. I like to distinguish PostCSS files from plain CSS.

```scss
/* global.pcss */

@tailwind base;

body {
  @apply bg-indigo-100;
}

@tailwind components;
@tailwind utilities;
```

Alright. Now that we are done with Tailwind configuration, let's wire it up into our PostCSS pipeline.

### Setting up PostCSS with Tailwind

First things first. We need to install PostCSS cli and a few PostCSS plugins that we will use.

```text
$ npm add -D postcss-cli
$ npm add -D autoprefixer postcss-import cssnano postcss-load-config postcss-preset-env
```

Next, we need to create PostCSS configuration file in the project's root folder.

```js
// postcss.config.js

const tailwind = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const postcssImport = require('postcss-import');
const presetEnv = require('postcss-preset-env')({
  features: {
    // enable nesting
    'nesting-rules': true,
  },
});

const plugins =
  process.env.NODE_ENV === 'production'
    ? [postcssImport, tailwind, presetEnv, cssnano]
    : [postcssImport, tailwind, presetEnv];

module.exports = { plugins };
```

Cool! We are almost there. Theoretically, we have everything we need. We just need to wire everything up.

## PostCSS in Svelte files

Actually, I forgot something. We want to style our Svelte components with Tailwind and PostCSS too. In order for that to work we need to use the good ol' `svelte-preprocess` plugin.

```text
$ npm add -D svelte-preprocess
```

Let's cheat a bit. We will create a `svelte.config.js` and setup the preprocessor there. Svelte config is needed for the editors to be able to work correctly. Syntax highlighting, intellisense and all those things.

We will later re-use the exported preprocessor in our Rollup config to keep things DRY.

```js
// svelte.config.js

import autoProcess from 'svelte-preprocess';

export const preprocess = autoProcess({ postcss: true });

```
There are a few different ways to setup Svelte prepocessor, but I found this the most minimal. The reason it works is that we installed [postcss-load-config](https://github.com/michael-ciniawsky/postcss-load-config) plugin earlier. It will automatically load `postcss.config.js` file if it exists. No need to require it in our code!

Now that we have finished setting up the preprocessor, we need to import it in our Rollup config.

```js
// rollup.config.js

import { preprocess } from './svelte.config';

// add preprocess to Svelte plugin in client section
svelte({
  dev,
  hydratable: true,
  emitCss: true,
  preprocess, // <-- add this
}),

// add preprocess to Svelte plugin in server section
svelte({
  generate: 'ssr',
  hydratable: true,
  dev,
  preprocess, // <-- add this
})
```

Phew! Everything is now configured correctly. Hopefully.

### Adjust your NPM scripts

Last thing we need to do is to wire everything together. We will do it by changing the `scripts` section in our `package.json`.

```json
"scripts": {
  "dev": "run-p watch:*",
  "watch:css": "postcss src/assets/global.pcss -o static/global.css -w",
  "watch:dev": "sapper dev",
  "build": "run-s build:css build:sapper",
  "build:css": "NODE_ENV=production postcss src/assets/global.pcss -o static/global.css",
  "build:sapper": "sapper build --legacy",
  "build:export": "sapper export --legacy",
  "export": "run-s build:css build:export",
  "start": "node __sapper__/build",
  "serve": "serve ___sapper__/export",
  "cy:run": "cypress run",
  "cy:open": "cypress open",
  "test": "run-p --race dev cy:run"
}
```

This requires some explanation. You can see that we have a `watch:css` script. What it does is replaces Sappers `static/global.css` with our Tailwind base file. We also need to explicitly set `NODE_ENV` to "production" in `build:css` since we are doing our PostCSS processing outside Sapper. It's needed by Tailwind in order to purge unused CSS styles from its base file.

Be careful not to set `NODE_ENV` to production in the Sapper build and export scripts. If you do, and you set any `:global` styles in your components, they will be purged leading to missing styles.

Oh, just another tip. If you what to use a background image in you CSS put it in the `static` folder. You can then use it your CSS like this.

```scss
.hero {
  background-image(image.png);
}
```
## Test-driving the new setup

To check that Tailwind and PostCSS works in Svelte components, replace your `src/routes/index.svelte` with this code.

```html
<!-- index.svelte -->

<style lang="postcss">
  .btn {
    @apply bg-red-500 text-red-100 uppercase tracking-wide font-semibold
      text-4xl px-4 py-3 shadow-lg rounded;
  }
</style>

<svelte:head>
  <title>Sapper project template</title>
</svelte:head>

<div class="space-y-10 text-center">
  <h1 class="text-7xl uppercase font-bold">Great success!</h1>
  <button class="btn">DO NOT PRESS THIS BUTTON!</button>
</div>
```
You can see that we set `lang="postcss"` in the style tag. That's not something that is required, PostCSS will still be processed. It's only so that editor understands it's dealing with PostCSS.

To see Tailwind's typography plugin in action change `src/routes/blog/[slug].svelte` to the code below.

```html
<script context="module">
  export async function preload({ params, query }) {
    const res = await this.fetch(`blog/${params.slug}.json`);
    const data = await res.json();

    if (res.status === 200) {
      return { post: data };
    } else {
      this.error(res.status, data.message);
    }
  }
</script>

<script>
  export let post;
</script>

<svelte:head>
  <title>{post.title}</title>
</svelte:head>

<!-- prose is a class from Tailwind typography plugin -->
<div class='prose prose-lg'>
  <h1>{post.title}</h1>

  {@html post.html}
</div>
```

And ... we are finally done!

## Demo

Below you can see the setup in action running on Vercel. Make sure to check out individual blog posts to see Tailwind's typography plugin in action.

Oh, and please don't press that button. Don't say I didn't warn you!

![sapper with postcss and tailwind](https://res.cloudinary.com/codechips/image/upload/v1599381878/posts/sapper-postcss-tailwind_dcsvss.webp)

Live demo: https://sapper-with-postcss-and-tailwind.vercel.app

## Mentioned and used plugins

- https://github.com/cssnano/cssnano
- https://github.com/csstools/postcss-preset-env
- https://github.com/egoist/rollup-plugin-postcss
- https://github.com/pirxpilot/postcss-cli
- https://github.com/postcss/autoprefixer
- https://github.com/postcss/postcss-import
- https://github.com/sveltejs/sapper-template
- https://github.com/sveltejs/svelte-preprocess
- https://github.com/tailwindlabs/tailwindcss
- https://github.com/tailwindlabs/tailwindcss-typography

## Conclusion

Implementing PostCSS in Sapper becomes clear when you understand how Sapper deals with CSS files.

We set up two separate PostCSS pipelines in our example app. First is processing Sapper's global CSS file. Second is replacing Sapper's component styling with PostCSS. We didn't actually change the way Sapper handles and serves CSS files, we only replaced it with PostCSS. Maybe "augmented" is a better word.

You can find the full code here [github.com/codechips/sapper-with-postcss-and-tailwind](https://github.com/codechips/sapper-with-postcss-and-tailwind).

Now go and create some beautifully styled apps!