Using Tailwind CSS with Sage 9 + Webpack

How to setup Tailwind CSS in Sage 9

I :heart: designing in the browser. Luckily, utility-first CSS frameworks like Tailwind make it easier than ever. Here’s how you can get started using Tailwind CSS with Sage 9.

During the Sage install choose No Framework (you don’t have to, but it will help avoid naming conflicts + help reduce filesize), then complete the following:

1. Install Tailwind:

We need to require Tailwind as a dev dependency:

yarn add tailwindcss --dev

2. Initialize Tailwind

This generates a tailwind.js config file in styles directory:

./node_modules/.bin/tailwind init resources/assets/styles/tailwind.js

or if you have npx installed globally :raised_hands: (tip: you do have it if you are running npm 5.2.0+):

npx tailwind init resources/assets/styles/tailwind.js

3. Add Tailwind to the top of the PostCSS plugin list

In resources/assets/build/postcss.config.js add:

// ...
plugins: {
  tailwindcss: `${options.paths.assets}/styles/tailwind.js`,
  // ...
},
See entire modified file
/* eslint-disable */

const cssnanoconfig = {
  preset: ['default', { discardcomments: { removeall: true } }]
};

module.exports = ({ file, options }) => {
  return {
    parser: options.enabled.optimize ? 'postcss-safe-parser' : undefined,
    plugins: {
      tailwindcss: `${options.paths.assets}/styles/tailwind.js`, 
      cssnano: options.enabled.optimize ? cssnanoconfig : false,
      autoprefixer: true,
    },
  };
};

4. Add Tailwind directives to Stylelint ignoreAtRules list in package.json:

"ignoreAtRules": [
  // ...
  "tailwind",
  "apply",
  "responsive",
  "variants",
  "screen"
]
See entire modified file
{
  "name": "sage",
  "version": "9.0.0",
  "author": "Roots <team@roots.io>",
  "homepage": "https://roots.io/sage/",
  "private": true,
  "repository": {
    "type": "git",
    "url": "git://github.com/roots/sage.git"
  },
  "bugs": {
    "url": "https://github.com/roots/sage/issues"
  },
  "licenses": [
    {
      "type": "MIT",
      "url": "http://opensource.org/licenses/MIT"
    }
  ],
  "browserslist": [
    "last 2 versions",
    "android 4",
    "opera 12"
  ],
  "stylelint": {
    "extends": "stylelint-config-standard",
    "rules": {
      "no-empty-source": null,
      "at-rule-no-unknown": [
        true,
        {
          "ignoreAtRules": [
            "extend",
            "at-root",
            "debug",
            "warn",
            "error",
            "if",
            "else",
            "for",
            "each",
            "while",
            "mixin",
            "include",
            "content",
            "return",
            "function",
            "tailwind",
            "apply",
            "responsive",
            "variants",
            "screen"
          ]
        }
      ]
    }
  },
  "scripts": {
    "build": "webpack --progress --config resources/assets/build/webpack.config.js",
    "build:production": "webpack --progress -p --config resources/assets/build/webpack.config.js",
    "build:profile": "webpack --progress --profile --json --config resources/assets/build/webpack.config.js",
    "start": "webpack --hide-modules --watch --config resources/assets/build/webpack.config.js",
    "rmdist": "rimraf dist",
    "lint": "npm run -s lint:scripts && npm run -s lint:styles",
    "lint:scripts": "eslint resources/assets/scripts resources/assets/build",
    "lint:styles": "stylelint \"resources/assets/styles/**/*.{css,sass,scss,sss,less}\"",
    "test": "npm run -s lint"
  },
  "engines": {
    "node": ">= 6.9.4"
  },
  "devDependencies": {
    "autoprefixer": "~7.2.5",
    "browser-sync": "~2.23.6",
    "browsersync-webpack-plugin": "^0.6.0",
    "bs-html-injector": "~3.0",
    "buble-loader": "^0.4.1",
    "cache-loader": "~1.2.0",
    "clean-webpack-plugin": "^0.1.18",
    "copy-globs-webpack-plugin": "^0.2.0",
    "css-loader": "^0.28.9",
    "cssnano": "~v4.0.0-rc.2",
    "eslint": "~4.17.0",
    "eslint-loader": "~1.9",
    "eslint-plugin-import": "~2.8.0",
    "extract-text-webpack-plugin": "~3.0.2",
    "file-loader": "^1.1.6",
    "friendly-errors-webpack-plugin": "^1.6.1",
    "imagemin-mozjpeg": "~7.0.0",
    "imagemin-webpack-plugin": "~2.0.0",
    "import-glob": "~1.5",
    "node-sass": "~4.7.2",
    "postcss-loader": "~2.1.0",
    "postcss-safe-parser": "~3.0",
    "resolve-url-loader": "~2.2.1",
    "rimraf": "~2.6",
    "sass-loader": "~6.0",
    "style-loader": "^0.20.1",
    "stylelint": "^8.4.0",
    "stylelint-config-standard": "~18.0.0",
    "stylelint-webpack-plugin": "^0.10.1",
    "tailwindcss": "^0.5.2",
    "url-loader": "^0.6.2",
    "webpack": "~3.10.0",
    "webpack-assets-manifest": "^1.0.0",
    "webpack-dev-middleware": "~2.0.4",
    "webpack-hot-middleware": "~2.21.0",
    "webpack-merge": "~4.1.1",
    "yargs": "~11.0.0"
  },
  "dependencies": {
    "bootstrap": "v4.0.0",
    "jquery": "^3.3.1",
    "popper.js": "^1.12.9"
  }
}

