Getting started with Rails 6.1, TailwindCSS JIT, Webpacker & PostCSS 8
David Teren
Posted on June 9, 2021
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
In your Gemfile update webpacker
# Replace
gem 'webpacker', '~> 5.0'
# With
gem 'webpacker', '~> 5.4.0'
Run bundler
bundle
Remove the default webpacker
yarn remove @rails/webpacker
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
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"
}
}
Install TailwindsCSS and the official plugins.
yarn add tailwindcss @tailwindcss/forms @tailwindcss/typography @tailwindcss/aspect-ratio @tailwindcss/typography @tailwindcss/line-clamp
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"
}
}
Create the custom CSS file.
mkdir app/javascript/stylesheets && touch app/javascript/stylesheets/application.scss
In app/javascript/stylesheets/application.scss
add the following.
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
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;
}
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";
In app/javascript/packs/application.js
add the following;
import "stylesheets/application.scss"
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()
In the app root file postcss.config.js
require TailwindCSS
require('tailwindcss'),
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
})
]
}
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' %>
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>
Scaffold the full TailwindCSS tailwind.config.js
npx tailwindcss init --full
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'),
]
Add the Inter
font to the fontFamily
node in tailwind.config.js
fontFamily: {
sans: [
'Inter var',
...
Enable JIT - just in time compilation at the top of the tailwind.config.js
file
module.exports = {
mode: 'jit',
purge: [],
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',
],
},
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'),],
}
In the babel.config.js
file set the following
...
'@babel/plugin-transform-runtime',
{
helpers: false,
regenerator: true,
corejs: false
}
And add the following to babel.config.js
['@babel/plugin-proposal-private-methods', { loose: true }]
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)
}
}
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)
})
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
Let's test this setup
rails g controller Home index
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>
Add the root path to point at the above /home/index
in config/routes.rb
root 'home#index'
In one terminal run bin/webpack-dev-server
And in another run rails s
⚠️ Do not try running
webpack-dev-server
without thebin/
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"
and in app/javascript/stylesheets/application.scss
add the following
.pulsing-text {
@apply animate-pulse font-bold text-gray-200 text-2xl mb-6;
}
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";
When you refresh you home page in the browser you should still see this.
Posted on June 9, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.