DEV Community

David Teren
David Teren

Posted on

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

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:

 -j, [--javascript=JAVASCRIPT] # Choose JavaScript approach [options: importmap (default), webpack, esbuild, rollup]
Enter fullscreen mode Exit fullscreen mode
  • 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 postgresql
Enter fullscreen mode Exit fullscreen mode
cd my_app
Enter fullscreen mode Exit fullscreen mode

Run the following

yarn remove esbuild
Enter fullscreen mode Exit fullscreen mode

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

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

Remove the following from the Gemfile

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

Add the following to the Gemfile

gem "vite_rails"
gem "vite_ruby"
Enter fullscreen mode Exit fullscreen mode

And run bundler

 

bundle
Enter fullscreen mode Exit fullscreen mode

Run the Vite installer

bundle exec vite install
Enter fullscreen mode Exit fullscreen mode

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 autoprefixer
Enter fullscreen mode Exit fullscreen mode
yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-tailwindcss path vite-plugin-full-reload vite-plugin-stimulus-hmr
Enter fullscreen mode Exit fullscreen mode

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}),
      ],

    }
)
Enter fullscreen mode Exit fullscreen mode

Create the tailwind.config.js

 

tailwind init
Enter fullscreen mode Exit fullscreen mode

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'),
  ],
}

Enter fullscreen mode Exit fullscreen mode

Replace the contents of app/assets/stylesheets/application.css with the following

@import "tailwindcss/base";

@import "tailwindcss/components";

@import "tailwindcss/utilities";
Enter fullscreen mode Exit fullscreen mode

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

echo '@import "../../assets/stylesheets/application.css";' > app/javascript/entrypoints/application.css
Enter fullscreen mode Exit fullscreen mode

Create a file named postcss.config.js in the project root and add the following

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Now we'll add a home page to test our setup

rails g controller Home index 
Enter fullscreen mode Exit fullscreen mode

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

 

root 'home#index'
Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

Replace the contents of Procfile.dev with

 

web: bin/rails server -p 3000
vite: bin/vite dev --clobber
Enter fullscreen mode Exit fullscreen mode

Create the database and run migrations

rails db:create db:migrate
Enter fullscreen mode Exit fullscreen mode

Start the web and vite processes

./bin/dev
Enter fullscreen mode Exit fullscreen mode

Go to 127.0.0.1:3000

You should see this
Image description

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

Latest comments (4)

Collapse
 
sjieg profile image
Gijs Paulides

Article really helped me getting started with Vite Rails, thanks!

Besides the previous comments talking about using yarn tailwind init instead of tailwind init, you could use vite-plugin-rails instead of vite-plugin-ruby to automatically include the recommended rails plugins:

// vite.config.ts
import {defineConfig} from 'vite'
import ViteRails from "vite-plugin-rails"

export default defineConfig({
        clearScreen: false,
        plugins: [
            ViteRails({
                fullReload: {
                    additionalPaths: ["config/routes.rb", "app/views/**/*"],
                    delay: 300
                }
            }),
        ],
    }
)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
rkumar profile image
r__k_u_m_a_r • Edited

tailwind init gives me a no such command.

Also in the previous step the file had a ".ts" extension, not ".js" as you mention.

In any case, I skipped tailwind init and created the file manually. And it's worked!!!

It would be super if you could show how you integrate react into this.

Collapse
 
iammadanlal profile image
Madan Lal • Edited

You can also do 'yarn tailwind init' or 'npx tailwind init'.

Collapse
 
saintjamesfr profile image
saint-james-fr

Thank you, great ressource, this is dope!