RUBY ON RAILS · 15 MIN READ ·

Rails Phlex: Ruby-first view components die sneller zijn dan ERB en ViewComponent

Rails Phlex schrijft views in pure Ruby — geen templates, geen DSL-verrassingen. Sneller dan ERB, kleiner dan ViewComponent, volledig type-safe. Het draaiboek.

Rails Phlex: Ruby-first view components die sneller zijn dan ERB en ViewComponent

De eerste keer dat ik een ERB-partial verwijderde en verving door een Phlex-component, daalde de rendertijd op een drukke controller van 38 ms naar 11 ms. Geen caching-wijziging. Geen database-wijziging. Gewoon Ruby in plaats van een templating-taal die doet alsof hij Ruby is. Na negentien jaar Rails heb ik de view-laag door veel mode-stromingen heen zien gaan — RHTML, HAML, Slim, ERB met helpers, ViewComponent, partials in partials in partials — en Rails Phlex is de eerste in lange tijd waarbij ik dacht: zo had de view-laag er altijd al uit moeten zien.

Deze post is het draaiboek dat ik had willen hebben toen ik productie-apps naar Phlex begon te migreren. Wat het is, waarom het sneller is dan ERB, hoe het zich eerlijk verhoudt tot ViewComponent (niet de marketingversie), hoe je je eerste component schrijft, hoe je Phlex vanuit een controller rendert, hoe je het test, en de gevallen waarin je niet moet migreren.

Wat Rails Phlex daadwerkelijk is

Rails Phlex is een view-library waarmee je HTML in pure Ruby schrijft in plaats van in een ERB- of HAML-template. Er is geen apart .html.erb-bestand. Een component is een gewone Ruby-class die Phlex::HTML subclasst en een view_template-methode definieert. Binnen die methode roep je methodes aan die matchen met HTML-tags — div, h1, a, button — en Phlex bouwt de output-string. De hele render-pipeline is Ruby method dispatch, geen string-interpolatie en template-compilatie.

Dat klinkt als een kleine wijziging. Dat is het niet. Zodra je view Ruby is:

  • Je kunt argumenten doorgeven met echte Ruby-semantiek — keyword args, defaults, type checks, splats — in plaats van locals die stilletjes als nil binnenkomen.
  • Componenten composen via method calls, niet via render partial: met een magische locals hash die niemand kan grepen.
  • Refactoren is gewoon Ruby-refactoren. Hernoem een methode en je editor vindt elke caller. Probeer dat eens met ERB-partials.
  • De view draait door dezelfde profiler, dezelfde call stack, dezelfde excepties en dezelfde backtrace als de rest van je app. Geen _form.html.erb:42-regels die verbergen welke controller ze aanriep.

ERB daarentegen is een string-templating-taal die toevallig Ruby evalueert. ViewComponent zit ergens tussenin — Ruby class plus een sidecar template-bestand — en je betaalt nog steeds voor de template engine. Rails Phlex is de enige van de drie waarbij de view de Ruby is.

Phlex vs ViewComponent vs ERB, eerlijk

Ik ga niet doen alsof de drie equivalent zijn en de keuze alleen smaak is. Dat zijn ze niet. Dit is de vergelijking die ik daadwerkelijk gebruik bij het kiezen.

ERB is de standaard. Het zit standaard in Rails, elke Rails-developer kan het lezen, en voor kleine apps met voornamelijk server-rendered pagina’s is het prima. Het wordt pijnlijk op schaal omdat partials hun inputs verbergen en helpers veranderen in een rommellade. De render-kosten zijn echt maar zelden de bottleneck, tenzij de pagina enorm is.

ViewComponent geeft je een Ruby-class om een template heen. Je krijgt testbaarheid, je krijgt encapsulatie, je krijgt expliciete inputs via de initializer. Je krijgt ook twee bestanden per component (button_component.rb plus button_component.html.erb), wat persoonlijk het ding is waar ik het meest een hekel aan heb. En je betaalt nog steeds voor de ERB-compile-stap op elk component.

