loading...

[Update] Using Svelte with Tailwindcss - A better approach

sarioglu profile image sarioglu Updated on ・4 min read

Updated 2020/01/27, GitHub link for the Sapper template is added below 🎉

I've been using Tailwind since its early days and it is a complete life changer for me. That's why I tried to use it on a project written using Svelte. Existing methods to combine these two weren't sufficient in terms of developer experience that they have provided. So, I've tried to come up with a better approach. Wish you enjoy reading!

TL;DR

I've combined Svelte's preprocessing feature and PostCSS using svelte-preprocess to handle Tailwind. You can skip the tutorial and use the template that I've published on GitHub:

GitHub logo sarioglu / svelte-tailwindcss-template

Template for building basic applications with Svelte

Looking for a shareable component template? Go here --> sveltejs/component-template

This is a fork of Svelte's project template to enable usage of Tailwindcss. Refer to https://github.com/sveltejs/template for more info.

To create a new project based on this template using degit:

npx degit sarioglu/svelte-tailwindcss-template svelte-app
cd svelte-app

Note that you will need to have Node.js installed.

Get started

Install the dependencies...

cd svelte-app
npm install

...then start Rollup:

npm run dev

Navigate to localhost:5000. You should see your app running. Edit a component file in src, save it, and reload the page to see your changes.

By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the sirv commands in package.json to include the option --host 0.0.0.0.

Building and running in production mode

To create an optimised version of the app:

npm run build

You can run the…

Existing methods

There are several other works to integrate Tailwind into Svelte. You can even find a couple of examples under Tailwind's GitHub account.

However, these methods have some structural weaknesses:

  • They create another pipeline alongside Svelte to process external css. Tailwind will be processed by PostCSS while component styles are being processed by Svelte. That's why developers need to reconsider everything from transpiling to minimization.
  • They make it impossible to use directives of Tailwind (like @apply or @screen) in component styles.
  • They create an auto-generated css file within the codebase.

That's why I've come up with a new approach to make this integration smoother. So let's start with it:

1-Create a Svelte app

First, we need to initialize a Svelte app using the following commands. If you already have an existing one, you can skip this section.

npx degit sveltejs/template [my-svelte-project]
cd [my-svelte-project]

npm install

These commands clone the official Svelte app template and install required dependencies.

2-Initialize Tailwind

Following the previous step, install required dependencies for Tailwind integration using the following command:

npm i -D @fullhuman/postcss-purgecss postcss postcss-load-config svelte-preprocess tailwindcss

Then, run the following command to initialize Tailwind:

npx tailwind init

This will create a file named tailwind.config.js in your codebase. You can edit or replace this file to extend your Tailwind config.

3-Make the integration

In order to make the integration we'll need following two files. We'll use postcss.config.js to configure PostCSS to process styles with Tailwind. Note that PostCSS uses Purgecss to get rid of unused styles in production mode. We'll also need to whitelist css classes generated by Svelte itself since Svelte itself takes are of these.

postcss.config.js

const purgecss = require('@fullhuman/postcss-purgecss')({
  content: [
    './src/**/*.html',
    './src/**/*.svelte'
  ],

  whitelistPatterns: [/svelte-/],

  defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
});

const production = !process.env.ROLLUP_WATCH

module.exports = {
  plugins: [
    require('tailwindcss'),
    ...(production ? [purgecss] : [])
  ]
};

Tailwindcss.svelte file includes a Svelte component which only has a style definition. We'll use it to inject our utility classes into the app. global directive here means that styles of this component will be available globally.

src/Tailwindcss.svelte

<style global>
  @tailwind base;
  @tailwind components;
  @tailwind utilities;
</style>

We need to import this component into our app:

src/App.svelte

<script>
  import Tailwindcss from './Tailwindcss.svelte';
  ...
</script>

...
<Tailwindcss />
...

By doing that, we'll be able to use the classes provided by Tailwind in our app.

Finally, we'll tweak the rollup config to use svelte-preprocess to process components' styles.

rollup.config.js

import sveltePreprocess from 'svelte-preprocess'

...
svelte({
  ...
  preprocess: sveltePreprocess({ postcss: true }),
  ...
})
...

Results

Using this new approach will enable us to benefit from every feature of Tailwind by combining Svelte's preprocessing ability and PostCSS. You can use utility classes, or call directives to combine them into component styles. All those styles will be processed by Svelte without creating additional pipeline.

To demonstrate the outcome let's run the app using npm run dev command and change some styles in App.svelte:

<style>
  h1 {
    @apply bg-black text-white;
  }
</style>

You will see that styles provided by Tailwind are perfectly applied to our mighty Hello world! message. So you can start using them for a better cause!

What about Sapper?

