ViewComponent: How to build a library

13 Mar 22

This is the first post in a series that focuses on the ViewComponent gem by GitHub. The series will document my progress as I build a library of reusable components. All the source code used to generate this series of blog posts is on GitHub.

#Getting started

To get started, we're going to create a Rails engine. For our ViewComponent library, we can overlook most aspects of Rails and keep our setup minimal.

Make sure you have Rails installed and run rails plugin new with the name of your library and the options detailed below.

rails plugin new view_component_library_example \
--mountable \
--skip-action-mailer \
--skip-action-mailbox \
--skip-action-text \
--skip-active-storage \
--skip-active-record \
--skip-active-job \
--skip-action-cable \
--skip-hotwire \
--skip-jbuilder \
--skip-test \
--skip-asset-pipeline \
--dummy_path=spec/dummy \

This will create a Rails engine with a dummy app in spec/dummy (we'll come back to that later). As well as keeping our initial setup as slim as possible, we can also remove unused app folders (controllers, helpers, models, views).

Since we're building an engine for ViewComponents only, we don't need to define the whole of Rails as a dependency. The gemspec can be updated to require the bare minimum dependencies.

--- a/view_component_library_example.gemspec
+++ b/view_component_library_example.gemspec


- spec.add_dependency "rails", ">= 7.0.2.3"
+ spec.add_dependency "activemodel", ">= 7.0.2.3"
+ spec.add_dependency "railties", ">= 7.0.2.3"

We can also comment out unused railties in bin/rails.

# bin/rails

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
# require "active_job/railtie"
# require "active_record/railtie"
# require "active_storage/engine"
# require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_view/railtie"
# require "action_cable/engine"
# require "rails/test_unit/railtie"
require "rails/engine/commands"

Once the gemspec and binstub have been updated, run bundle to install the dependencies.

#Installing dependencies

Before we can start building components our library, we need to install a couple more dependencies — namely RSpec and ViewComponent.

#RSpec

To install RSpec, first add rspec-rails as a development dependency in the gemspec file.

--- a/view_component_library_example.gemspec
+++ b/view_component_library_example.gemspec

spec.add_dependency "activemodel", ">= 7.0.2.3"
spec.add_dependency "railties", ">= 7.0.2.3"
+ spec.add_development_dependency "rspec-rails"

After running bundle again, we can run the RSpec installation generator.

bin/rails generate rspec:install

This will generate all the helper files needed to run our tests, although, we need to make a small change to rails_helper.rb. before we can run tests successfully.

--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb

- require_relative '../config/environment'
+ require_relative 'dummy/config/environment'

#ViewComponent

Similarly, to install ViewComponent, we need to add view_component as a dependency in the gemspec file before running bundle once again.

--- a/view_component_library_example.gemspec
+++ b/view_component_library_example.gemspec

spec.add_dependency "activemodel", ">= 7.0.2.3"
spec.add_dependency "railties", ">= 7.0.2.3"
+ spec.add_dependency "view_component", ">= 2.50.0"

spec.add_development_dependency "rspec-rails"

Before we can use ViewComponent, we need to require view_component in our library code.

--- a/lib/view_component_library_example.rb
+++ b/lib/view_component_library_example.rb

+ require "view_component"
require "example_library/version"
require "example_library/engine"

module ViewComponentExampleLibrary
  # Your code goes here...
end

RSpec can also be configured to include the ViewComponent test helpers in component tests by default.

--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb

require 'rspec/rails'

# Add additional requires below this line. Rails is not loaded until this point!
+ require "view_component/test_helpers"

RSpec.configure do |config|
  config.use_active_record = false
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
+ config.include ViewComponent::TestHelpers, type: :component
end

#Creating the first component

Now we have configured our Rails engine and installed our library dependencies, we can start creating some components.

To begin with, let us consider a basic alert component. You're probably already familiar with the concept of flash messages in Rails. We're going to build a basic alert component that could be used to render flash messages in a Rails application.

The ViewComponent generator generates the files required for our first component.

bin/rails generate component ViewComponentLibraryExample::Alert --test-framework rspec --skip_namespace

We can write a simple test to assert that the content passed to the component is rendered.

# spec/components/view_component_library_example/alert_component_spec.rb

require "rails_helper"

module ExampleLibary
  RSpec.describe AlertComponent, type: :component do
    it "renders the alert content" do
      alert_text = "This is an alert!"
      component = AlertComponent.new

      render_inline(component) do
        alert_text
      end

      expect(rendered_component).to match(alert_text)
    end
  end
end

To make the test pass, all we need to do is render the component's content in the component template file.

# app/components/view_component_library_example/alert_component.html.erb

<div role="alert">
  <%= content %>
</div>

And there we have it. We've created an ultra minimal ViewComponent library using a Rails 7 engine and RSpec for testing.

In future posts we'll explore the lookbook gem and ViewComponent previews, as well as how to integrate Tailwind CSS — plus deeper dives into aspects such as testing.