Skip to content

v0.21.0

Latest

Choose a tag to compare

@mauro-andre mauro-andre released this 08 Nov 20:44
5581f64

CuboMX Release 0.21.0: JSX/TSX Support & Enhanced Event Modifiers

This release brings first-class JSX/TSX support to CuboMX, enabling you to use modern React-like syntax for server-side rendering while maintaining full compatibility with CuboMX's reactive directives. Additionally, we've enhanced event modifiers to work seamlessly with JSX's attribute naming constraints.


✨ New Features

JSX/TSX Support via Preact

CuboMX now supports JSX/TSX for server-side rendering through Preact integration! This allows you to write type-safe, composable components using familiar JSX syntax while leveraging CuboMX's hydration and reactivity.

Installation:

npm install preact preact-render-to-string

Configuration (tsconfig.json):

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

Example Usage:

import { CuboMX } from "cubomx";

// Define a JSX component
function UserCard({ name, email }: { name: string; email: string }) {
    return (
        <div mx-data="userCard" className="user-card">
            <h2 mx-bind:text="name">{name}</h2>
            <p mx-bind:text="email">{email}</p>
        </div>
    );
}

// Register the component
CuboMX.component("userCard", {
    name: null,
    email: null,
});

// Swap JSX directly into the DOM
await CuboMX.swap(
    <UserCard name="John Doe" email="john@example.com" />,
    [{ target: "#container:innerHTML" }]
);

// After hydration, the component becomes reactive
CuboMX.userCard.name = "Jane Smith"; // DOM updates automatically

What's Included:

  • VNode Support in CuboMX.swap(): The swap() function now accepts both HTML strings and Preact VNodes
  • New Helper Functions:
    • isVNode(value) - Type guard to check if a value is a VNode
    • renderVNodeToString(vnode) - Manually render a VNode to HTML
  • Automatic Rendering: VNodes are automatically converted to HTML during swap operations
  • Full CuboMX Compatibility: All directives (mx-data, mx-bind:*, mx-on:*, etc.) work seamlessly in JSX
  • Type Safety: Full TypeScript support with proper type inference

Benefits:

  • ✅ Modern DX with familiar React-like syntax
  • ✅ Type-safe component props with TypeScript
  • ✅ Composable, reusable components
  • ✅ Server-side rendering out of the box
  • ✅ Zero breaking changes - fully optional feature
  • ✅ Works alongside traditional HTML templates

JSX-Compatible Event Modifier Syntax

Since JSX doesn't allow the @ symbol or dots (.) in attribute names, we've introduced an alternative syntax for event modifiers that's case-insensitive and works across all environments.

New Syntax:

Append the modifier name directly to the event name:

// Traditional HTML/Template syntax (still works)
<form @submit.prevent="save()">
<button @click.stop="handleClick()">
<div @click.outside="close()">

// New JSX/TSX syntax (case-insensitive)
<form mx-on:submitPrevent="save()">
<button mx-on:clickStop="handleClick()">
<div mx-on:clickOutside="close()">

How It Works:

The parser intelligently detects modifiers at the end of event names, supporting:

  • Dot notation: click.prevent, submit.stop
  • camelCase: clickPrevent, submitStop (JSX)
  • lowercase: clickprevent, submitstop (HTML normalized)
  • ANY CASE: CLICKPREVENT, ClickPrevent (all work!)

Supported Modifiers:

  • prevent / .prevent → event.preventDefault()
  • stop / .stop → event.stopPropagation()
  • outside / .outside → Listen for clicks outside the element

Examples:

// Prevent form submission
<form mx-on:submitPrevent="handleSubmit()">
    <button type="submit">Save</button>
</form>

// Stop event propagation
<div mx-on:click="outerClick()">
    <button mx-on:clickStop="innerClick()">
        Click me
    </button>
</div>

// Close on outside click
<div mx-data="dropdown">
    <button mx-on:click="toggle()">Toggle</button>
    <div mx-show="isOpen" mx-on:clickOutside="close()">
        Dropdown content
    </div>
