Sample Frontend Stack and Why: Svelte, Tailwind and Storybook
10 min read

Sample Frontend Stack and Why: Svelte, Tailwind and Storybook

Sample Frontend Stack and Why: Svelte, Tailwind and Storybook

I was exploring this specific stack for one of my hobby projects. Surprisingly, there wasn't a lot of info online. So, I wanted to share a hopefully working starting template for that one person looking for it online. I got you buddy!

This post organized in this format:

  1. Linking a working sample
  2. Explain how you can set this up in your own project
  3. I ramble on and discuss reasons I choose this stack.

πŸ€”That seems like the order of priority for people looking this post.


A Working Template

Random photo that's not related to articleRandom photo that's not related to article.

Customary Github Link or download the zip archive here.

What's included

  • Svelte: Hippest JS technology as of writing
  • TailwindCSS: Utility CSS Framework that makes it easy to have a non-bland and non-generic looking frontend
  • Storybook: Make it easy to build, test and explore frontend components.

What's might be missing for you:

  • Router (to route to different components or toolset to route to different pages of your app)

Project Structure:

It's pretty barebones. The base came from a standard svelte template. I've added the postcss config js and init js file for Tailwind which you can find in their install documentation. Finally, Storybook needs its own folder and special config files which you can see under .storbook. These 3 are all hooked up in the package.json.

The only weird thing you might find is that the components have a directory structure with chemistry sounding names. Fret not. It's not because this application strictly for hip js programmers interested in elementary chemistry but rather it follows the atomic design methodology.

Development Workflow

To initialize:
npm install

To run development mode for the app:
npm run dev

To run storbook:
npm run storybook

To publish files
npm run build

If you take a peek into the package.json you'll notice how they are chained together. The order is important so that the technologies work well together.


Set Up from Scratch

A random programming related image A random programming related image

1. Setup Svelte Starter with Rollup

