New way of bundling JS/CSS in Rails
Tags: rails, tailwindcss, tutorial, beginners
Published: 2021-09-12 09:53:16

Apologies for misleading you with a vague title, but I wouldn't call this approach the new way, because under the hood `webpacker`  and `sass-rails` were doing all of the bundlings without us needing to tinker with the configuration. For simpler applications, this was rock solid, but for complex applications, `webpacker` kind of stood in the way. But all is good now since the advent of two gems [`jsbundling-rails`](https://github.com/rails/jsbundling-rails) and [`cssbundling-rails`](https://github.com/rails/cssbundling-rails).

So let's see what these gems have to offer. We'll start with a bare-bone version of rails without any JavaScript.

```bash
rails new myapp --skip-javascript
```

After initializing the rails app, go to the Gemfile and add these two gems that we'll be using.

```ruby
gem 'jsbundling-rails'
gem 'cssbundling-rails'
```

Run `bundle install` after that. 

In this demo, we'll be using `webpack` and `tailwindcss` as the example.

```bash
./bin/rails javascript:install:webpack
./bin/rails css:install:tailwind
```

These generators should create a `Procfile.dev`, `webpack.config.js`, and more. Go to the `app/javascript/application.js` and write some JavaScript code. Now run `./bin/dev` and with any luck, your JavaScript and CSS should be bundled and compiled into `app/assets/builds` directory into two files, namely: `application.css` and `application.js`. This is so much better because now we can use

```erb
<%= stylesheet_link_tag "application", "data-turbo-track": "reload", media: "all" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
```
into our `layouts/application.html.erb` file.

Having `webpack.config.js` in our root directory is even better. It's all plain vanilla `webpack` without any fancy wrappers around it and hence we can customize it to our hearts.

However, the above-mentioned approach to using `tailwind` in our app is limited. For example, we cannot use `@layer` or any other fancy `tailwind` syntax because it depends on `postcss` and other jazz. Again, for simpler applications it's fine, but a complex one may need those syntaxes and may need to write `scss` instead of `css` alone.

Although we can revert the changes that we made above easily, for ease of understanding let's spin up a new rails app instead.

```bash
rails new myapp2 --skip-javascript
```

This time we'll only be installing the `jsbundling-rails` gem.

```bash
./bin/rails javascript:install:webpack
```

As usual, the generator will create the `Procfile.dev`, `webpack.config.js`, and more.

So in this approach to installing TailwindCSS, we'll be installing `postcss` and its plugins to handle all situations.

```bash
yarn add tailwindcss autoprefixer css-loader mini-css-extract-plugin postcss postcss-import postcss-loader
```

That was quite a lot. But you can add or remove these libraries depending on your application's need.

Our approach will be the same. We'll output two files, namely: `application.css` and `application.js` in the `app/assets/builds` directory so that we can use the `stylesheet_link_tag` and `javascript_include_tag` in our `layout/application.html.erb` file.

Go to the `app/assets/stylesheets/application.css` and rename this file to `application.scss`, and also paste the following lines of code.

```scss
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
```

Let's configure our `webpack.config.js`

```js
const path = require('path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  mode: 'production',
  entry: {
    application: [
      './app/javascript/application.js',
      './app/assets/stylesheets/application.scss',
    ],
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'app/assets/builds'),
  },
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
    new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 1,
    }),
  ],
}
```

As you can see, we have defined two entry points inside the `application` array. If you have separate assets for your admin page or a marketing page, you can define another array of entry points and `webpack` will spit out the compiled assets into separate files.

Also, take note of the `plugins` array. The `MiniCssExtractPlugin` is responsible for compiling the stylesheet and then spitting it out into the defined directory.

Now the final piece of the puzzle is to configure `postcss` and `manifest.js`. Create a file called `postcss.config.js` in the root of the project directory and write down the following lines.

```js
module.exports = {
  plugins: [
    require('postcss-import'),
    require('tailwindcss'),
    require('autoprefixer'),
  ],
}
```

```scss
// app/assets/config/manifest.js

//= link_tree ../images
//= link_tree ../builds
```

Now run `./bin/dev` and check the `app/assets/builds` directory for your compiled assets. It wasn't that hard, was it.

I'm excited about where rails is heading to. We started with `hotwire` and now we have new asset bundling options. I wonder what's next. Now is the time to hop into the rails ecosystem if you haven't already.

Cover picture: https://unsplash.com/photos/cvBBO4PzWPg