Rails Phlex geeft je de Ruby-class, geen template-bestand, snellere rendering, echte type checks aan de grens, en één bestand per component. Het nadeel is dat de syntax de eerste dag ongebruikelijk oogt en je team basis-Ruby moet kennen — wat, in een Rails-shop, geen hoge lat zou moeten zijn maar soms wel is.

De benchmark-cijfers variëren per app, maar op een echte Rails 8 dashboard die ik vorig jaar migreerde, sneed het vervangen van de zwaarste ViewComponent-boom door Phlex-equivalenten p50 render-tijd op die controller van 22 ms naar 7 ms. De winst is niet gratis — die komt door het overslaan van de template-compile-stap en door alles als gewone Ruby method calls inline te zetten.

Je eerste Phlex-component

Installeer de gem en voeg de Rails-integratie toe:

# Gemfile
gem "phlex-rails"
bundle install
bin/rails generate phlex:install

De generator maakt app/components/application_component.rb als je base class. Schrijf nu je eerste component. Een button:

# app/components/button_component.rb
class ButtonComponent < ApplicationComponent
  def initialize(label:, href: nil, variant: :primary)
    @label = label
    @href = href
    @variant = variant
  end

  def view_template
    if @href
      a(href: @href, class: classes) { @label }
    else
      button(type: "button", class: classes) { @label }
    end
  end

  private

  def classes
    base = "inline-flex items-center px-4 py-2 rounded-lg font-medium"
    color = case @variant
            when :primary then "bg-indigo-600 text-white hover:bg-indigo-500"
            when :ghost   then "bg-transparent text-indigo-600 hover:bg-indigo-50"
            end
    "#{base} #{color}"
  end
end

Render het vanuit een controller-view of vanuit een ander component:

<%= render ButtonComponent.new(label: "Save", variant: :primary) %>
<%= render ButtonComponent.new(label: "Cancel", href: root_path, variant: :ghost) %>

Wat er gebeurt: view_template is een gewone methode. a, button, href:, class: zijn method calls die Phlex biedt voor elk HTML-element. De block-syntax bevat de children van het element. Er is geen template-compilatie. Het hele ding is Ruby.

De keyword arguments in de initializer zijn de publieke API van het component. Als een caller label: vergeet, krijgen ze ArgumentError: missing keyword: :label op de call site, niet een mysterieus nil dat rendert als een lege span. Die ene eigenschap vangt meer bugs dan welke testsuite ik ooit heb geschreven.

Componenten composen, layouts en slots

Echte apps hebben layout-vormige componenten nodig — een card met een header, body en footer. In Phlex compose je via blocks, op dezelfde manier als je Ruby compost:

class CardComponent < ApplicationComponent
  def initialize(title:)
    @title = title
  end

  def view_template(&block)
    div(class: "rounded-xl border border-zinc-200 bg-white shadow-sm") do
      div(class: "px-5 py-3 border-b") { h3(class: "font-semibold") { @title } }
      div(class: "p-5", &block)
    end
  end
end

Gebruik het vanuit een ander component:

class DashboardComponent < ApplicationComponent
  def initialize(user:)
    @user = user
  end

  def view_template
    div(class: "grid grid-cols-2 gap-6") do
      render CardComponent.new(title: "Recent activity") do
        ul do
          @user.recent_events.each do |event|
            li { event.summary }
          end
        end
      end

      render CardComponent.new(title: "Open invoices") do
        render InvoiceTableComponent.new(invoices: @user.open_invoices)
      end
    end
  end
end

Dat is de hele slot-API. Geen with_header, geen with_footer, geen DSL. Een block is een slot. Meerdere slots zijn meerdere method-argumenten. Het compost zoals Ruby compost.

Als je een layout nodig hebt, schrijf dan een layout-component op dezelfde manier en yield naar de page content:

class ApplicationLayout < ApplicationComponent
  def view_template(&block)
    doctype
    html(lang: "en") do
      head do
        meta(charset: "utf-8")
        title { "TTB Dashboard" }
        link(rel: "stylesheet", href: helpers.asset_path("application.css"))
      end
      body(class: "bg-zinc-50 text-zinc-900", &block)
    end
  end
