Hanami 1.3 > Hanami 2.2 : tips & notes about my journey

As a long time Hanami user (8 years) for my SaaS company, I’m currently working on the migration from 1.3 to 2.2. Thanks to the team for this amazing piece of software :hugs:

With a quite big codebase and many things that work quite well, my first step is to keep most of the things in place, while embrassing some new concepts.

I’m making good progress, and I take notes along the way. I think that they may interest some fellow migrators like me, so here it is, I’ll add stuff in this thread.

3 Likes

Use case #1: compiling scss files

I like using scss files, useful for class nesting, variables, and few other things. Hanami 1 did that out of the box IIRC, so I wanted to be able to do it in Hanami 2, while keeping the official assets pipeline

After a few search in the guide + forum + a bit of ChatGPT, here is what works for me:

  • Add the dedicated plugin for esbuild (this will update package.json)

npm install --save-dev esbuild-sass-plugin sass

  • Load the plugin for asset compilation in app/config/assets.js (or slices/your_slice/config/assets.js if you want to configure one specific slice only)
import * as assets from "hanami-assets";
import { sassPlugin } from "esbuild-sass-plugin";

await assets.run({
  esbuildOptionsFn: (args, esbuildOptions) => {
    // Modify the esbuild options to include the sass plugin
    esbuildOptions.plugins = [
      ...(esbuildOptions.plugins || []), // preserve any existing plugins
      sassPlugin() // add sass plugin
    ];
    return esbuildOptions;
  }
});
  • Import the scss file in the app/assets/js/app.js file (or slices/your_slice/assets/js/app.js if you’re in a specific slice)
import "../css/app.css";
import "../css/your_file.scss";

This part was the most counter-intuitive for me as I was handling some stylesheet files, but I understand that it’s part of the compilation pipeline

With that in place, your main app.css compiled file, that you can load in your layout with <%= stylesheet_tag "app" %> will have the content of both app.css and your_file.scss files :tada:

@timriley would it make sense to enrich the official doc with concrete example like that? just let me know

Use case #2: slice based application, only start one slice at the time

I like monoliths when it comes to code base sharing, but I prefer spawing individual services (app / api / backoffice / pdf renderer… etc) for the run (dev or prod, doesn’t matter)

With Hanami 1.3 I’ve been doing that from the beginning by using the different apps (subfolder of apps directory)

With Hanami 2, obviously the slices are made for that. But I’ve came to the point where my app is almost empty, and I want to only start individual slices.

Here is how I did that (a bit simplified for the sake of example)

# config/routes.rb
# frozen_string_literal: true

module MyApp
  class Routes < Hanami::Routes
    
    if ENV["HANAMI_SLICES"] == "api"
      slice :api, at: "/" 
    elsif ENV["HANAMI_SLICES"] == "backoffice"
      slice :backoffice, at: "/"
    end
    
  end
end
# slices/api/config/routes.rb
# frozen_string_literal: true
require_relative "../middlewares/rack_global_error_handler" # an example of a custom middleware

module API
  class Routes < Hanami::Routes

    # Here is how I inject my custom middleware in the stack
    use API::Middlewares::RackGlobalErrorHandler

    # We are interested in the body of the request
    use :body_parser, :json

    scope 'api' do
      scope 'v1' do
        get  "/status", to: "v1.utils.status", as: :utils_status # pointing to an action in my slice
      end
    end
    
  end
end

Then we need to tweak Procfile a little bit by defining specific targets

#Procfile.dev
api: HANAMI_PORT=2303 bundle exec hanami server
backoffice: HANAMI_PORT=2308 bundle exec hanami server
assets: bundle exec hanami assets watch

And finally we adjust foreman launching to adapt to the slice we want to launch:

# bin/dev
#!/usr/bin/env sh

if ! gem list foreman -i --silent; then
  echo "Installing foreman..."
  gem install foreman
fi

# API slice does not need assets watching
if [ "$HANAMI_SLICES" = "api" ]; then
  FOREMAN_ASSETS=""
else
  FOREMAN_ASSETS=",assets=1"
fi

FOREMAN_M="$HANAMI_SLICES=1$FOREMAN_ASSETS"

exec foreman start -f Procfile.dev "$@" -m $FOREMAN_M

You can then launch the api slice alone :tada:

HANAMI_SLICES=api bundle exec hanami dev

Or the backoffice slice, which will include assets watching, with:

HANAMI_SLICES=backoffice bundle exec hanami dev

Use case #3: load boostrap icons

More generally speaking, this example could help someone trying to integrate external css files from node modules

I’ve been a long time user of bootstrap glyphicons since v3, which can be used as a font and which I find very easy to use. But they abandonned them at some point. Now they are back, in a dedicated package. So I took the time to integrate them properly in my fresh Hanami 2 project

  • First, add the module with npm i bootstrap-icons (this will update package.json)

  • Load the plugin for asset compilation in app/config/app.js (or slices/your_slice/config/app.js if you want to configure one specific slice only)

// Import boostrap icons only
import "bootstrap-icons/font/bootstrap-icons";

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

The css plugin will then be included in the built asset file app.css that should already be included in your layout thanks to the stylesheet_tag helper

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <%= favicon_tag %>
    <%= stylesheet_tag "app" %>
  </head>
[...]

The project link: https://icons.getbootstrap.com/