reka.js

repository·main·Indexed 21 days ago

https://github.com/prevwong/reka.js

A state management system designed for building no-code editors using an AST-powered state system. It allows the design of complex, stateful UI components that compute a serializable JSON View tree for rendering in any framework. The ecosystem includes @rekajs/core, @rekajs/parser for Reka-syntax, @rekajs/collaboration for Yjs-powered real-time multiplayer editing, and integrations for React and CodeMirror.

Tokens
37.2K
Snippets
126
Records
151
Agent score
73%

What's inside reka.js

  1. What is reka.js?

    main

    reka.js is a state management system designed specifically for building no-code editors. It uses an Abstract Syntax Tree (AST) based state system to allow end-users to design complex, stateful UI components.

    Key capabilities include:

    • AST-based State: Enables users to define components with props, state, and templates (similar to React).
    • Portability: Computes a serializable JSON View tree from the state, making it easy to render in any UI framework (React, Vue, Svelte, etc.).
    • Extensibility: Allows adding custom data to the state via extensions.
    • External Functionalities: Exposes arbitrary JavaScript functions to the state/template environment.
    • Realtime Collaboration: Supports multiplayer editing via CRDTs (using Yjs).
  2. Use @rekajs/react-code-editor for development and testing

    main

    The @rekajs/react-code-editor package provides a React component built on top of CodeMirror. This component is designed to mutate the Reka Abstract Syntax Tree (AST) directly through the editor interface.

    Note: This component is primarily intended for local development and testing purposes rather than production use.

  3. Enable collaboration with @rekajs/collaboration, Yjs, and WebRTC

    main

    The Collaboration Example demonstrates how to integrate real-time collaboration capabilities into a Reka.js application using the @rekajs/collaboration package, powered by Yjs for shared state and WebRTC for peer-to-peer communication. This setup allows multiple users to edit the same document simultaneously.

    /* This example uses: */
    // @rekajs/collaboration
    // Yjs
    // WebRTC
  4. What is a Frame and how does it produce a View

    main

    In reka.js, a Frame is an instance of a component defined in the State. Its primary responsibility is to transform a component's stateful definition into a View—a serializable, static JSON structure that represents the component's output.

    When a component's definition in the state is updated, or when a side effect occurs, the Frame recomputes the View to reflect those changes.

    Example of a View output for a button component:

    {
        type: "TagView",
        tag: "button",
        props: {
            className: "bg-blue-900",
        },
        children: [
            {
                type: "TagView",
                tag: "text",
                props: {
                    value: 0
                },
            },
            {
                type: "TagView",
                tag: "text",
                props: {
                    value: "I'm enabled!"
                }
            }
        ]
    }
    {
        type: "TagView",
        tag: "button",
        props: {
            className: "bg-blue-900",
        },
        children: [
            {
                type: "TagView",
                tag: "text",
                props: {
                    value: 0
                },
            },
            {
                type: "TagView",
                tag: "text",
                props: {
                    value: "I'm enabled!"
                }
            }
        ]
    }
  5. Use TagTemplate for rendering logic

    main

    The template property of a RekaComponent uses TagTemplate to define the UI. It supports advanced rendering features similar to modern UI frameworks:

    • Expressions in Props: Use Identifier to bind props to component state or props (e.g., className: { type: 'Identifier', name: 'color' }).
    • Conditional Rendering: Use the if property with an expression (like BinaryExpression) to determine if the element should render.
    • List Rendering: Use the each property to iterate over an array. It requires an iterator (the array), an alias (the name for the current item), and an index (the current loop index).
    • Nesting Components: You can nest other components within a TagTemplate using a ComponentTemplate type, referencing other RekaComponents by name.
    // Example: Conditional rendering with 'if'
    {
        type: "TagTemplate",
        name: "div",
        props: {},
        children: [],
        if: {
            type: "BinaryExpression",
            left: "counter",
            operator: "==",
            right: 0
        },
        each: null,
    }
    
    // Example: Iterating with 'each'
    {
        type: "TagTemplate",
        name: "div",
        props: {
            className: {
                type: "Identifier",
                name: "color",
            },
        },
        children: [],
        each: {
            iterator: {
                type: "Identifier",
                name: "someArrayValue",
            },
            alias: "color",
            index: "i"
        },
    }
    
    // Example: Nesting a RekaComponent
    {
        type: "TagTemplate",
        name: "div",
        children: [
            {
                type: "ComponentTemplate",
                component: { type: "Identifier", name: "Button" },
                props: {},
                children: []
            }
        ]
    }
  6. How realtime collaboration works in Reka

    main

    Reka achieves multiplayer capabilities through the @rekajs/collaboration package, which is powered by Yjs (a CRDT library).

    By design, Reka's core State is not a CRDT. The @rekajs/collaboration package provides an Extension that mirrors the core State in a Yjs CRDT.

    The synchronization flow:

    1. A change occurs in the Reka State.
    2. The change is propagated to the mirrored CRDT.
    3. The change is sent across the network to other clients.
    4. The change is applied to the local CRDTs of all clients.
    5. The changes from the CRDT are applied back to the core State of each client.

    State Representation: While Reka's State is a nested tree, it is represented as a flat tree within the Yjs CRDT to facilitate conflict-free merging.

    // State representation in Reka (Nested Tree)
    {
        type: "State',
        program: {
            type: "Program",
            components: [
                {
                    type: "RekaComponent",
                    state: [],
                    props: [],
                    template: null,
                }
            ]
        }
    };
    
    // Flatten State representation in Yjs-CRDT (Flat Tree)
    {
        types: {
            "state-id": {
                type: "State",
                program: "program-id",
            },
            "program-id": {
                type: "Program",
                components: ["component-id"]
            },
            "component-id": {
                type: "RekaComponent",
                state: [],
                props: [],
                template: null,
            }
        },
        root: "state-id",
    }
  7. Reka-syntax: Component Templates

    main

    Component templates in Reka are similar to JSX but include specific directives for text, conditionals, loops, and slots.

    Text Values

    Unlike standard JSX where text can be naked, in Reka-syntax, text values must be wrapped in a <text /> tag.

    Conditionals

    Use the @if directive on an element to conditionally render it based on a boolean expression.

    Foreach Loops

    Use the @each directive to iterate over a list. You can optionally specify an index variable.

    Children (Slots)

    To allow a component to accept and render children, use the <slot /> element within the template.

    // Text values
    component ComponentName() {} => (
        <text value="Hello World!" />
    )
    
    // Conditionals
    component ComponentName(prop1) {
        val showCounter = false;
    } => (
        <div @if={showCounter}>
        </div>
    )
    
    // Foreach
    val items = ["a", "b", "c"];
    component ComponentName(prop1) {}
    } => (
        <div @each={item in items}>
            <text value={item} />
        </div>
    )
    
    // Foreach with index
    <div @each={(item, i) in items}>
        <text value={i + " " + item} />
    </div>
    
    // Slots
    component Button() {} => (
        <button>
            <slot />
        </button>
    )
    
    component App() {} => (
        <div>
            <Button>
                <text value="Click me!" />
            </Button>
        </div>
    )
  8. Understand the core concept of Reka's state management

    main

    Reka is designed as an experimental state management system intended to replace the internal state management of Craft.js. While Craft.js handles high-level page builder functionalities like drag-and-drop, Reka focuses on providing a more powerful state structure that supports complex UI component logic.

    In traditional systems like Craft.js, the EditorState is a simple implicit tree data structure representing a static template. Reka aims to evolve this by allowing end-users to build UI components that behave more like React components—supporting JS expressions, stateful values, conditional rendering, and array iteration—rather than just mutating static prop values in a tree.

    // Example of the simple implicit tree structure Reka aims to enhance:
    {
        "ROOT": {
            "type": "div",
            "props": {},
            "nodes": ["node-a"],
        },
        "node-a": {
            "type": "Button",
            "props": {
                "text": "Hello World!"
            },
            "nodes": []
        }
    }
  9. What are Extensions in Reka.js

    main

    Extensions allow you to store additional, arbitrary values within the Reka State alongside the Program AST. This is useful for metadata that your application requires but doesn't belong in the core AST, such as user comments on specific nodes.

    Key benefits include:

    • Unified API: Interact with extension state using the same APIs used for the core State.
    • Realtime Collaboration: When using the @rekajs/collaboration package, extension state is automatically synchronized across peers.