Getting started with Rails 6.1, TailwindCSS JIT, Webpacker & PostCSS 8

davidteren

David Teren

Posted on June 9, 2021

Getting started with Rails 6.1, TailwindCSS JIT, Webpacker & PostCSS 8

This is my first post here and we are going to look at getting TailwindCSS with the awesome JIT (just in time) compilation setup in Rails 6.1 app via Webpacker and using PostCSS 8.0

Create a new Rails app

rails new tails_on_rails
Enter fullscreen mode Exit fullscreen mode

In your Gemfile update webpacker

# Replace
gem 'webpacker', '~> 5.0'

# With
gem 'webpacker', '~> 5.4.0'
Enter fullscreen mode Exit fullscreen mode

Run bundler

bundle
Enter fullscreen mode Exit fullscreen mode

Remove the default webpacker

yarn remove @rails/webpacker
Enter fullscreen mode Exit fullscreen mode

Add the required version of Webpacker and other dependancies

yarn add @rails/webpacker@5.4.0 @fullhuman/postcss-purgecss@^4.0.3 postcss@^8.2.10 postcss-loader@^4.0.3 sass@^1.32.7 autoprefixer@^10.2.6
Enter fullscreen mode Exit fullscreen mode

Your package.json should look something like this.

{
  "name": "tails-on-rails",
  "private": true,
  "dependencies": {
     "@fullhuman/postcss-purgecss": "^4.0.3",
     "@rails/actioncable": "^6.0.0",
     "@rails/activestorage": "^6.0.0",
     "@rails/ujs": "^6.0.0",
     "@rails/webpacker": "5.4.0",
     "postcss": "^8.2.10",
     "postcss-loader": "^4.0.3",
     "sass": "^1.32.7",
     "turbolinks": "^5.2.0",
     "webpack": "^4.46.0",
     "webpack-cli": "^3.3.12"
    },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.2"
 }
}
Enter fullscreen mode Exit fullscreen mode

Install TailwindsCSS and the official plugins.

yarn add tailwindcss @tailwindcss/forms @tailwindcss/typography @tailwindcss/aspect-ratio @tailwindcss/typography @tailwindcss/line-clamp
Enter fullscreen mode Exit fullscreen mode

Your package.json should look something like this.

