Need help with komponent?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

komposable
428 Stars 25 Forks MIT License 286 Commits 29 Opened issues

Description

An opinionated way of organizing front-end code in Ruby on Rails, based on components

Services available

!
?

Need anything else?

Contributors list

Komponent

Tests GitHub release Maintainability Coveralls github

Komponent implements an opinionated way of organizing front-end code in Ruby on Rails, based on components.

Each component has its own folder, containing a Ruby module, a partial, a stylesheet and a JavaScript file.

Komponent relies heavily on webpacker to manage dependencies and generate the production JS and CSS files.

This README examples are written in Slim, but Komponent is compatible with:

  • your preferred templating language (Slim, Haml, erb)
  • your stylesheet language of choice (Sass, SCSS, CSS, PostCSS)

This gem has been inspired by our Rails development practices at Ouvrages and Etamin Studio, and the (excellent) Modern Front-end in Rails article from Evil Martians.

Table of Contents

Compatibility

  • Ruby 2.5+
  • Rails 5.0+
  • Webpacker 3.0.0+

Getting started

# Gemfile

gem 'komponent'

Run the following command to set up your project instantly:

rails generate komponent:install

This command will:

  • check that the dependencies (currently, webpacker) are installed
  • rename the
    app/javascript
    folder to
    frontend
    and modify webpacker config accordingly
  • create the
    frontend/components
    folder where you will put your component
  • create the
    frontend/components/index.js
    file that will list your components and
    import
    it in
    frontend/packs/application.js

Usage

Generate a new component with the

component
generator:
rails generate component button

Then, render it in your views with the

component
helper (or its alias
c
).
/ app/views/pages/home.html.slim

= component 'button' = c 'button'

Or, directly from your controllers:

# app/controllers/pages_controller.rb

def home render html: helpers.component('home') end

Make sure to include javascript pack tag and stylesheet pack tag in your application layout file, for instance:

/ app/views/layouts/application.html.slim

doctype html html head = stylesheet_pack_tag 'application'

body = yield = javascript_pack_tag 'application'

Check Webpacker documentation for further information.

Passing variables

You can pass

locals
to the helper. They are accessible within the component partial, as instance variables. Additionally, the entire
locals
hash is made available through a
properties
helper method.
/ app/views/pages/home.html.slim

= component 'button', text: 'My button'

/ frontend/components/button/_button.html.slim

.button = @text

Passing options

Component caching

Komponent relies on Rails Low-level caching.

You can cache the component by passing the

cached: true
option. The cache will expire when the locals, options or block change. If you want better control of the cache expiration, you can provide a custom
cache_key
. When the
cache_key
changes, the cache will be cleared.
/ app/views/pages/home.html.slim

/ Cache the component based on its locals = component "button", { text: 'Click here' }, cached: true

/ or cache the component with a specific key, such as the last update of a model = component "button", { text: 'Click here' }, cached: true, cache_key: @product.updated_at

Passing a block

The component also accepts a

block
. To render the block, just use the standard
yield
.
/ app/views/pages/home.html.slim

= component 'button' span= 'My button'

/ frontend/components/button/_button.html.slim

.button = yield

You can check if the component has been called with a block using the

block_given_to_component?
helper from within the component.

Properties

Each component comes with a Ruby

module
. You can use it to set properties:
# frontend/components/button/button_component.rb

module ButtonComponent extend ComponentHelper

property :href, required: true property :text, default: 'My button' end

/ frontend/components/button/_button.html.slim

a.button([email protected]) = @text

Helpers

If your partial becomes too complex and you want to extract logic from it, you may want to define custom helpers in the

ButtonComponent
module:
# frontend/components/button/button_component.rb

module ButtonComponent extend ComponentHelper

property :href, required: true property :text, default: 'My button'

def external_link? @href.starts_with? 'http' end end

/ frontend/components/button/_button.html.slim

a.button([email protected]) = @text = ' (external link)' if external_link?

/ app/views/pages/home.html.slim

= component "button", text: "My button", href: "http://github.com"

Component partials

You can also choose to split your component into partials. In this case, we can use the default

render
helper to render a partial, stored inside the component directory.
/ frontend/components/button/_button.html.slim

a.button([email protected]) = @text

  • if external_link? = render 'suffix', text: 'external link'
/ frontend/components/button/_suffix.html.slim

= " (#{text})"

Namespacing components

To organize different types of components, you can group them in namespaces when you use the generator:

rails generate component admin/header

This will create the component in an

admin
folder, and name its Ruby module
AdminHeaderComponent
.

Stimulus integration

Komponent supports Stimulus >= 1.0.

You can pass

--stimulus
to both generators to use Stimulus in your components.
rails generate komponent:install --stimulus

This will

yarn add stimulus
and create a
stimulus_application.js
in the
frontend
folder.
rails generate component button --stimulus

This will create a component with an additional

button_controller.js
file, and define a
data-controller
in the generated view.

Internationalization

In case your component will contain text strings you want to localize, you can pass the