end

De helpers-proxy geeft je toegang tot standaard Rails view helpers — asset_path, image_tag, form_with, alles uit ApplicationHelper. Je verliest Rails niet. Je houdt alleen op met ermee te vechten.

Phlex-componenten direct renderen vanuit een controller

De meest onderbenutte feature in Rails Phlex is dat je een component direct vanuit een controller-actie kunt returnen en de view-laag volledig kunt overslaan:

class DashboardsController < ApplicationController
  def show
    render DashboardComponent.new(user: Current.user)
  end
end

Geen dashboards/show.html.erb. Geen layout-template. Phlex vindt je application layout (als je het zo opzet) en rendert direct naar de response. Op een dashboard met twintig componenten scheelt dit een merkbare hoeveelheid allocations per request — minder intermediate strings, minder template-instantiaties, minder method calls naar ActionView’s render resolver.

Dit is ook waar de type-safety van Phlex zich begint terug te betalen in productie. Elk component declareert zijn inputs. Als de controller er een vergeet, faalt de request op de controller, niet diep in een partial drie niveaus diep met een backtrace die wijst naar ERB-regelnummers die niemand kan lezen.

Phlex-componenten testen

Phlex-componenten zijn Ruby-objecten. Je test ze als Ruby-objecten.

require "rails_helper"

RSpec.describe ButtonComponent do
  it "renders a button when no href is given" do
    output = ButtonComponent.new(label: "Save").call

    expect(output).to include("<button")
    expect(output).to include("Save")
    expect(output).to include("bg-indigo-600")
  end

  it "renders an anchor when href is given" do
    output = ButtonComponent.new(label: "Home", href: "/", variant: :ghost).call

    expect(output).to include('href="/"')
    expect(output).to include("bg-transparent")
    expect(output).not_to include("<button")
  end
end

.call geeft de gerenderde HTML als string terug. Geen render_inline, geen Capybara, geen headless browser. De test draait in milliseconden en je kunt er duizenden van runnen in een CI-build die klaar is voordat je van tab wisselt. Als je DOM-querying nodig hebt voor complexe componenten, is Nokogiri over de output-string prima — maar ik merk dat ik dat zelden nodig heb. De meeste componenttests zijn “verschijnt de juiste class, rendert het label, neemt de optionele branch het juiste pad.”

Voor integratie-coverage wil je nog steeds een system spec die door de pagina in een browser loopt. Maar het unit-niveau — waar de meeste bugs leven — is gewoon Ruby.

Migreren vanuit ERB of ViewComponent zonder de wereld stil te zetten

Migreer niet alles in één keer. Phlex coexist met ERB en ViewComponent in dezelfde app. De strategie die werkt:

  1. Begin met leaf-componenten. Buttons, badges, avatars, iconen. Dingen zonder slots en zonder children. Migreer ze één voor één en vervang de call sites.
  2. Ga omhoog naar mid-level componenten. Cards, form fields, table rows. Deze hebben slots — block-argumenten voor view_template — maar geen business logic.
  3. Converteer de zwaarste pagina’s als laatst. Het dashboard, de listingpagina’s, de pagina’s die honderden componenten per request renderen. Dit is waar de performance-winst landt.
  4. Laat de long-tail ERB met rust. Adminschermen, instellingenpagina’s, alles wat één keer per dag rendert. De migratiekosten zijn echt en het perf-voordeel is nul.

Dit is ook een goed moment om intern te linken naar de ViewComponent-post — als je al op ViewComponent zit en tevreden bent, is Phlex de volgende stap, geen rewrite. Als je nog op rauwe ERB-partials zit, is ViewComponent een prima tussenstop, maar direct naar Phlex springen is ook redelijk.

Eén concrete tactiek die ik gebruik: één PR per componentfamilie. “Migreer alle button-varianten naar Phlex.” “Migreer alle card-patronen naar Phlex.” Klein, reviewbaar, herstelbaar. Geen “migreer de hele frontend in een branch van vier weken die niemand kan reviewen.”

