ViewComponent, Hotwire & Tailwind with Propshaft and Importmap in Rails 7.2

Markus - Sep 18 - - Dev Community

Introduction

In this blog post, we explore how to configure ViewComponent, a framework for creating reusable, testable and encapsulated view components, Hotwire - more specifically, Stimulus - and Tailwind in a Rails 7.2 application with Propshaft, the asset pipeline planned for Rails 8, and the node- and yarn-less Import Maps. Additionally, we will be making use of view_compenent-contrib, which provides nice extra features for ViewComponent. This guide aims to provide a walkthrough, whether you're just starting out with these technologies or looking to integrate them into an existing Rails application. Let's dive in.

Setup

This guide assumes you have created your Rails 7 app with Hotwire, Propshaft, Import Maps and Tailwind CSS. If you don't, you can create a new application with the rails command:

rails new . -a propshaft -j importmap -c tailwind --skip-test
Enter fullscreen mode Exit fullscreen mode

Hotwire is included in Rails 7.2 by default. Hotwire is a set of tools for building modern web applications without using much JavaScript.

Propshaft is a asset pipeline for Rails.

Importmap is a Rails engine that provides a modern way to manage JavaScript dependencies in Rails.

Tailwind CSS for Rails offers ...class="text-sky-500 dark:text-sky-400">Tailwind</...> in Rails.

We skip the default test framework minitest because we will use RSpec for testing:

bundle add rspec-rails
rails generate rspec:install
Enter fullscreen mode Exit fullscreen mode

ViewComponent

Installation

To install ViewComponent you can use the convenient install script from the view_compenent-contrib gem:

rails app:template LOCATION="https://railsbytes.com/script/zJosO5"
Enter fullscreen mode Exit fullscreen mode

It's recommended to read the documentation of view_compenent-contrib first before running the script.

The script needs some questions to be answered, so let's go through them:

  • Where do you want to store your view components? (default: app/frontend/components) take the default
  • Would you like to use dry-initializer in your component classes? (y/n) y/n (what you prefer)
  • Do you use Stimulus? (y/n) y
  • Do you use TailwindCSS? (y/n) y
  • Would you like to create a custom generator for your setup? (y/n) y
  • Which template processor do you use? (1) ERB, (2) Haml, (3) Slim, (0) Other 1 I'm using ERB, but you can choose your preferred template language

This script installs the ViewComponent and the view_compenent-contrib gems, the latter provides a lot of useful things to work with ViewComponent. It also adds the app/frontend/components directory for your view components, the base classes ApplicationViewComponent and ApplicationViewComponentPreview and configures our testing framework rspec to work with ViewComponent. Last but not least, it creates a custom generator for creating new view components.

Configuration

view_compenent-contrib assumes the usage of Vite or Webpacker for JavaScript bundling. As we use Propshaft and Importmap in this guide, we have to make some adjustments, to make it work. Most of the configuration snippets comes from this discussion and the comments from light-flight which helped me a lot.

To configure view_compenent-contrib with Tailwind CSS, Propshaft and Importmap, add the following snippets to your Rails application.

config/application.rb:

# ViewComponents
# Paths for view components
FRONTEND_PATH = "app/frontend"
VIEW_COMPONENTS_PATH = "app/frontend/components"
# Path for view components
config.view_component.view_component_path = VIEW_COMPONENTS_PATH
# Eager load view components
config.eager_load_paths << Rails.root.join(VIEW_COMPONENTS_PATH)
Enter fullscreen mode Exit fullscreen mode

config/environments/development.rb:

# Watches the frontend directory for changes and reloads the importmap
config.importmap.cache_sweepers << Rails.root.join(Horizon::Application::FRONTEND_PATH)
# ViewComponent - path for preview view components
config.view_component.preview_paths << Rails.root.join(Horizon::Application::VIEW_COMPONENTS_PATH)
# Auto load view components in development
config.autoload_paths << Rails.root.join(Horizon::Application::VIEW_COMPONENTS_PATH)
Enter fullscreen mode Exit fullscreen mode

config/initializers/assets.rb:

# ViewComponents
Rails.application.config.assets.paths << Rails.root.join(Horizon::Application::FRONTEND_PATH)
Enter fullscreen mode Exit fullscreen mode

config/importmap.rb:

# Pin stimulus controllers
pin_all_from "app/frontend/components", under: "components"
Enter fullscreen mode Exit fullscreen mode

app/javascript/controllers/index.js:

// Eager loads all view component controllers from the importmap, provided by https://github.com/palkan/view_component-contrib/discussions/14#discussioncomment-8434576
function registerController(name, module, application) {
  if (name in registeredControllers) return;

  application.register(name, module.default);
  registeredControllers[name] = true;
}

function registerControllerFromPath(path, application) {
  const name = path
    .match(/components\/(.+)\/controller$/)[1]
    .replaceAll("/", "-")
    .replaceAll("_", "-");

  import(path)
    .then((module) => registerController(name, module, application))
    .catch((error) => console.debug(`Failed to register controller: ${name} (${path})`, error));
}
const registeredControllers = {};
const imports = JSON.parse(document.querySelector("script[type=importmap]").text).imports;
const paths = Object.keys(imports).filter((path) => path.match(new RegExp(`^components/*`)));
paths.forEach((path) => registerControllerFromPath(path, application));
Enter fullscreen mode Exit fullscreen mode

Change your app/frontend/components/application_view_component.rb, so we can use the controller_name method for the Stimulus controller in our view components:

class ApplicationViewComponent < ViewComponentContrib::Base
  extend Dry::Initializer

  private

  def identifier
    @identifier ||= self.class.name.sub("::Component", "").underscore.split("/").join("--")
  end

  # Use this in your component template, e.g. <div data-controller="<%= controller_name %>"></div>
  alias controller_name identifier
end
Enter fullscreen mode Exit fullscreen mode

And finally, add the frontend/components directory to your Tailwind CSS configuration:

content: [
  "./app/frontend/components/**/*.html.erb", // ViewComponent templates
  "./app/frontend/components/**/*.rb", // ViewComponent classes
  "./public/*.html",
  "./app/helpers/**/*.rb",
  "./app/javascript/**/*.js",
  "./app/views/**/*.{erb,haml,html,slim}",
],
Enter fullscreen mode Exit fullscreen mode

Usage

Now you can create a new view component with the following command:

./bin/rails g view_component Example
Enter fullscreen mode Exit fullscreen mode

This will create a new view component in the app/frontend/components directory with the following structure:

app/frontend/components/example
├── component.rb
├── component.html.erb
└── component_preview.rb
Enter fullscreen mode Exit fullscreen mode

You can render the component in your views with:

<%= render Example::Component.new %>
Enter fullscreen mode Exit fullscreen mode

Try out the example at the Style Variants section of the view_component-contrib documentation to see how you can use Tailwind CSS with ViewComponent and define your styling rules in a declarative way.

Also add a stimulus controller to your view component to test if the controller is loaded correctly:

app/frontend/components/example/controller.js:

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  connect() {
    console.log("Hello from the example controller!");
  }
}
Enter fullscreen mode Exit fullscreen mode

And add the following code to your view component template:

app/frontend/components/example/example.html.erb:

<div data-controller="example">
    <h1>Hello World!</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

When you refresh your browser, you should see the following output in the console:

Hello from the example controller!
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this guide, we installed and configured ViewCompoment including view_component-contrib, Tailwind and Stimulus in a Rails setup with Propshaft and Import Map. I hope this guide helps you to set up your Rails application and if you have any questions or feedback, please let me know in the comments below. Thank you for reading!

.
Terabox Video Player