The Evolution of Web Component Modules in Raku: A Journey of Diverse Approaches

Over the past several years, the Raku community has explored a variety of approaches to building web interfaces. Each module represents a distinct set of design choices and trade-offs, reflecting different philosophies and priorities. In this post, we’ll review key milestones in the evolution of web component modules in Raku and provide code examples that illustrate how each solution works in practice.
2017 – p6-react
p6-react
was one of the earliest experiments in bringing a component-based approach to Raku web development. Inspired by modern front-end frameworks, p6-react
enables developers to write server-side components with a declarative syntax. The following example demonstrates how to create reusable components using the p6-react
style:
use Component;
use Slang;
component Item {
has Str $.data;
method render {
{{$.data}}
}
}
component UlList {
has Str @.items;
method render {
{{
do for @.items -> $item {
-
}
}}
}
}
say UlList.new(:items).render.render
In this example, the Item component renders a list item (
) using its data, while UlList
composes a set of Item components into an unordered list (). This approach leverages a clear, nested component structure that promotes code reuse and declarative UI construction.
2018 – MemoizedDOM
MemoizedDOM
emerged in 2018 with a fresh perspective by focusing on a functional approach to HTML generation. Borrowing ideas from memoization techniques, MemoizedDOM
emphasizes the declarative construction of web interfaces. It leverages tools like rakudo.js
and integrates optionally with platforms such as webperl6
and 6pad
.
The example below demonstrates a more advanced application—a dynamic todo list—with interactive form handling and live updates:
# App.rakumod
use MemoizedDOM;
use Todo;
class App does Tag {
has @.todos;
has Str $.new-title = "";
method render {
form(
:event{
:submit{
.preventDefault;
@!todos.push: {
:title($!new-title),
:!done
};
$!new-title = "";
self.call-render
}
},
ul({ do for @!todos -> (Str :$title!, Bool :$done! is rw) {
Todo(:$title, :$done, :toggle{ $done = !$done; self.call-render })
}}),
input(
:type,
:event{
:keyup{ $!new-title = . }
},
:value{ $!new-title }
),
input(
:type,
:value,
)
)
}
}
# Todo.rakumod
use MemoizedDOM;
class Todo does Tag {
has Str $.title;
has Bool $.done is rw;
has &.toggle is required;
method render {
li(
:style{
$!done
?? { :textDecoration, :opacity<0.3> }
!! { :textDecoration , :opacity<1> }
},
:event{
:click( &!toggle )
},
input(
:type,
:checked{ $!done },
),
$!title
)
}
}
# todo.raku
use App;
EVAL :lang, 'HTMLElement.prototype.defined = function() { return true }';
EVAL :lang, 'document.defined = function() { return true }';
my \document = EVAL :lang, 'return document';
my $app = App(:todos[
{:title, :!done},
{:title, :done },
{:title, :!done},
]);
$app.mount-on: document.getElementById: 'todoapp';
This example shows how MemoizedDOM
can be used to build an interactive todo list. It demonstrates declarative UI rendering, event handling, and live updates through memoization. The functional style emphasizes a clear separation between data and view, though it may require a shift in mindset compared to traditional object-oriented approaches.
2023 – HTML::Component
HTML::Component
took a different turn by embracing an object-oriented design to build HTML structures. Instead of relying solely on functions, this module uses methods and role composition to generate HTML elements. This approach harnesses Raku’s strengths in object composition and method chaining, offering a distinct set of trade-offs.
The following example demonstrates a complete todo list application using HTML::Component
. It is divided across multiple modules to showcase component loading, dynamic updates, and integration with HTMX for seamless interactivity.
# examples/todo/Todo.rakumod
use HTML::Component;
use HTML::Component::Endpoint;
unit class Todo does HTML::Component;
my @todos;
has UInt $.id = ++$;
has Str() $.description is required;
has Bool() $.done = False;
submethod TWEAK(|) {
@todos[$!id - 1] := self;
}
method LOAD(UInt() :$id) {
@todos[$id - 1]
}
multi method new($description) { self.new: :$description, |%_ }
method RENDER($_) {
.li:
:htmx-endpoint(self.toggle),
:hx-swap,
:class,
{
.input-checkbox:
:checked($!done),
;
if $!done {
.del: $!description
} else {
.add-child: $!description
}
}
;
}
method toggle is endpoint{ :return-component } {
$!done .= not
}
# examples/todo/TodoList.rakumod
use HTML::Component::Endpoint;
use HTML::Component;
use HTML::Component::Boilerplate;
use HTML::Component::Traits;
use Todo;
unit class TodoList does HTML::Component;
method new(|) { $ //= self.bless }
method LOAD(|) { self.new }
has UInt $.id = ++$;
has Todo @.todos;
method RENDER($_) {
.ol: {
.add-children: @!todos;
}
.form: self.new-todo;
}
method new-todo(
Str :$description! is no-label, #= What should be done?
) is endpoint{ :verb, :redirect> } {
@!todos.push: Todo.new: :$description;
}
# examples/todo/App.rakumod
use TodoList;
use HTML::Component;
use HTML::Component::Boilerplate;
unit class App does HTML::Component;
method RENDER($) {
boilerplate
:title("My TODO list"),
:body{
.script: :src;
.add-child: TodoList.new;
}
;
}
# examples/todo/cro-todo.raku
use Cro::HTTP::Log::File;
use Cro::HTTP::Server;
use Cro::HTTP::Router;
use HTML::Component::CroRouter;
use Cro::HTTP::Log::File;
use lib "examples";
use App;
my $route = route {
root-component App.new
}
my $app = Cro::HTTP::Server.new(
host => '127.0.0.1',
port => 10000,
application => $route,
after => [
Cro::HTTP::Log::File.new(logs => $*OUT, errors => $*ERR)
],
);
$app.start;
say "Listening at http://127.0.0.1:10000";
react whenever signal(SIGINT) {
$app.stop;
exit;
}
This HTML::Component
example is more elaborate, demonstrating how to split a todo list application into multiple components. It showcases dynamic component registration and loading, interactive endpoints using HTMX, and the integration of boilerplate templates to create a robust web application.
2024 – Cromponent
Cromponent
offers a flexible way to create web components using Cro
templates
. It stands out by supporting three complementary modes of integration:
• Template-Only Usage: Use a Cromponent
as a “fancy substitute for cro-template sub/macro.”
• Data Embedding: Pass a Cromponent
as a value to a template so that printing it yields its rendered HTML.
• Auto-Generated Routes: Automatically create Cro
endpoints
for your components, enabling dynamic, REST
-like interactions.
Below is the simplest example taken directly from Cromponent
’s README, demonstrating the template-only approach:
use Cromponent;
class AComponent does Cromponent {
has $.data;
method RENDER {
Q:to/END/
END
}
}
sub EXPORT { AComponent.^exports }
In this example, AComponent
is defined as a Cromponent
that renders an tag with its data. This concise setup highlights Cromponent
’s elegance: by simply defining a RENDER
method using a Cro
template
, you can quickly build components that are reusable within your Cro
applications. Moreover, Cromponent
’s versatility allows its use in embedding within templates or even auto-generating HTTP routes for dynamic web interfaces.
2025 – Air
Air represents a fresh direction in the evolution of Raku’s web component ecosystem. Air
merges automatic routing and functional HTML generation, aiming for a clear, declarative approach to building web pages. Below is an example demonstrating how you can use Air
to define a full web page that dynamically renders content:
#!/usr/bin/env raku
use Air::Functional :BASE;
use Air::Base;
my &index = &page.assuming( #:REFRESH(1),
title => 'hÅrc',
description => 'HTMX, Air, Red, Cro',
footer => footer p ['Aloft on ', b safe 'Åir'],
);
my &planets = &table.assuming(
:thead[["Planet", "Diameter (km)",
"Distance to Sun (AU)", "Orbit (days)"],],
:tbody[["Mercury", "4,880", "0.39", "88"],
["Venus" , "12,104", "0.72", "225"],
["Earth" , "12,742", "1.00", "365"],
["Mars" , "6,779", "1.52", "687"],],
:tfoot[["Average", "9,126", "0.91", "341"],],
);
sub SITE is export {
site #:bold-color,
index
main
div [
h3 'Planetary Table';
planets;
]
}
In this Air
example, the code demonstrates:
-
Declarative Page Composition: Using &page.assuming
to create a page with a title, description, and footer.
-
Data-Driven Tables: Constructing an HTML table via &table.assuming
with structured data for headers, body, and footers.
-
Functional Composition: The SITE subroutine assembles the complete page layout by combining various components, resulting in a dynamic page rendered in a functional style.
Air
’s approach illustrates the potential for combining HTMX-enhanced interactivity with a concise, functional programming model, adding another valuable tool to the diverse landscape of Raku
web component modules.
Other Noteworthy Projects
The evolution of web component modules in Raku is further enriched by additional projects that explore diverse integrations and methodologies:
-
Cro-WebSocket-WebComponents-test
: Investigates the integration of Cro
with WebSockets to enable live, reactive updates within web components.
-
Proact
: Introduces reactive programming paradigms into web development, offering a creative approach to managing dynamic interfaces.
Each of these projects reflects different priorities—whether it’s seamless interactivity, modularity, or declarative design—allowing developers to select the tool that best fits their specific needs and preferences.
Conclusion
The history of web component modules in Raku is a testament to the community’s commitment to experimentation and innovation. From p6-react’s declarative, component-based structure to MemoizedDOM
’s functional approach, from HTML::Component’s object-oriented, HTMX-powered applications to Cromponent
’s versatile Cro
template integration, and finally to Air
’s functional, data-driven page composition—each module brings its own set of trade-offs and benefits. Rather than comparing these solutions as better or worse, it is more productive to view them as complementary approaches addressing different design goals and use cases.
I probably have not found all modules related to web components in Raku. If you remember any other module that should be cited here, please let me know—I’d love to hear your suggestions and will create a new post to cover them!
As the ecosystem continues to evolve, Raku developers have a rich set of tools at their disposal, each contributing to a diverse and dynamic web development landscape.