You can download the base svelte project using the npx degit below. It comes with Rollup (not Webpack if you're used to that) to do the build process for svelte.

npx degit sveltejs/template my-svelte-project
# or download and extract 
cd my-svelte-project

npm install
npm run dev

If you want to work with highly popular CSS frameworks like Bulma or component libraries like Bootstrap, you should check out forks people have made based on the original template above. There's a chance somebody's done it before and maybe you can just totally just steal build on from their work.

Notes on Sapper

If you want more of a full-stack setup, you can consider the related project, Sapper. It's svelte bundled with a nodejs app. Since the nodejs ecosystem is pretty mature at this point, you can add easily add everything from authentication to database access and so on. Sapper can be used with Rollup or webpack as the bundler. Unless you know webpack well, I'd recommend to use rollup since the sapper community seems to have more examples on rollup. For my needs, Sapper ended up being overkill and a hindrance.

2. Add TailwindCSS

Surpringly, it was really easy to get Tailwind working (in Sapper is a diffent story). You'll basically need to go through the recommended steps of installtion with PostCSS. The rough idea here is that we're using some tailwind syntax then postCSS will automagically convert that into one css file. We want this to all happens before svelte runs. Once we run svelte, we just see it as one css file we can use.

Install Tailwind

Install via npm or yarn
npm install tailwindcss

Create a file and insert the tailwind syntax.

# For my uses, I put it in public/tailwind.css
@tailwind base;
@tailwind components;
@tailwind utilities;

Setup File for Customizations later

You can add a tailwind config file via the command below. This adds a tailwind.config.js. It's a javascript object which you can change to do things like change defaults or customize more of the look and feel.
npx tailwind init

Setup the Workflow

Now we setup the workflow so that Tailwind works nicely with Svelte.

PostCSS

As mentioned earlier, You will want PostCSS to wangjangle your tailwind css file and configuration into a bundled css file. To do that, you'll need to install PostCSS. In this example we also use PurgeCSS which removes unused css in the process.

Install postcss-cli and @fullhuman/postcss-purgecss by going to Google and seeing how to do that. Probably using npm.

Once you've hated me for the above but continued reading, you'll want to setup a postcss.config.js. It is what it sounds like... yet another config file. Don't worry, you can see the copy the contents below.

Before you blindly copy this file, you'll notice in the code that it's looking for a config file (./tailwind.config.js) and uses the src and public folder. If you have a different directory structure or file names, make sure it's pointing to the right thing.

const tailwindcss = require("tailwindcss");

// only needed if you want to purge
const purgecss = require("@fullhuman/postcss-purgecss")({
    content: ["./src/**/*.svelte", "./public/**/*.html"],
    defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
});

module.exports = {
    plugins: [
        tailwindcss("./tailwind.config.js"),

        // only needed if you want to purge
        ...(process.env.NODE_ENV === "production" ? [purgecss] : [])
    ]
};

Running in order

The last step is to setup our package.json to have commands run in order and in parallel.

First step is to not hate me because I didn't put all the things you had to install at the start. Second, install npm-run-all to, well, run all npm commands in parallel. Third, run tailwind before running rollup like what you see below.

Sample package.json commands below.

"scripts": {
    "watch:tailwind": "postcss public/tailwind.css -o public/index.css -w",
    "build:tailwind": "NODE_ENV=production postcss public/tailwind.css -o public/index.css",
    "dev": "run-p start:dev autobuild watch:tailwind",
    "build": "npm run build:tailwind && rollup -c",
    "autobuild": "rollup -c -w",
    "start": "sirv public",
    "start:dev": "sirv public --dev",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"
  },

Note:
After playing around with this template with Routify (Svelte Router), I realized that there maybe tweaks needed as Tailwind won't work beyond the App.svelte file. For now, check out this post which explains the process:

3. Add Storybook

A picture of a book with lights in it. Who reads like this?A totally inefficient way to read a book

Last thing we want to setup is Storybook. Setting up storybook with svelte is relatively easy (without Sapper + Tailwind). If you run the command below, it installs storybook with svelte configurations).
npx -p @storybook/cli sb init --type svelte

You should just double check how you want storybook to read from your project. In the example .storybook/conjig.js below, you'll see that I want it to read from a particular folder.

import { configure } from '@storybook/svelte';

// automatically import all files ending in *.stories.js
configure(require.context('../src/components', true, /\.stories\.js$/), module);

Is that it? Well not quite. If you just use it run storybook at this point, you'll notice that your components are uglier than normal but not because we lack design. We'll need to use the generated css so that the components can use it. In this sample story file below, you'll see that we import the index.css which is the final output of our Tailwind process.

# File `PillButton.stories.js`
import '../../../public/index.css';

import { action } from '@storybook/addon-actions';

import PillButton from './PillButton.svelte';

export default {
    title: 'PillButton',
};

export const text = () => ({
    Component: PillButton,
    props: { title: 'Hello Button' },
    on: { click: action('clicked') },
});

export const emoji = () => ({
    Component: PillButton,
    props: {
        title: 'πŸ˜€ 😎 πŸ‘ πŸ’―',
    },
    on: { click: action('clicked') },
});

Done, I hope.

I hope this helps you setup your own project. I'm sure you have your own unqiue set of tools that you want to work together so you may have to deviate from what I have here. As a final note on this section, there maybe better ways to mangle these technologies together so take all of this with a grain of salt. Question your stack and use your better judgement.


Workflow, Architecture and Reasons I choose this Stack

Alright, now for the section that nobody but me is probably interested in. Why this stack? Why choose this over others? What are the advantages / disadvantages? Are you just joining a new trend?

Working in the Frontend in 2019
Even though I'd consider myself fullstack, most of my day-to-day work is in backend. Professionally, I have built and maintained frontends during the Angular 1.x age. This year, I got to work on React projects (including the new 16.x hooks, etc).