5. Add directives to main.scss:

/** Tailwind base styles – includes modified normalize.css */
@tailwind preflight;

/** Tailwind plugins – included in tailwind.js */
@tailwind components;

/**
 * Sage’s styles
 *
 * Placing these here allows you to use @apply
 * to apply Tailwind’s utilities
 */

/** Tailwind utilities – generated from config in tailwind.js */
@tailwind utilities;
See example

This example is a pure combination of Sage’s default main.scss and Tailwind’s recommended CSS structure:

@import "common/variables";
/**
 * This injects Tailwind's base styles, which is a combination of
 * Normalize.css and some additional base styles.
 *
 * You can see the styles here:
 * https://github.com/tailwindcss/tailwindcss/blob/master/css/preflight.css
 *
 * If using `postcss-import`, you should import this line from it's own file:
 *
 * @import "./tailwind-preflight.css";
 *
 * See: https://github.com/tailwindcss/tailwindcss/issues/53#issuecomment-341413622
 */
@tailwind preflight;

/** Import everything from autoload */
@import "./autoload/**/*";

/**
 * Import npm dependencies
 *
 * Prefix your imports with `~` to grab from node_modules/
 * @see https://github.com/webpack-contrib/sass-loader#imports
 */
// @import "~some-node-module";

/**
 * This injects any component classes registered by plugins.
 *
 * If using `postcss-import`, use this import instead:
 *
 * @import "tailwindcss/components";
 */
@tailwind components;

/**
 * Here you would add any of your custom component classes; stuff that you'd
 * want loaded *before* the utilities so that the utilities could still
 * override them.
 *
 * Example:
 *
 * .btn { ... }
 * .form-input { ... }
 *
 * Or if using a preprocessor or `postcss-import`:
 *
 * @import "components/buttons";
 * @import "components/forms";
 */
@import "common/global";
@import "components/buttons";
@import "components/comments";
@import "components/forms";
@import "components/wp-classes";
@import "layouts/header";
@import "layouts/sidebar";
@import "layouts/footer";
@import "layouts/pages";
@import "layouts/posts";
@import "layouts/tinymce";

/**
 * This injects all of Tailwind's utility classes, generated based on your
 * config file.
 *
 * If using `postcss-import`, you should import this line from it's own file:
 *
 * @import "./tailwind-utilities.css";
 *
 * See: https://github.com/tailwindcss/tailwindcss/issues/53#issuecomment-341413622
 */
@tailwind utilities;

/**
 * Here you would add any custom utilities you need that don't come out of the
 * box with Tailwind.
 *
 * Example :
 *
 * .bg-pattern-graph-paper { ... }
 * .skew-45 { ... }
 *
 * Or if using a preprocessor or `postcss-import`:
 *
 * @import "utilities/background-patterns";
 * @import "utilities/skew-transforms";
 */
A simpler setup
/** Tailwind base styles */
@tailwind preflight;

/** Import everything from autoload */
@import "./autoload/**/*";

/** Tailwind plugins */
@tailwind components;

/** Your own components – checkout @apply */

/** Tailwind utilities */
@tailwind utilities;

/** Define custom utilities */

Check out the Tailwind docs for notes on CSS structure.

6. Add Purgecss for unused CSS removal (optional)

  1. Add Purgecss to Sage.
  2. Once you’ve successfully added Purgecss, you will need to complete an addition step to make sure Purgecss can extract Tailwind’s classes properly. Luckily, Tailwind has a guide in their docs to add a custom Purgecss extractor.
See example

In webpack.config.optimize.js:

'use strict'; // eslint-disable-line

const { default: ImageminPlugin } = require('imagemin-webpack-plugin');
const imageminMozjpeg = require('imagemin-mozjpeg');
const glob = require('glob-all');
const PurgecssPlugin = require('purgecss-webpack-plugin');

const config = require('./config');

class TailwindExtractor {
  static extract(content) {
    return content.match(/[A-z0-9-:\/]+/g) || [];
  }
}

module.exports = {
  plugins: [
    new ImageminPlugin({
      optipng: { optimizationLevel: 7 },
      gifsicle: { optimizationLevel: 3 },
      pngquant: { quality: '65-90', speed: 4 },
      svgo: { removeUnknownsAndDefaults: false, cleanupIDs: false },
      plugins: [imageminMozjpeg({ quality: 75 })],
      disable: (config.enabled.watcher),
    }),
    new PurgecssPlugin({
      paths: glob.sync([
        'app/**/*.php',
        'resources/views/**/*.php',
        'resources/assets/scripts/**/*.js',
      ]),
      extractors: [
        {
          extractor: TailwindExtractor,
          extensions: ["js", "php"]
        }
      ],
    }),
  ],
};

And there you go — you’re all setup. Just run yarn start,1 pop open tailwind.js to define your design system, and start building! :tada:


1 You will notice a Webpack warning when running yarn start. There is nothing going wrong with the compiling of your styles, but this is a known issue with the resolve url Webpack loader. If you comment it out in the Webpack config file, the error will no longer appear, however, an error will persist in the browser console since this breaks source maps. Since you are using Tailwind — which generates CSS via a JavaScript config file — you don’t really need source maps, so you can go even further and remove the sourceMap option form the css/postcss/sass loaders. It does suck, but using a utility CSS framework you’ll notice you will be spending a lot less time in your stylesheets and more time in the markup designing.

:woman_shrugging:

12 Likes