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

David Teren
6 min readOct 10, 2022

--

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:

-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

  • 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 are maintaining a fork named 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

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

Getting started

Setting up Vite in a Rails project is straightforward, thanks to the excellent Vite Ruby 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

rails new my_vite_app -j esbuild -d postgresqlcd my_vite_app

Run the following

yarn remove esbuild

And remove the following line from the package.json scripts section

"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets"

Remove the following from the Gemfile

# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
gem "jsbundling-rails"

Add the following to the Gemfile

gem "vite_rails"
gem "vite_ruby"

And run bundler

bundle

Run the Vite installer

bundle exec vite install

Install TailwindCSS and several dev plugins including vite-plugin-full-reload

yarn add tailwindcss @tailwindcss/forms @tailwindcss/typography @tailwindcss/aspect-ratio @tailwindcss/forms @tailwindcss/line-clamp autoprefixeryarn 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

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

tailwind init

Replace the contents of tailwind.config.js with the following

/** @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

@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";

Create a file named application.css in app/javascript/entrypoints/

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

module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

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

<%= 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

rails g controller Home index

And make this the root path by adding the following to the config/routes.rb.

root 'home#index'

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

<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

web: bin/rails server -p 3000
vite: bin/vite dev --clobber

Create the database and run migrations

rails db:create db:migrate

Start the web and vite processes

./bin/dev

Go to 127.0.0.1:3000

You should see this

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.

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

--

--

David Teren
David Teren

Written by David Teren

Software Engineering Team Lead @ Prodigy Finance. All things Ruby on Rails.

Responses (1)