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]
-
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 shakapackeresbuild
- 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
cd my_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 autoprefixer
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
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
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
Top comments (4)
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.
You can also do 'yarn tailwind init' or 'npx tailwind init'.
Article really helped me getting started with Vite Rails, thanks!
Besides the previous comments talking about using
yarn tailwind init
instead oftailwind init
, you could usevite-plugin-rails
instead ofvite-plugin-ruby
to automatically include the recommended rails plugins:Thank you, great ressource, this is dope!