Where am I going with all this? For me, the frontend/javascript ecosystem in 2019 is pretty amazing. There's tooling for any and everything. We can build highly complex applications right in our browser with hundreds of engineers contributing by the hour. We're doing that while serving millions of people everyday.

"So what?" you say. I see two problems if I'm building simple frontend projects. One, it feels extremly bloated (emphasis on feels since I could be entirely wrong). A starter project in React/Vue/Angular feels like it has a zillion dependencies, configurations and whatnots. After starter project, it feels like the every tutorial defaults to adding a lot on top of that starter projects (ie: Redux, form things). These are probably great for big SaaS projects but seems overkill for hobby projects.

Second, it feels like I'm learning highly specific languages, tools and concepts for simple user workflows. Some examples include: JSX, Vue-syntax, Redux (or equivalent state managers). Looping back to my original argument, it's great if you're building something like Slack, Facebook and so on but it feels like a lot of overhead if all you want to do is display and manage data from a backend.

I must admit that it's super easy to get up and running in these frameworks (ie: create-react-app, vue cli). A lot of people did a ton of great work to enable a great development experience.

UI sketches

What is ideal? Svelte is not the answer
Working in the frontend used to be simple. A little bit of HTML, JS and CSS. Dump the files in a folder in the server and you're done. It felt relatively easy to spin up a site and simple app. There was no magic. It was painful once you think about versioning, dependencies and so on. Sites and apps are a lot more powerful in 2019 so it's not a fair comparison at all. We also have better versioning, developer experience, collaboration, utilities for sharing components, testing, etc.

What I'm looking for is a balance of old and new. Can I have something simple where I don't need 20 million packages downloaded on the runtime? Can easily spin up new pages, components and apps? Can I take advantage of all the things the JS ecosystem provides if I need it? Can I develop these with a langusge and ecosystem that feels simple to start and maintain?

Svelte ecosystem (as of writing) does not provide all of these. In fact, it has some of the downsides I mentioned like some custom syntax. It is the closest to it though. Working on/off for a few hours in the last month has been a pleasure. Most of the time, it feels like I'm writing plain html and js. It's enjoyable. It was simple. With the project I've setup, it feels like I can understand most of libraries and configuration and it was all intentional.

Atomic Design and Development with Tailwind and Storybook
Using just Svelte (or React/Vue/Angular) is all good but also all ugly. You'll eventually want to add some CSS framework (something akin to Bootstrap) if you're not a developer with a heavy design background. And that's totally fine. For me though, I don't ever like how that turns out. It usually looks too ugly or too generic for me. I want my hobby project to look better than that.

It's usually been really hard for a non-design background person to design half-decent UIs. Customizing CSS Frameworks was either too limited, bland or extremly frustrating. For a while, there wasn't much of a choice.

I recently tried Tailwind for one of our products at work and it was pretty good experience. It allowed me to think of it as layout manager first. For example: this component is centered, these divs should be in a column and so on. It felt like just declaratively organizing the ui. After that it was easy to add style states (when you hover, turn blue). Beyond that, the syntax (which is just strings) for styling is simple but powerful. For example, I want this background blue but not too blue, instead of defining a blue hex code, you just put bg-blue-100 or make it more intense to bg-blue-800.

The developer of Tailwind, also posts some really nice material. You can see him copy a website from scratch. It helps you think in the mindset but also see live of how you can achieve it too (without the need for a designer).

Storybook allows me to create components. A button, a menu, a form and so on. Just like how in the backend, you can separate components to their uses, you can use storybook to create the components first then think about how they interact with each other.Storybook paired with Atomic Design methodology is even better. It helps you break down your pages into reusable components then build them up into pages. It helps keep your business logic separate from your presentation logic.

Summary, It's Not the Best but It's Not Bad

Overall, this stack is good enough that I feel like it's enjoyable to build a simple frontend again. There's still a ton of work I'd like to do like building it into one deployable file but that's for another day...