--locale
option to generate localization files in your component directory.
rails generate component button --locale

This will create a

yml
file for each locale (using
I18n.available_locales
). In your component, the
t
helper will use the same "lazy" lookup as Rails.
/ frontend/components/button/_button.html.slim

= a.button([email protected]) = @text = render('suffix', text: t(".external_link")) if external_link?

# frontend/components/button/button.en.yml

en: button_component: external_link: external link

# frontend/components/button/button.fr.yml

fr: button_component: external_link: lien externe

Available locales configuration

You can whitelist the locales you use by setting this into an initializer, as explained in the "official guide":

ruby
I18n.available_locales = [:en, :fr]

If you have the

rails-i18n
gem in your
Gemfile
, you should whitelist locales to prevent creating a lot of locale files when you generate a new component.

Styleguide

Komponent includes a basic styleguide engine that you can use in your project to document your components.

Komponent styleguide UI

To set it up, you can use the generator:

rails generate komponent:styleguide

This command will:

  • copy the styleguide components (
    komponent/container
    ,
    komponent/footer
    ,
    komponent/header
    and
    komponent/sidebar
    ) to your components folder, so you can customize them
  • add a new
    komponent.js
    pack to your packs folder
  • mount the engine in your routes

Then, for each component, you can describe it and render examples for each state in the

_example.html.slim
file from the component folder. The engine will then render it on the component page.

If you have existing components, you can generate all their example files at once with:

rails generate komponent:examples

Finally, visit

http://localhost:3000/styleguide
to access your styleguide.

Configuration

Change default root path

You can change the default root path (

frontend
) to another path where Komponent should be installed and components generated. You need to change
komponent.root
in an initializer.
Rails.application.config.komponent.root = Rails.root.join('app/frontend')

Default options for the generators

You can configure the generators in an initializer or in

application.rb
, so you don't have to add
--locale
and/or
--stimulus
flags every time you generate a fresh component.
config.generators do |g|
  g.komponent stimulus: true, locale: true # both are false by default
end

Change default stylesheet engine

You can configure the stylesheet engine used for generate stylesheet file, allowed values are

:css
,
:scss
,
:sass
.
Rails.application.config.komponent.stylesheet_engine = :css # default value is :css

Force default templating engine

If for some reason your preferred templating engine is not detected by Komponent, you can force it by manually defining it in your config:

Rails.application.config.generators.template_engine = :haml

Additional paths

You may want to use components in a gem, or a Rails engine, and expose them to the main app. In order to do that, you just have to configure the paths where Komponent will look for components.

From a gem:

module MyGem
  class Railtie < Rails::Railtie
    config.after_initialize do |app|
      app.config.komponent.component_paths.append(MyGem.root.join("frontend/components"))
    end

initializer "my_gem.action_dispatch" do |app|
  ActiveSupport.on_load :action_controller do
    ActionController::Base.prepend_view_path MyGem.root.join("frontend")
  end
end

initializer 'my_gem.autoload', before: :set_autoload_paths do |app|
  app.config.autoload_paths &lt;&lt; MyGem.root.join("frontend")
end

end

private

def self.root Pathname.new(File.dirname(dir)) end end

or from an engine:

module MyEngine
  class Engine < Rails::Engine
    isolate_namespace MyEngine

config.after_initialize do |app|
  app.config.komponent.component_paths.append(MyEngine::Engine.root.join("frontend/components"))
end

initializer 'my_engine.action_dispatch' do |app|
  ActiveSupport.on_load :action_controller do
    ActionController::Base.prepend_view_path MyEngine::Engine.root.join("frontend")
  end
end

initializer 'my_engine.autoload', before: :set_autoload_paths do |app|
  app.config.autoload_paths &lt;&lt; MyEngine::Engine.root.join('frontend')
end

end end

Make sure you add

komponent
to the runtime dependencies in your
gemspec
.

In order to compile packs from engine, and to use

javascript_pack_tag 'engine'
, you need to:
  • Create a pack file in main app
// frontend/packs/engine.js

import 'packs/engine';

  • Append engine frontend folder to
    resolved_paths
    in
    config/webpacker.yml
    from your main app
resolved_paths:
  - engine/frontend

Running tests

Run all Cucumber features and unit tests with

bundle exec appraisal rake test

Run the full test matrix with

bundle exec appraisal rake test

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/komposable/komponent.

Releasing a new version

  • Update the CHANGELOG (add a title and a date for the new version)
  • Update the version number in
    lib/komponent/version
  • Install the
    gem-release
    gem if you haven't already
  • Run
    gem release --tag --push
  • Create or update the release on Github with the same version number and copy-paste the description from the CHANGELOG

Please note:

If you're releasing a patch version (eg. from 2.0.1 to 2.0.2) you can run

gem bump patch --release --tag --push --sign
so you don't have to manually change the version number.

If you want to release a specific version (eg. beta, RC...), you can run

gem bump 3.0.0.beta1 --release --tag --push --sign

License

The gem is available as open source under the terms of the MIT License.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.