Not a problem! You can apply the same steps to integrate Tailwind into your Sapper app. Just be sure that you've changed both client and server configs.

I've published the Sapper template to GitHub. Since it is based on the official template, you can use either of rollup and webpack setups. Here is the link:

GitHub logo sarioglu / sapper-tailwindcss-template

Starter template for Sapper apps

sapper-template

This is a fork of Sapper's project template to enable usage of Tailwindcss. Refer to Sapper for more info.

Getting started

Using degit

degit is a scaffolding tool that lets you create a directory from a branch in a repository. Use either the rollup or webpack branch in sapper-template:

# for Rollup
npx degit "sarioglu/sapper-tailwindcss-template#rollup" my-app
# for webpack
npx degit "sarioglu/sapper-tailwindcss-template#webpack" my-app

Running the project

However you get the code, you can install dependencies and run the project in development mode with:

cd my-app
npm install # or yarn
npm run dev

Open up localhost:3000 and start clicking around.

Consult sapper.svelte.dev for help getting started.

Structure

Sapper expects to find two directories in the root of your project — src and static.

src

The src directory contains the entry points for your app — client.js, server.js and (optionally) a service-worker.js —…

Other benefits

Using svelte-preprocess to let PostCSS to handle component styles provides various other side benefits. You can use postcss.config.js to import some other PostCSS plugins like autoprefixer, etc. Those plugins will immediately take care of your component styles.

Discussion

pic
Editor guide
Collapse
filip_uzunovic profile image
Case09

Great solution! One thing that i'm still struggling with, i can't seem to use regular classes in my svelte files like <div class="bg-red-200">...</div>. It works if i rerun sapper dev. If i use @apply inside tag it works fine. Btw i added inside _layout.svelte. Any idea how to get this working without having to rerun npm run dev every time?

Collapse
sarioglu profile image
sarioglu Author

I'm not sure why it is happening. I will check this issue when I create the sapper template.

Collapse
srinivasarajui profile image
Srinivasa Raju

I faced the same problem but fixed it with the following change.

Change line12 in postcss.config.js
from : const production = !process.env.ROLLUP_WATCH
to : const production = process.env.NODE_ENV !=='development'

Thread Thread
sarioglu profile image
sarioglu Author

Thanks for the information! In fact, using process.env.NODE_ENV in both setups would be better. However since process.env.ROLLUP_WATCH is used to determine mode in rollup.config.js, I wanted to be consistent with it. I've changed it to process.env.NODE_ENV in Sapper template.

Collapse
mikenikles profile image
Mike Nikles

Thanks for sharing that. I'm having issues with responsive classes such as sm:block. Using it throws an error saying that class doesn't exist. Are others able to use responsive classes with the approach explained in this article?

Collapse
sarioglu profile image
sarioglu Author

Hi Mike,
Thanks for your comment. Unfortunately I couldn't reproduce this issue. But it can be caused by one of following reasons:

Collapse
mikenikles profile image
Mike Nikles

Thanks for your response. I cloned your repo and changed App.svelte's style to the following:

h1 {
  @apply bg-black text-white md:text-red-200;
}

This throws the same error I see in my project:

[!] (plugin svelte) CssSyntaxError: /home/mikenikles/dev/github/sarioglu/svelte-tailwindcss-template/src/App.svelte:3:3: `@apply` cannot be used with `.md\:text-red-200` because `.md\:text-red-200` either cannot be found, or its actual definition includes a pseudo-selector like :hover, :active, etc. If you're sure that `.md\:text-red-200` exists, make sure that any `@import` statements are being properly processed *before* Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.
src/App.svelte

Are you able to use responsive classes such as md:text-red-200?

Thread Thread
sarioglu profile image
sarioglu Author

I'm afraid it is a limitation of tailwindcss. It cannot @apply variants directly. You can use it directly or find a workaround at tailwindcss.com/docs/functions-and...

Thread Thread
mikenikles profile image
Mike Nikles

Thank you, that helped fix the issue. @screen and regular Tailwind utility classes works as documented.

Collapse
patrickgoeler profile image
Patrick Göler von Ravensburg

Thanks for sharing. I ran into a small issue where the production flag in the postcss config is always false and therefore purgecss is not invoked. The NODE_ENV is "development" even when running npm run build in my case. I am on windows and I am resolving this by changing the build script to set NODE_ENV=production && sapper build --legacy. With that purgecss is invoked and everything is fabulous, thanks again.

Collapse
sarioglu profile image
sarioglu Author

Same for me too. I didn't mention that because I couldn't find any official information whether it is an expected behavior or not. Thank you for mentioning.

Collapse
fillipvt profile image
fillipvt

This still happens even though I'm using set NODE_ENV=production && sapper export --legacy I find this super weird. Not sure what to do at all.