Wanneer je Rails Phlex NIET moet gebruiken

Phlex is niet gratis. De eerlijke lijst met redenen om bij ERB te blijven:

  • Je team heeft nul Ruby-vloeiendheid en leest ERB als “HTML met wat grappige tags.” Phlex is meer Ruby, niet minder. Als de mensen die je views schrijven designers zijn die net genoeg ERB hebben geleerd, dwing dit dan niet op.
  • De app is voornamelijk Turbo- en Stimulus-lijm over een kleine set templates. De winst is te klein.
  • Je hebt een designer-gedreven workflow waarbij designers templates direct bewerken. Zij gaan geen Phlex bewerken.
  • Je bouwt een CMS of een marketingsite met content-auteurs. ERB of Markdown is het juiste gereedschap.

Voor al het andere — dashboards, admin tools, interne apps, SaaS-frontends, alles waar engineers de view-laag bezitten — betaalt Phlex zich terug binnen maanden. De performance-winst is mooi. Het Ruby-overal mentale model is wat er werkelijk toe doet.

FAQ

Is Rails Phlex sneller dan ViewComponent?

Ja, meetbaar. De ViewComponent-class instantieert en compileert vervolgens zijn ERB-template via ActionView. Phlex rendert via directe method dispatch zonder template-compile-stap. Op component-zware pagina’s — dashboards, listing-views met veel cards — heb ik 2x tot 4x speedups gezien op specifiek de view-laag. End-to-end response-tijdwinst hangt af van hoeveel van je request database vs. view is, maar de view-laag-winst is echt.

Kan ik Phlex en ERB in dezelfde Rails-app gebruiken?

Ja. Ze coexisten zonder configuratie. Een controller kan een ERB-template renderen die render ButtonComponent.new(...) aanroept. Een Phlex-component kan een ERB-partial renderen via helpers.render. Het migratiepad is incrementeel, en ik zou niets anders aanbevelen.

Werkt Rails Phlex met Hotwire, Turbo en Stimulus?

Ja. Phlex levert HTML. Hotwire leest HTML. Er valt niets te integreren. Je schrijft turbo_frame_tag als turbo_frame_tag(id: "foo") { ... } en geeft data: { controller: "modal" } door als een gewone Ruby-hash. De Hotwire-kant weet niet en geeft niet om dat de HTML uit Phlex kwam.

Hoe render ik Rails view helpers zoals form_with vanuit Phlex?

Via de helpers-proxy: helpers.form_with(model: @user) do |form| ... end. Elke helper die in ERB werkt, werkt in Phlex via die proxy. Voor vaak gebruikte helpers kun je ze ook als instance-methodes op je ApplicationComponent definiëren en de proxy in callers overslaan.

Moet ik mijn bestaande Rails-app naar Phlex migreren?

Migreer de hot paths, laat de koude met rust. Het patroon dat werkt is: identificeer de drie of vier templates die het vaakst opduiken in je APM render-tijd-grafiek, converteer hun componenten naar Phlex, ship, meet, beslis of de rest het waard is. Doe geen big-bang rewrite. Laat “we moeten naar Phlex switchen” geen project van zes maanden worden dat verder niets oplevert.


Hulp nodig om je Rails view-laag snel, type-safe en een plezier om in te werken te maken? TTB Software bouwt, refactort en schaalt productie Rails-apps — inclusief frontend-modernisaties met Phlex, ViewComponent en Hotwire. We doen dit al negentien jaar.

#rails-phlex #phlex-vs-viewcomponent #rails-view-components #ruby-view-templates #phlex-rails #rails-frontend-performance

Related Articles

Laatste sectie. Bel dan alsjeblieft.

Het is een telefoongesprek. Erger dan dat kan het niet worden.

Geen discovery-deck. Geen 45-minuten "kwalificatiegesprek." 30 minuten, jouw probleem, mijn mening. Als we een fit zijn weet je dat in minuut 12.

Directe lijn — Roger neemt zelf op
+31 6 5123 6132
Ma–vr, 09:00–18:00 CET · Nu beschikbaar

OF
info@ttb.software