Getting Started

Install

bun i -D vite
bun i @denshya/proton

Setup

Use vite

package.json
{ "type": "module", // ... "scripts": { "dev": "vite", }, "dependencies": { // ... }, }

TIP

Any bundler works (not just vite), no bundler plugins required.

Enable Tama JSX

tsconfig.json
{ "compilerOptions": { // ... "jsx": "react-jsx", "jsxImportSource": "@denshya/tama", // ... } }

NOTE

Any JSX may work well in TamaJs, it depends on deviations from React/Tama JSX, but you can fix them with JSX customization.

Quick Start

Code

/src/main.tsx
import { WebInflator } from "@denshya/proton" function RangeApp() { return ( <div> <input type="range" min="0" max="100" step="1" value={0} /> <progress value={0} max="100">{0} %progress> <button>Resetbutton> div> ) } const inflator = new WebInflator const AppView = inflator.inflate(<App />) document.getElementById("root").replaceChildren(AppView)

Start

bun dev

Understanding

Inflation

Inflation is creating in-memory nodes from semi-serialized version (JSX). Inflating any structure will always output at least Node.

inflator.inflate(123) // => Text
inflator.inflate(<div />) // => HTMLDivElement
inflator.inflate(<div mounted={new State(false)} />) // => Comment
inflator.inflate(<Component />) // => ComponentGroup
inflator.inflate(new Comment) // => Comment

Learn more about ComponentGroup.

Component

Is pretty different from React:

  • no hooks
  • no rendering life cycle (function runs only once, i.e.)
  • no type constrains (supports async, async generator functions)
  • no return constrains
function Component() {
  return (...)
}

JSX

It's 100% compatible with React JSX, but it has a flavor. If you have interest in using different flavors create/support discussions in GitHub Repository.

<div
  onClick={event => event.x}
  on={{ click: event => event.x }}
  ariaLabel="label"
  aria={{ ariaLabel: "label" }}
>div>

onClick and ariaLabel supported but not typed to remain compatibility while giving a flavor.

Observable in JSX

Playground

function ColorApp() {
  const pointerMoveX$ = window.when("pointermove").map(event => event.x)
  const background = pointerMoveX$.map(x => x > 500 ? "red" : "green")

  return (
    <div style={{ background }}>{pointerMoveX$}div>
  )
}

Conditional mounting

function ColorApp() {
  const mounted$ = window.when("pointermove").map(event => !!event.x)

  return (
    <div mounted={mounted$}>Visiblediv>
  )
}

Lists

Supports plain array mapping just like in React, though doesn't require key attribute.

<div>{[1, 2, 3].map(item => <span>{item}span>)}div>

Also supports observable iterable (e.g. Array, Set, ...).

const items = new State([1, 2, 3])

<div>{items.map(items => items.map(item => <span>{item}span>))}div>

NOTE

This is a bit confusing snippet, you can ease it by using StateArray.

Extend Code

import { State } from "@denshya/reactive"
const PROGRESS_DEFAULT = 50

function App() {
  const progress = new State(PROGRESS_DEFAULT)

  return (
    <div style={{ display: "grid" }}>
      <input type="range" min="0" max="100" step="1" value={progress} />
      <progress value={progress} max="100">{progress} %progress>
      <button disabled={progress.is(PROGRESS_DEFAULT)} on={{ click: () => progress.set(PROGRESS_DEFAULT) }}>Resetbutton>

      <div>
        {Array.from({ length: 11 }, (_, index) => (
          <button on={{ click: () => progress.set(index * 10) }}>{index}button>
        ))}
      div>
    div>
  )
}

NOTE

You should acknowledge that this example uses @denshya/reactive, which is complementary, any observable-based state library works.