Getting Started
Install
bun i -D vite
bun i @denshya/proton
Setup
Use vite
{
"type": "module",
// ...
"scripts": {
"dev": "vite",
},
"dependencies": {
// ...
},
}
Any bundler works (not just vite
), no bundler plugins required.
Enable Proton JSX
{
"compilerOptions": {
// ...
"jsx": "react-jsx",
"jsxImportSource": "@denshya/proton/jsx/virtual",
// ...
}
}
Any JSX may work well in Proton, it depends on deviations from React/Proton JSX, but you can fix them with JSX customization.
Quick Start
Code
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>Reset</button>
</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
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$}>Visible</div>
)
}
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>
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) }}>Reset</button>
<div>
{Array.from({ length: 11 }, (_, index) => (
<button on={{ click: () => progress.set(index * 10) }}>{index}</button>
))}
</div>
</div>
)
}
You should acknowledge that this example uses @denshya/reactive
, which is complementary, any observable-based state library works.