</div>

All case variations work:

<form mx-on:submitPrevent="save()">   // ✅ camelCase
<form mx-on:submitprevent="save()">   // ✅ lowercase
<form mx-on:SUBMITPREVENT="save()">   // ✅ uppercase
<form mx-on:SubmitPrevent="save()">   // ✅ mixed case

🛠️ Improvements

Enhanced Type Definitions

  • Updated PublicAPI type: The swap() method now accepts string | any to support VNodes without creating a hard dependency on Preact
  • New exports: isVNode and renderVNodeToString are now exported from the main module
  • JSX type support: Full TypeScript support for JSX elements in swap operations

Documentation Updates

  • JSX/TSX Installation Guide: Added comprehensive setup instructions in README
  • JSX Examples: Updated CuboMX.swap() documentation with JSX usage examples
  • Event Modifier Guide: Documented the new JSX-compatible syntax for event modifiers
  • Best Practices: Added guidance on when to use JSX vs HTML templates

📦 Technical Details

New Files

  • src/jsx-helpers.ts - JSX/TSX support utilities
  • test/JSX-support.test.tsx - 17 comprehensive tests for JSX functionality
  • test/Directive-mx-on-camelCase.test.tsx - 11 tests for case-insensitive event modifiers

Modified Files

  • src/swap.ts - Added VNode support and automatic rendering
  • src/mx-on.ts - Implemented case-insensitive event modifier parsing
  • src/cubomx.ts - Exported JSX helper functions
  • src/types.ts - Updated type definitions for VNode support
  • tsconfig.json - Added JSX configuration
  • package.json - Added Preact dependencies
  • README.md - Extensive documentation updates

Dependencies

New Dev Dependencies:

  • preact: ^10.26.1
  • preact-render-to-string: ^6.6.1

Note: These are peer dependencies and completely optional. CuboMX works perfectly without them if you don't use JSX.


📊 Testing

  • All existing tests pass: 339 tests total
  • 17 new JSX tests: Comprehensive coverage of JSX functionality
  • 11 new event modifier tests: Validates case-insensitive parsing
  • Zero regressions: Full backward compatibility maintained

🔄 Migration Guide

No migration needed! This release is 100% backward compatible.

If you want to use JSX/TSX:

  1. Install dependencies:

    npm install preact preact-render-to-string
  2. Update tsconfig.json:

    {
      "compilerOptions": {
        "jsx": "react-jsx",
        "jsxImportSource": "preact"
      }
    }
  3. Start using JSX in your swap() calls:

    await CuboMX.swap(<MyComponent />, [{ target: "#app" }]);

Event Modifiers:

  • Old syntax (@click.prevent) continues to work
  • New syntax (mx-on:clickPrevent) is available for JSX compatibility
  • Both can be used interchangeably

🎯 What's Next

This release lays the groundwork for modern component-based development with CuboMX. Future releases may explore:

  • Enhanced JSX/TSX type definitions
  • Additional JSX-specific utilities
  • More comprehensive JSX examples and patterns

🙏 Acknowledgments

Thanks to the Preact team for their excellent lightweight React alternative that makes this integration possible!


📝 Full Changelog

Added:

  • JSX/TSX support via Preact integration
  • isVNode() helper function
  • renderVNodeToString() helper function
  • normalizeToHTMLString() internal utility
  • Case-insensitive event modifier parsing
  • JSX-compatible event modifier syntax (e.g., clickPrevent, submitStop)
  • 28 new tests for JSX and event modifier functionality
  • Comprehensive JSX documentation in README

Changed:

  • CuboMX.swap() now accepts string | VNode
  • Event modifier parsing is now case-insensitive
  • Updated TypeScript configuration for JSX support

Fixed:

  • Event modifiers now work seamlessly in JSX/TSX environments

Version: 0.21.0
Release Date: November 8, 2025
Total Tests: 339 (all passing)