The only way for me to avoid having all the classes in production is to use purgecss in both dev and prod. What a bummer

Thread Thread
danawoodman profile image
Dana Woodman

What about?

NODE_ENV=production sapper export --legacy
Collapse
gmartigny profile image
Guillaume Martigny

You just solve a whole day of headache. I could kiss you!

I had to add a few bolts to make it work fully for me:

Collapse
sarioglu profile image
sarioglu Author

Thank you! I've updated the template according to your comments. Issue with html and body was happening because index.html is in public folder. Purgecss config now checks every folder under project directory.

Collapse
taffit profile image
taffit

Thanks a lot for this wonderful article! Much appreciated!

I included it in Sapper by following exactly your description and then, instead of importing and including it in src/App.svelte, I just included it in src/routes/_layout.svelte. This way it should be included in all pages rendered by Sapper.

Works like a charm and, as you mentioned, no additional workflow side-by-side with Sapper/Svelte.

Thanks man!

Collapse
mustofa profile image
Habib Mustofa

Ahh, simply.
This is great! Thank you!

Note:
If anyone facing css linting warning in @tailwind keyword, just add type="text/postcss".

<style type="text/postcss" global>
  @tailwind base;
  @tailwind components;
  @tailwind utilities;
</style>
Collapse
city41 profile image
Matt Greer

Thanks for making these templates. I noticed after switching the sapper template to typescript with node scripts/setupTypeScriptRollup.js,Tailwind stopped working. The TS script creates some conflicts in rollup.config.js. They are easy to fix though.

  1. remove the extra sveltePreprocess import
  2. in both client and server, in the svelte() call, remove the extra preprocess: sveltePreprocess() lines that got added

I am brand new to Svelte, so might have something wrong, but this seemed to both get me TypeScript and Tailwind.

Collapse
sarioglu profile image
sarioglu Author

Thank you Matt! I've noticed this issue. scripts/setupTypeScriptRollup.js does not check if there is an existing sveltePreprocess. So it results with a silent failure. Your fix is correct. I am going to examine the script deeper to find a solution.

Collapse
ateammate profile image
Malte H

Thank you so much for sharing this!
Got my build time down from 2:20 to just 19 seconds!

One note: noticing that my dev builds are slower than my production ones, i found that (at least for me) always executing purgecss gave me even better timings: 8 seconds!

i.e. replaced
...(production ? [purgecss] : []
with
purgecss

Collapse
sarioglu profile image
sarioglu Author

Thank you for sharing your results! Glad to help you out.

I think it worths to disable Purgecss in development. I frequently use DevTools to add/remove some styles to get an instant preview. Therefore, I can sacrifice some seconds for better development experience 🙂

Collapse
claxxmoldii profile image
claxx moldii

i think this is great! i made a recipe blog based on the sapper at master-tailwind method you mentioned above. i would like to try this method instead. do you have any plans on coming out with sapper-tailwind template on github? i can't figure out which goes into the server and which into client configs as you said. i'm a noob with those sort of things and more of a UI/UX guy. thank you for this! i'll try it out on a svelte project. cheers!

Collapse
sarioglu profile image
sarioglu Author

Thanks for your reply. I'm planning to create the sapper version soon.

Collapse
leonistor profile image
Leo Nistor

Great article! I'm also using .prettierrc.json and .eslintrc.json from this article.

Collapse
thompcd profile image
Corey Thompson

Thank you!! I found the previous Tailwind implementation a bit lackluster, I'm excited to give it a try again!

Collapse
jfdelarosa profile image
Juan Fernando

Any workaround to make it work with svelte-vscode?

Collapse
tobius profile image
Toby Miller

A better approach indeed!

Collapse
djnitehawk profile image
Đĵ ΝιΓΞΗΛψΚ

thank you so much for this! working perfectly...

Collapse
dustypaws profile image
dustypaws

It's just a minor annoyance, but since I've made those changes, the bundle.js.map file gets send twice on every request. Don't know why really. Everything else seems to be working great tho. :)

Collapse
claxxmoldii profile image
claxx moldii

thank you! this is a lot of help!

Collapse
josfk profile image
josfk

Thanks so much for sharing!! Have you done or are you aware of any public recipe which used this svelte-tw-template?

Collapse
dydxgit profile image
Muhammad Taha

This doesn't remove unused tailwindcss in dev mode but should remove it when building, right?

Collapse
wildpow profile image
Aaron Wilder

Something is wrong with your sapper-template. When exporting, builds are 2 or 3 megs because the extra Tailwind CSS is not getting purged.

Collapse
leandropelegrini profile image
Leandro Pelegrini

Need extra config now, Tailwind added purgecss. tailwindcss.com/docs/controlling-f...