Notes to self

Running SASS and Tailwind side by side in Rails

What if you want to incrementally switch to Bulma? Or build a marketing site with Tailwind while keep using SASS for your main application? It’s not as hard as you think.

The cssbundling-rails gem is a popular choice when it comes to handling CSS in Rails. It supports Bulma, plain SASS, Tailwind, PostCSS, and Bootstrap out of the box. While there are other option, cssbundling-rails is the most flexible solution for most.

The best part of it is design which is easy to understand.

One framework

In case you are not using cssbundling-rails gem, you can add the gem and install the framework you are using:

$ ./bin/bundle add cssbundling-rails
$ ./bin/rails css:install:bulma

If we installed Bulma, we would end up with the bulma package dependency as well as build:css entry in package.json:

{
  "name": "app",
  "private": "true",
  "dependencies": {
    ...
    "bulma": "^0.9.3",
    "sass": "^1.45.0",
    ...
  },
  "scripts": {
    "build:css": "sass ./app/assets/stylesheets/application.bulma.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules",
    "build": "..."
  }
}

And it’s the build:css that does the trick in building our assets. Here we are calling the sass executable with our stylesheet entrypoint followed by the desired build path. The whole magic is assumption that build:css will build your assets for production.

The build name then references the bundle we include with stylesheet_link_tag, e.g.:

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

On top, the gem will also add an entry to Procfile.dev for auto-reloading in development.

$ cat Procfile.dev
web: bin/rails server -p 3000
css: yarn build:css --watch
...

Again, the magic is appending the --watch option to the yarn build command, so the command will rerun when there are new changes.

Two frameworks

To support two frameworks side by side, it’s enough to include both in the build:css task.

We can start by adding our second framework the same as the first one:

$ ./bin/rails css:install:tailwind

Running the task will make sure we have all the Node packages present (adding tailwindcss to devDependencies) and will also update the scripts and Procfile.dev entries.

The key here is to keep both build commands around. In this case sass as well as tailwindcss:

{
  "name": "app",
  "private": "true",
  "dependencies": {
    ...
    "bulma": "^0.9.3",
    "sass": "^1.45.0",
    ...
  },
  "scripts": {
    "build:css": "sass ./app/assets/stylesheets/application.bulma.scss ./app/assets/builds/bulma_application.css --no-source-map --load-path=node_modules && tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/tailwind_application.css --minify",
    "build": "..."
  },
  "devDependencies": {
    "tailwindcss": "^3.3.2"
  }
}

The entrypoint files are already without conflict but I changed the output names for the final CSS files to bulma_application and tailwind_application.

The layout will now have to require both files:

<%= stylesheet_link_tag "bulma_application", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "tailwind_application", "data-turbo-track": "reload" %>

Now things should generally work when running bin/rails assets:precompile.

The last step is to update the Procfile.dev. We can add each framework task as a separate script and then run both separately with Foreman:

  ...
  "scripts": {
    "build:bulma": "sass ./app/assets/stylesheets/application.bulma.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules",
    "build:tailwind": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/tailwind_application.css --minify",
    "build:css": "sass ./app/assets/stylesheets/application.bulma.scss ./app/assets/builds/application.css --no-source-map --load-path=node_modules && tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/tailwind_application.css --minify",
    "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds"
  }
}

And:

$ cat Procfile.dev
web: bin/rails server -p 3000
bulma: yarn build:bulma --watch
tailwind: yarn build:tailwind --watch
...

Conclusion

cssbundling-rails can run more CSS frameworks if you want to and I was able to copy & paste Tailwind components into my Bulma layout.

Check out my book
Interested in Ruby on Rails default testing stack? Take Minitest and fixtures for a spin with my latest book.

Get Test Driving Rails and make your tests faster and easier to maintain.

by Josef Strzibny
RSS