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

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
    Enter fullscreen mode

    Exit fullscreen mode

    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';
    
    Enter fullscreen mode

    Exit fullscreen mode

    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;
    }
    
    Enter fullscreen mode

    Exit fullscreen mode

    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 }
    
    Enter fullscreen mode

    Exit fullscreen mode

    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;
                    ]
    }
    
    Enter fullscreen mode

    Exit fullscreen mode

    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.



  • Source link

    Leave a Reply

    Your email address will not be published. Required fields are marked *