Rails 7 with Vite + Stimulus + Tailwind

Sasikala Ravichandran
5 min readJun 14, 2023

Rails 7 comes with the importmap but still, Vite is used popularly for Instant Server Start and Lightning Fast HMR features. It uses esbuild(fast pre-bundler) and rollup(bundler) under the hood leveraging their strengths for both dev and prod environments.

Also, Building and cleaning Vite assets are automatically integrated with assets: precompile and assets:clean, so deploying is straightforward.

Step 1: Installing Vite

gem "vite_rails"
bundle install
bundle exec vite install

The vite install will generate the following files

# Procfile for dev env to start both rails and vite server
Procfile.dev
vite: bin/vite dev
web: bin/rails s

# Only Vite entry files - mainly used for imports
app/javascript/entrypoints/

# Executable to start the dev server
bin/vite

# Defines the node dependencies
package.json
package-lock.json

# Configuration for the vite for the app
vite.config.ts

# Setting for the Vite
config/vite.json
  1. A sample file is created app/javascriot/entrypoints/application.js in the web app.
  2. Vite will also add the following tags to the app/views/layouts/application.html.erb
  • vite_javascript_tag: Renders a <script> tag referencing a JavaScript file
  • vite_typescript_tag: Renders a <script> tag referencing a TypeScript file
  • vite_stylesheet_tag: Renders a <link> tag referencing a CSS file

3. Configure the Vite in the vite.config.ts file. The basic configuration is like adding RubyPlugin is done for us by the vite-rails gem. Apart from this, vite-plugin-full-reload can be added.

Import using import FullReload from "vite-plugin-full-reload";
Add plugin FullReload(["config/routes.rb", "app/views/**/*"], { delay: 200 })

`vite.config.ts`

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import FullReload from "vite-plugin-full-reload";

export default defineConfig({
clearScreen: false,
plugins: [
RubyPlugin(),
FullReload(["config/routes.rb", "app/views/**/*"], { delay: 200 })
],
root: "./app/assets",
build: {
manifest: true,
rollupOptions: {
input: "/app/javascript/entrypoints/application.js"
}
}
})

4. Vite also comes with an auto build feature which enables it to not run the Vite dev server and Vite-rails will automatically detect the option "autoBuild": true in the vite.json .

If there is any code happening in the file mentioned in the sourceCodeDir and watchAdditionalPaths option, it triggers a build automatically when the asset is requested.

vite.json

{
"all": {
"sourceCodeDir": "app/javascript",
"watchAdditionalPaths": []
},
"development": {
"autoBuild": true,
"publicOutputDir": "vite-dev",
"port": 3036
},
"test": {
"autoBuild": true,
"publicOutputDir": "vite-test",
"port": 3037
}
}

5. Vite builds the assets and stores them in public/vite-dev and serves the bundled CSS and JS files. The manifest.json file includes the CSS and Js files along with assets from mainfest-assets.json

Building with Vite ⚡️
vite v4.3.9 building for development...
transforming...

✓ 7 modules transformed.

rendering chunks...
computing gzip size...
../../public/vite-dev/manifest-assets.json 0.00 kB │ gzip: 0.02 kB
../../public/vite-dev/manifest.json 0.30 kB │ gzip: 0.14 kB
../../public/vite-dev/assets/application-23aabef0.css 9.26 kB │ gzip: 2.39 kB
../../public/vite-dev/assets/application-cda856d1.js 43.44 kB │ gzip: 10.95 kB
✓ built in 481ms
Build with Vite complete: /Users/projects/app/public/vite-de

6. Now, Restarting the rails server will output these lines in the browser console. The console.log is from app/javascriot/entrypoints/application.js the file

Vite ⚡️ Ruby
Visit the guide for more information: <https://vite-ruby.netlify.app/guide/rails>

All set to write JavaScript functionality of the app with Vite! 😃

Step 2: Vite + Stimulus

