After a year of work, Hanami 2.1 is here! This release introduces our view layer and front-end assets support, and brings Hanami a big step closer to our full stack vision.

It all starts with hanami dev

Working on your app’s front-end now starts with a single new command: hanami dev.

Running hanami dev starts the familiar Hanami web server alongside our new front-end assets watcher and compiler.

From there, you’re ready to open http://localhost:2300 and take in our gorgeous new welcome screen, in both light and dark mode.

Welcome screen in light mode

Welcome screen in dark mode

Welcome (back!) to Hanami. We’ve been building something special for you!

You’ll love our view on views

To build your new front-end, you can start with views. Like actions, Hanami views are standalone, callable objects, bringing a new level of clarity and reusability to the view layer.

# app/views/posts/index.rb
module MyApp
  module Views
    module Posts
      class Index < MyApp::View
        include Deps["posts_repo"]

        expose :posts do |page:|
          posts_repo.listing(page:)
        end
      end
    end
  end
end

View exposures explicitly prepare the values we pass to templates, and they work seamlessly with Hanami’s Deps mixin, allowing your view to cleanly access other parts of your app as required.

<h1>Posts</h1>

<%= posts.each do |post| %>
  <h2><%= post.title %></h2>
<% end %>

Hanami 2.1 delivers a brand new ERB engine, providing you a familiar template environment while also allowing for natural Ruby in view-focused methods, with a simple yield capturing nested template content, with no special handling required.

Your views have access to a library of familiar helpers:

<%= form_for :post, routes.path(:create_post) do |f| %>
  <%= f.label "title" %>
  <%= f.text_field "title" %>
<% end %>

You can write your own helpers, too:

module MyApp
  module Views
    module Helpers
      def warning_box
        # tag is Hanami's built-in HTML builder helper
        tag.div(class: "warning") do
          yield # captures nested content in the template; so natural!
        end
      end
    end
  end
end

On their own, helpers can become a bit of a mishmash, so Hanami provides view parts that encapsulate your view logic right alongside the value it relates to. They can even render their own partials! This keeps your templates simple and lets you use ordinary OO techniques to refactor and independently test your view code.

module MyApp
  module Views
    module Parts
      # Every post exposed from a view comes with these methods
      class Post < MyApp::Views::Part
        def title_link
          helpers.tag.a(title, href: context.routes.path(:post, id:))
        end

        def feature_box
          render("feature_box", title: title, text: teaser_text)
        end
      end
    end
  end
end
<ul>
  <% posts.each do |post| %>
    <li>
      <%= post.title_link %>
      <%= post.feature_box %>
    </li>
  <% end>
</ul>

Rendering views from actions is a breeze. From the get go, actions render their matching view automatically, no extra work required. Once your views need certain input, you can also make that wiring clear:

def handle(request, response)
  response.render(view, id: request.params[:id])
end

Say hello to app/assets/

With your views ready to go, it’s time to explore assets.

Your assets live in app/assets/. JavaScript files live under js/, with app files serving as your entry points:

import "../css/app.css";

console.log("Hello from app.ts");

As you can see, TypeScript works out of the box. Just run npm install typescript.

Your assets come fast and flexible

Hanami assets are powered by esbuild, giving you lightning quick build times.

Modern front-end affordances are ready for you out of the box, no configuration required, with our standard assets config a picture of simplicity:

import * as assets from "hanami-assets";

await assets.run();

If you need more, you can have more! Assets config can be gracefully extended to provide advanced esbuild options or take advantage of its many plugins. A fully integrated PostCSS, for example, is just a few lines away:

import * as assets from "hanami-assets";
import postcss from "esbuild-postcss";

await assets.run({
  esbuildOptionsFn: (args, esbuildOptions) => {
    const plugins = [...esbuildOptions.plugins, postcss()];

    return {
      ...esbuildOptions,
      plugins,
    };
  },
});

Slice it your way

As first-class Hanami features, views and assets work great inside slices as well as your app/. Every slice can have its own views/, templates/ and assets/ directories, for your views, parts, helpers, assets and more.

With Hanami we want to help you draw the right boundaries to support your app’s domain, and views are no different.

The view ahead looks bright

With Hanami 2.1, we are continuing to deliver our vision for a fresh take for Ruby apps. We’d love for you dive in and give our views and assets a try!

Check out our updated getting started guide for your first steps in building a full stack Hanami app. You’re only a few commands away:

$ gem install hanami
$ hanami new my_app
$ cd my_app
$ bundle exec hanami dev
$ open http://localhost:2300

With views and assets done, our next step is the persistence layer. You can look forward to hearing more about this later this year.

Thank you from Tim Riley and Luca Guidi.

Thank you also to these wonderful people for contributing to Hanami 2.1!

🌸