Skip to content

Reactive UI Rendering without roots | JSX | React Unconstrained Alternative | Web Components Alternative | Micro-frontends | Cross-framework

License

Notifications You must be signed in to change notification settings

pinely-international/tama

🐈‍⬛ Tama - Reactive UI Rendering

😱 5kb gzip, DOM-first, Rootless, Class-based internals, No build, Fully customizable

Full TypeScript Support, Treeshakable, Based on the No Framework Principle

npm package minimized gzipped size Commitizen friendly Static Badge

Install

bun i @denshya/proton

What is "Rootless"?

It means you don't have to hijack an element to render the App, it cancels the Root Component and Host element completely.

That is a novel wording, another good phrase is "Reversed Element Factory Ownership." These all stand for a component (or element) factory function producing/providing ownership to owned elements rather than being a side-effect function, which only modifies a given element.

From querying the element and modifying it:

const element = document.getElementById("id")
makeWidget(element)

function makeWidget(element) {
  element.style = "..."
  // ... Some other styling and structure
}

To creating the desired element and sharing ownership:

function createWidget() { // returns an element instead.
  const element = document.createElement("div")
  element.style = "..."
  // ... Some other styling and structure

  return element
}

This forces you to find the exact place where the new element should go, which may be tricky. This is what Tama solves with JSX while still letting you choose the place to attach or reattach a Tama Component.

This allows you to do this: (Somewhat of an alternative to Web Components)

function Widget() {
  return document.createElement("div")
}
Widget.Standalone = inflator.inflate(<Widget />)

const container = document.querySelector(".container")
container?.append(Widget.Standalone)

DOM First & Easier Integration

Nodes are allowed in JSX, making it easier to integrate third-party libraries that are for VanillaJS.

function Widget() {
  const button = document.getElementById("button")
  button.append("Click me")

  return <div style={...}>{button}</div>
}

Observables Allowed in JSX

The turning point is that JSX element attributes and children can consume WICG Observables, meaning practically any library can be used as a State Manager.

const text = new State("")

const button = document.getElementById("button")
button.append(inflate.inflate(<div>{text}</div>))

text.set("Some text")

Continue reading about JSX Reactivity

Customization

Adding your own JSX Attribute for any element is as easy as ever.

For example, classMods - it will ensure BEM for elements without annoying imports.

inflator.jsxAttributes.set("classMods", context => {
  if (context.value == null) return
  context.bind("className", bem(context.props.className, context.value))
})

More about customization

Fault Tolerance

Unlike React, Tama will not propagate thrown errors to parents - errors in Children will not break Parents, while you can still catch them.

function Child() { throw new Error("Test") }
function Parent(this: Tama.Component) { return <div>123<Child /></div> }

document.body.append(inflate.inflate(<Parent />)) // Will render `123` without errors.

Learn how you can catch errors

Open Internals

To maintain open internals, this library uses Classes instead of Functions as factories and uses the private identifier in TypeScript, which gives you proper types while not stopping you from experimenting with internal variables and even allowing you to override them in a conventional way.

Similar Libraries

If you want to manage your components in a somewhat complex way (like in React), you can continue reading this, but otherwise, you may want to consider these alternatives:

Why Tama over React?

It is very similar to React, it tries to simplify development as we know it in React.

Feature Description
Extended Customization Custom Attributes, Children Adapters, Element Transformation, Class extension
Transition queue this.view.transitions exposes an awaitable FSM so you can stage view swaps and integrate with document.startViewTransition
No built-in State Manager Any State Manager that supports a Signal-like interface will just work in Tama, while there is no enforcement of one
Signals/Observables Support Native support for WICG Observables and Signal-like structures
No root elements Any component can be inflated and attached anywhere
Components can be Async 😱 (Client side) Await your values and delay/schedule the views with fallbacks and an initial view.
Top-level allowed You can do anything in any scope, Tama doesn't put any constraints on where or from what something is initialized - enjoy!
Children don't crash Parents An error in the subtree will not break the rendering of parents.
Return any value Components can be returned with any value, no seriously, even DOM Nodes/Elements will work.
Class-based Enables tree-shaking, extensibility, and open internals

React Inherited Features

Feature Description
Tree Context Explicit context sharing between subtree components
Conditional Rendering Tama implements Conditional Mounting
Layouts Swapping Conditionally changing the whole component layout
JSX Tama supports React JSX, but it also has its own flavor
ref attribute Access a DOM element when it's ready - supports refs merging as well
Event delegation Tama subscribes to parents rather than directly to elements too (for lists) (WIP)
SSR Provides an extendable JSXSerializer and examples with full DOM support in DOM-less environments like servers
Portal Portals are natural and very easy, you just use a component-scoped inflator
Error catching Tama exposes a clear API to catch errors and others

Problems to solve:

Tama isn't perfect, it's being developed. https://github.com/pinely-international/tama/milestones

Getting Started

function App() {
  return <div>Hello World!</div>
}

const inflator = new WebInflator
const AppView = inflator.inflate(<App />)

document.getElementById("root").replaceChildren(AppView)

JSX

Tama supports JSX. It maps directly to Document elements and allows any value to be put into attributes or as children of any element. And it has a different flavor compared to React JSX.

<div className="product-card">
  <h2>Title</h2>
  <p>Description</p>
  <img src="/static/product-card.jpg" />

  // You can put your weird stuff here.
  <aside id={new MyIdObject()} />
</div>

Learn more about Inflator to provide custom handlers.

TypeScript

Report if there is anything uncovered for TypeScript.

FAQ

Do React hooks work in Tama? No, but it's very extensible, so some enthusiasts might implement it. BTW, even though libraries from other "frameworks" won't work in Tama, libraries for Tama are supposed to work in other frameworks too.