{
  "name": "tails-on-rails",
  "private": true,
  "dependencies": {
    "@fullhuman/postcss-purgecss": "^4.0.3",
    "@rails/actioncable": "^6.0.0",
    "@rails/activestorage": "^6.0.0",
    "@rails/ujs": "^6.0.0",
    "@rails/webpacker": "5.4.0",
    "@tailwindcss/aspect-ratio": "^0.2.1",
    "@tailwindcss/forms": "^0.3.3",
    "@tailwindcss/typography": "^0.4.1",
    "postcss": "^8.2.10",
    "postcss-loader": "^4.0.3",
    "sass": "^1.32.7",
    "tailwindcss": "^2.1.4",
    "turbolinks": "^5.2.0",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.3.12"
  },
  "version": "0.1.0",
  "devDependencies": {
    "webpack-dev-server": "^3.11.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create the custom CSS file.

mkdir app/javascript/stylesheets && touch app/javascript/stylesheets/application.scss

Enter fullscreen mode Exit fullscreen mode

In app/javascript/stylesheets/application.scss add the following.

@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";
Enter fullscreen mode Exit fullscreen mode

While we are on here we will add a custom component to validate that the @apply directive works.

Add the following to app/javascript/stylesheets/application.scss

.btn {
  @apply px-4 py-2 bg-blue-600 text-white rounded; 
}
Enter fullscreen mode Exit fullscreen mode

Your application.scss should look something like this.

@import "tailwindcss/base";

@import "tailwindcss/components";

.btn {
  @apply px-4 py-2 bg-blue-600 text-white rounded; 
}

@import "tailwindcss/utilities";
Enter fullscreen mode Exit fullscreen mode

In app/javascript/packs/application.js add the following;

import "stylesheets/application.scss"
Enter fullscreen mode Exit fullscreen mode

Your application.js should look something like this

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import "stylesheets/application.scss"

Rails.start()
Turbolinks.start()
ActiveStorage.start()
Enter fullscreen mode Exit fullscreen mode

In the app root file postcss.config.js require TailwindCSS

require('tailwindcss'),
Enter fullscreen mode Exit fullscreen mode

Your postcss.config.js should look something like this.

module.exports = {
  plugins: [
    require('tailwindcss'),
    require('postcss-import'),
    require('postcss-flexbugs-fixes'),
    require('postcss-preset-env')({
      autoprefixer: {
        flexbox: 'no-2009'
      },
      stage: 3
    })
  ]
}
Enter fullscreen mode Exit fullscreen mode

Add the following to your app/views/layouts/application.html.erb

<link rel="stylesheet" href="https://rsms.me/inter/inter.css">

<%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
Enter fullscreen mode Exit fullscreen mode

Your application.html.erb should look something like this.

<!DOCTYPE html>
<html>
  <head>
    <title>TailsOnRails</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <link rel="stylesheet" href="https://rsms.me/inter/inter.css">

    <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Scaffold the full TailwindCSS tailwind.config.js

npx tailwindcss init --full
Enter fullscreen mode Exit fullscreen mode

The above will generate a large configurable TailwindCSS file.

Add the TailwindCSS plug-ins to tailwind.config.js (right at the bottom of the file in the plugins: [] node.

plugins: [
  require('@tailwindcss/typography'),
  require('@tailwindcss/forms'),
  require('@tailwindcss/line-clamp'),
  require('@tailwindcss/aspect-ratio'),
]
Enter fullscreen mode Exit fullscreen mode

Add the Inter font to the fontFamily node in tailwind.config.js

fontFamily: {
  sans: [
    'Inter var',
       ...
Enter fullscreen mode Exit fullscreen mode

Enable JIT - just in time compilation at the top of the tailwind.config.js file

module.exports = {
  mode: 'jit',
  purge: [],
Enter fullscreen mode Exit fullscreen mode

Configure the purge option in the tailwind.config.js file

purge: {
enabled: ["production", "staging"].includes(process.env.NODE_ENV),
content: [
  './app/views/**/*.html.erb',
  './app/helpers/**/*.rb',
  './app/javascript/**/*.js',
],
},

Enter fullscreen mode Exit fullscreen mode

Your tailwind.config.js should look something like this.

const colors = require('tailwindcss/colors')

module.exports = {
  mode: 'jit',
  purge: {
    enabled: ["production", "staging"].includes(process.env.NODE_ENV),
    content: [
      './**/*.html.erb',
      './app/helpers/**/*.rb',
      './app/javascript/**/*.js',
    ],
  },
  presets: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    screens: {
      sm: '640px',
      md: '768px',
      lg: '1024px',
      xl: '1280px',
      '2xl': '1536px',
    },
    colors: {
      transparent: 'transparent',
      current: 'currentColor',

      // content removed ....

    flexGrow: {
      0: '0',
      DEFAULT: '1',
    },
    flexShrink: {
      0: '0',
      DEFAULT: '1',
    },
    fontFamily: {
      sans: [
        'Inter var',
        'ui-sans-serif',
        'system-ui',
        '-apple-system',
        'BlinkMacSystemFont',
        '"Segoe UI"',
        'Roboto',
        '"Helvetica Neue"',
        'Arial',
        '"Noto Sans"',
        'sans-serif',
        '"Apple Color Emoji"',
        '"Segoe UI Emoji"',
        '"Segoe UI Symbol"',
        '"Noto Color Emoji"',
      ],
      serif: ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
      mono: [
        'ui-monospace',
        'SFMono-Regular',
        'Menlo',

      // content removed ....

    zIndex: ['responsive', 'focus-within', 'focus'],
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
    require('@tailwindcss/line-clamp'),
    require('@tailwindcss/aspect-ratio'),],
}
Enter fullscreen mode Exit fullscreen mode

In the babel.config.js file set the following

...
'@babel/plugin-transform-runtime',
{
  helpers: false,
  regenerator: true,
  corejs: false
}
Enter fullscreen mode Exit fullscreen mode

And add the following to babel.config.js

['@babel/plugin-proposal-private-methods', { loose: true }]
Enter fullscreen mode Exit fullscreen mode

Your babel.config.js should look something like this

module.exports = function (api) {
  var validEnv = ['development', 'test', 'production']
  var currentEnv = api.env()
  var isDevelopmentEnv = api.env('development')
  var isProductionEnv = api.env('production')
  var isTestEnv = api.env('test')

  if (!validEnv.includes(currentEnv)) {
    throw new Error(
      'Please specify a valid `NODE_ENV` or ' +
        '`BABEL_ENV` environment variables. Valid values are "development", ' +
        '"test", and "production". Instead, received: ' +
        JSON.stringify(currentEnv) +
        '.'
    )
  }

  return {
    presets: [
      isTestEnv && [
        '@babel/preset-env',
        {
          targets: {
            node: 'current'
          }
        }
      ],
      (isProductionEnv || isDevelopmentEnv) && [
        '@babel/preset-env',
        {
          forceAllTransforms: true,
          useBuiltIns: 'entry',
          corejs: 3,
          modules: false,
          exclude: ['transform-typeof-symbol']
        }
      ]
    ].filter(Boolean),
    plugins: [
      'babel-plugin-macros',
      '@babel/plugin-syntax-dynamic-import',
      isTestEnv && 'babel-plugin-dynamic-import-node',
      '@babel/plugin-transform-destructuring',
      [
        '@babel/plugin-proposal-class-properties',
        {
          loose: true
        }
      ],
      [
        '@babel/plugin-proposal-object-rest-spread',
        {
          useBuiltIns: true
        }
      ],
      [
        '@babel/plugin-transform-runtime',
        {
          helpers: false,
          regenerator: true,
          corejs: false
        }
      ],
      [
        '@babel/plugin-transform-regenerator',
        {
          async: false
        }
      ],
      ['@babel/plugin-proposal-private-methods', { loose: true }]
    ].filter(Boolean)
  }
}
Enter fullscreen mode Exit fullscreen mode

For Webpacker to work with this version of Postcss we need to tell it to use the loader.
In config/webpack/environment.js add the following. Just insert it between the two existing lines.

// Get the actual sass-loader config
const sassLoader = environment.loaders.get('sass')
const sassLoaderConfig = sassLoader.use.find(function (element) {
return element.loader == 'sass-loader'
})

// Use Dart-implementation of Sass (default is node-sass)
const options = sassLoaderConfig.options
options.implementation = require('sass')

function hotfixPostcssLoaderConfig (subloader) {
  const subloaderName = subloader.loader
  if (subloaderName === 'postcss-loader') {
    subloader.options.postcssOptions = subloader.options.config
    delete subloader.options.config
  }
}

environment.loaders.keys().forEach(loaderName => {
  const loader = environment.loaders.get(loaderName)
  loader.use.forEach(hotfixPostcssLoaderConfig)
})
Enter fullscreen mode Exit fullscreen mode

Your environment.js should look something like this.

const {environment} = require('@rails/webpacker')

// Get the actual sass-loader config
const sassLoader = environment.loaders.get('sass')
const sassLoaderConfig = sassLoader.use.find(function (element) {
return element.loader == 'sass-loader'
})

// Use Dart-implementation of Sass (default is node-sass)
const options = sassLoaderConfig.options
options.implementation = require('sass')

function hotfixPostcssLoaderConfig(subloader) {
  const subloaderName = subloader.loader
  if (subloaderName === 'postcss-loader') {
    subloader.options.postcssOptions = subloader.options.config
    delete subloader.options.config
  }
}

environment.loaders.keys().forEach(loaderName => {
const loader = environment.loaders.get(loaderName)
loader.use.forEach(hotfixPostcssLoaderConfig)
})

module.exports = environment
Enter fullscreen mode Exit fullscreen mode

Let's test this setup

rails g controller Home index 
Enter fullscreen mode Exit fullscreen mode

Add the following to app/views/home/index.html.erb

<div class="font-sans bg-white h-screen flex flex-col w-full">
  <div class="h-screen bg-gradient-to-r from-green-400 to-blue-500">
    <div class="px-4 py-48">
     <div class="relative w-full text-center">
       <h1 class="animate-pulse font-bold text-gray-200 text-2xl mb-6">
        Your TailwindCSS setup is working if this pulses...
       </h1>
    </div>
   </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Add the root path to point at the above /home/index in config/routes.rb

root 'home#index'
Enter fullscreen mode Exit fullscreen mode

In one terminal run bin/webpack-dev-server
And in another run rails s

⚠️ Do not try running webpack-dev-server without the bin/ prefix as it's likely it will fail to compile.

You should see this

To test the @apply directive replace the following from app/views/home/index.html.erb with class="pulsing-text"

class="animate-pulse font-bold text-gray-200 text-2xl mb-6"
Enter fullscreen mode Exit fullscreen mode

and in app/javascript/stylesheets/application.scss add the following

.pulsing-text {
@apply animate-pulse font-bold text-gray-200 text-2xl mb-6;
}
Enter fullscreen mode Exit fullscreen mode

Your applications.scss should look like something like this.

@import "tailwindcss/base";

@import "tailwindcss/components";

.pulsing-text {
@apply animate-pulse font-bold text-gray-200 text-2xl mb-6;
}

@import "tailwindcss/utilities";
Enter fullscreen mode Exit fullscreen mode

When you refresh you home page in the browser you should still see this.

GitHub Repo

💖 💪 🙅 🚩
davidteren
David Teren

Posted on June 9, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related