Ruby on Rails 7 - High-performance frontend development with Esbuild, Rollup & Vite
Tags: rails, esbuild, rollup, vite
Published: 2022-10-10 09:59:04

# Ruby on Rails 7 - High-performance frontend development with Esbuild, Rollup & Vite

The title is a little misleading but not entirely. Keep reading; it will make sense.

##  Current options

There are four options when scaffolding a Rails 7 app:
```bash
 -j, [--javascript=JAVASCRIPT] # Choose JavaScript approach [options: importmap (default), webpack, esbuild, rollup]
```

- `importmap` - lets you import the libs you need without transpiling or bundling. 

The other three offer bundle and transpiling of JavaScript in Rails via [jsbundling-rails](https://github.com/rails/jsbundling-rails)  

-  `webpacker` -  Note that Webpacker has been retired, and it is not recommended for use unless you want the additional overhead and pain ;) That said, the folks over at [ShakaCode](%09https://www.shakacode.com/) are maintaining a fork named [shakapacker](https://github.com/shakacode/shakapacker)  

- `esbuild` - A super fast pre-bundler built in Go. Ideal for development.

- `rollup`  -  A decent bundler for production-optimized code.

In an ideal world, you’d want `esbuild` in your development environment for fast precompiling and page reloads as you change your views. For preparing your assets for production, `rollup` would be the right tool.   
This makes Vite a good choice, as it does precisely this. It uses `esbuild` and `rollup` under the hood leveraging each one’s strengths.

---- 

## Vite is what?

So then, what is Vite? (pronounced _veet_)
Is Vite just a wrapper around these pre-compilers and bundlers? Not exactly. 

> At the very basic level, developing using Vite is not that much different from using a static file server. However, Vite provides many enhancements over native ESM imports to support various features that are typically seen in bundler-based setups. - [ Features - Vite Guide](https://vitejs.dev/guide/features.html "Vite Guide")
  
The Vite JS site promotes the following features, among others.  

-  ** Instant Server Start** - On-demand file serving over native ESM, no bundling required!
-  **Lightning Fast HMR** - Hot Module Replacement (HMR) that stays fast regardless of app size.
- **Rich Features** - Out-of-the-box support for TypeScript, JSX, CSS and more.
-  **Optimized Build**  - Pre-configured Rollup builds with multi-page and library mode support.

---- 

## Why not just use esbuild?

> While esbuild is blazing fast and is already a very capable bundler for libraries, some of the important features needed for bundling applications are still work in progress - in particular, code-splitting and CSS handling. For the time being, Rollup is more mature and flexible in these regards. That said, we won't rule out the possibility of using esbuild for production builds when it stabilizes these features in the future. - [Why Not Bundle with esbuild? -Vite Guide](https://vitejs.dev/guide/why.html#why-not-bundle-with-esbuild)


---- 

## Getting started

Setting up Vite in a Rails project is straightforward, thanks to the excellent [Vite Ruby](https://vite-ruby.netlify.app/) implementation.
For our setup, we will include TailwindCSS to test that it works.

To have Rails not default to `importmaps` and generate the `app/javascript` directory for us, we will install `esbuild` and then uninstall it.
Installing Vite will add all the dependencies it requires, including `esbuild` and `rollup`

Assuming you are running `Ruby 3.1.2+` & `Rails 7.0.4+`

Create the new rails app with the following options
```bash
rails new my_vite_app -j esbuild -d postgresql
```

```bash
cd my_app
```

Run the following
```bash
yarn remove esbuild
```

And remove the following line from the package.json scripts section
```bash
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"
```

Remove the following from the Gemfile
```bash
# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
gem "jsbundling-rails"
```

Add the following to the Gemfile
```bash
gem "vite_rails"
gem "vite_ruby"
```

And run bundler 
```bash
bundle
```

Run the Vite installer
```bash
bundle exec vite install
```

Install TailwindCSS and several dev plugins including  `vite-plugin-full-reload`
```bash
yarn add tailwindcss @tailwindcss/forms @tailwindcss/typography @tailwindcss/aspect-ratio @tailwindcss/forms @tailwindcss/line-clamp autoprefixer
```

```bash
yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-tailwindcss path vite-plugin-full-reload vite-plugin-stimulus-hmr
```

Replace the contents of vite.config.js with
```js
import {defineConfig} from 'vite'
import FullReload from "vite-plugin-full-reload"
import RubyPlugin from 'vite-plugin-ruby'
import StimulusHMR from 'vite-plugin-stimulus-hmr'

export default defineConfig({
      clearScreen: false,
      plugins: [
          RubyPlugin(), 
          StimulusHMR(), 
          FullReload(["config/routes.rb", "app/views/**/*"], {delay: 300}),
      ],

    }
)
```

Create the `tailwind.config.js` 
```bash
tailwind init
```

Replace the contents of `tailwind.config.js` with the following
```js
/** @type {import('tailwindcss').Config} */
const colors = require('tailwindcss/colors')
const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  content: [
    './app/helpers/**/*.rb',
    './app/assets/stylesheets/**/*.css',
    './app/views/**/*.{html,html.erb,erb}',
    './app/javascript/components/**/*.js',
  ],
  theme: {
    fontFamily: {
      'sans': ["BlinkMacSystemFont", "Avenir Next", "Avenir",
        "Nimbus Sans L", "Roboto", "Noto Sans", "Segoe UI", "Arial", "Helvetica",
        "Helvetica Neue", "sans-serif"],
      'mono': ["Consolas", "Menlo", "Monaco", "Andale Mono", "Ubuntu Mono", "monospace"]
    },
    extend: {
    },
  },
  corePlugins: {
    aspectRatio: false,
  },
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
    require('@tailwindcss/aspect-ratio'),
    require('@tailwindcss/line-clamp'),
  ],
}

```

Replace the contents of `app/assets/stylesheets/application.css` with the following
```css
@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";
```

Create a file named `application.css` in  `app/javascript/entrypoints/`
```bash
echo '@import "../../assets/stylesheets/application.css";' > app/javascript/entrypoints/application.css
```

Create a file named `postcss.config.js` in the project root and add the following
```js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
```

In `app/views/layouts/application.html.erb`, change the `_tags` to the following.

```eruby
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<%= vite_client_tag %>
<%= vite_stylesheet_tag 'application', data: { "turbo-track": "reload" } %>
<%= vite_javascript_tag 'application' %>

```

Now we'll add a home page to test our setup
```bash
rails g controller Home index 
```

And make this the root path by adding the following to the `config/routes.rb.` 
```ruby
root 'home#index'
```

Replace the contents of `app/views/home/index.html.erb`  this the following  

```eruby
<div class="relative flex min-h-screen flex-col justify-center overflow-hidden bg-gray-50 py-6 sm:py-12">
  <div class="relative mx-32 bg-white px-6 pt-10 pb-8 shadow-xl sm:rounded-lg sm:px-10">
    <div class="mx-auto">
      <img src="https://vite-ruby.netlify.app/logo.svg" class="h-32" />
      <h1 class="mt-6 mb-12 text-center font-sans text-5xl">Vite Rails</h1>
      <section class="justify-center-center my-0 mx-auto flex flex-wrap justify-between px-8 pt-0 pb-16 leading-6 tracking-normal">
        <a class="max-h-32 max-w-[23rem] flex-shrink flex-grow-0 cursor-pointer rounded-lg bg-gray-200 py-6 px-8 text-sm font-medium leading-6 tracking-normal shadow-xl" href="/">
          <h2 class="mx-0 mt-0 mb-3 cursor-pointer text-xl font-semibold leading-6 tracking-tight">🔥 Fast Server Start</h2>
          <p class="m-0 cursor-pointer text-sm font-medium leading-6 tracking-normal">Unlike Webpacker, files are processed on demand!</p>
        </a>

        <a class="my-6 max-h-32 max-w-[23rem] flex-shrink flex-grow-0 cursor-pointer rounded-lg bg-gray-100 bg-transparent py-6 px-8 text-sm font-medium leading-6 tracking-normal shadow-xl" href="/">
          <h2 class="mx-0 mt-0 mb-3 cursor-pointer text-xl font-semibold leading-6 tracking-tight">⚡️ Instant Changes</h2>
          <p class="m-0 cursor-pointer text-sm font-medium leading-6 tracking-normal">Fast updates thanks to HMR. Goodbye full-page reloads!</p>
        </a>

        <a class="max-h-32 max-w-[23rem] flex-shrink flex-grow-0 cursor-pointer rounded-lg bg-gray-200 bg-transparent py-6 px-8 text-sm font-medium leading-6 tracking-normal shadow-xl" href="/">
          <h2 class="mx-0 mt-0 mb-3 cursor-pointer text-xl font-semibold leading-6 tracking-tight">🚀 Zero-Config Deploys</h2>
          <p class="m-0 cursor-pointer text-sm font-medium leading-6 tracking-normal">Integrates with Rake asset management tasks.</p>
        </a>
      </section>
    </div>
  </div>
</div>

```

Replace the contents of `Procfile.dev` with 
```bash
web: bin/rails server -p 3000
vite: bin/vite dev --clobber
```

Create the database and run migrations
```bash
rails db:create db:migrate
```

Start the web and vite processes
```bash
./bin/dev
```

Go to  [127.0.0.1:3000](http://127.0.0.1:3000/)

You should see this
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0d4swh6lvacu4vzo1rcv.png)

Try editing some text or the Tailwind utility classes and watch the browser view update immediately with your changes when you save.

## Conclusion  

Vite does what it says on the box.   
For more on Rails integration, head over to the [docs](https://vite-ruby.netlify.app/guide/rails.html).

If you want a repo with an example of the above, that comes with some sensible semantic HTML styling out of the box. Check out [SimpleTails](https://github.com/davidteren/simple_tails)