The Vite.js has plugins for frameworks like ReactJs, and VueJs. Also, it has a plugin for stimulus framework. In Rails 7, gem 'stimulus-rails' automatically gets configured. So to combine vite and stimulus, let’s do the following

1. Generate the stimulus files

gem 'stimulus-rails' #ignore if already added by rails
bundle install
rails stimulus:install #Generates the following files

# app/javascript/controllers/index.js
# app/javascript/controllers/application.js
# app/javascript/controllers/hello_controller.js

2. Rails add the initial setup for stimulus in app/javascript/controllers/application.js and app/javascript/controllers/index.js. Since we are going to configure the Stimulus using Vite, we can go ahead and comment on all the lines from these two files.

3. Uncomment alsothe following imports from app/javascript/application.js

// import "@hotwired/turbo-rails"
// import "controllers"

4. Add the two Vite plugins that can be used for Stimulus

a) stimulus-vite-helpers — Helpers to easily load all your Stimulus controllers when using Vite.js

b) vite-plugin-stimulus-hmr — HMR for Stimulus controllers in Vite.js

yarn add -D @hotwired/stimulus
yarn add -D stimulus-vite-helpers
yarn add -D vite-plugin-stimulus-hmr

The yarn will add the dependencies to the package.json file.

5. Configure the Vite for stimulus HMR plugin. Now, vite.config.js is going to look like this

import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import FullReload from 'vite-plugin-full-reload';
import StimulusHMR from 'vite-plugin-stimulus-hmr';

export default defineConfig({
clearScreen: false,
plugins: [
RubyPlugin(),
FullReload(["config/routes.rb", "app/views/**/*"], { delay: 200 }),
StimulusHMR()
],
root: "./app/assets",
build: {
manifest: true,
rollupOptions: {
input: "/app/javascript/entrypoints/application.js"
}
}
}

6. Add the following in the app/javascript/controllers/index.js. The Stimulus controllers are registered using Vite's globEager and the registerControllers helper

import { Application } from '@hotwired/stimulus'
import { registerControllers } from 'stimulus-vite-helpers'

const application = Application.start()
const controllers = import.meta.globEager('./**/*_controller.js')
registerControllers(application, controllers)

7. Import the Stimulus controller on Vite’s entry point file.

Add the following in app/javascript/entrypoints/application.js

import '../controllers'

8. To test the hello stimulus controller working, add the following in the app/views/layouts/application.html.erb

<div data-controller="hello">
<input data-hello-target="name" type="text">/</input>
<input data-hello-target="output" type="text"></input>
<button data-action="click->hello#greet">Greet</button>
</div>

We are all set with Stimulus that can be bundled using the Vite

Step 3: Vite + Tailwind

  1. Add the tailwind and other necessary packages using yarn
yarn add -D 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

2. Create a tailwind.config.js configuration file under the project root.

npx tailwindcss init

Add the following code to the tailwind.config.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'),
],
}

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

@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

4. Create a file named application.css in app/javascript/entrypoints/and import the asset's stylesheets

@import "../../assets/stylesheets/application.css";

5. Create a file named postcss.config.js in the project root

touch postcss.config.js

Add the following

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

6. In app/views/layouts/application.html.erb,
use the vite_client_tag, vite_stylesheet_tag and vite_javascript_tag instead of the default js and css tags.

<%= csrf_meta_tags %>
<%= csp_meta_tag %>

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

7. Make sure that the compiled assets are delivered to the browser using the Vite by inspecting the page.

<link rel="stylesheet" href="/vite-dev/assets/application-23aabef0.css" data-turbo-track="reload">
<script src="/vite-dev/assets/application-cda856d1.js" crossorigin="anonymous" type="module"></script>

8. If needed, run the Vite server to get the tailwind styling applied

bin/vite dev - clobber

We are set to write the app-related JS and CSS code and let the Vite bundle these assets for our app!

--

--

Sasikala Ravichandran

A coder with a keen interest on writing highly maintainable, secure and efficient code. http://sasi-kala.com/