Dataflow is a lightweight reactive JavaScript library heavily inspired by Observable Framework. It brings reactive programming to plain HTML pages with minimal overhead - the core library (including JSX support) is under 3KB compressed!
Just add a single data-reactive attribute to any script tag and it becomes reactive. That's it.
It works like a spreadsheet: just reference a value from another reactive script tag, and the plumbing happens automatically. No setState, no Redux, no reducers, no signals, no manual subscriptions. Just declarative dependencies.
Document order does not matter, all reactive scripts will be topologically sorted and turned into a DAG before execution.
A live clock using a generator that yields the current time:
<script type="module" data-reactive>
`The current time is ${new Date(now).toLocaleTimeString("en-GB")}.`
</script>A single expression automatically displays its result:
<script type="module" data-reactive>
1 + 2
</script>Statements like variable declarations don't display anything - they just define values for other blocks to use:
<script type="module" data-reactive>
const foo = 1 + 2;
</script>
The variable foo is defined but nothing is displayed.
For explicit control, import the display function. You can call it multiple times:
<script type="module" data-reactive>
for (let i = 0; i < 5; ++i) {
display(i);
}
</script>Any import in a reactive script is available to all other reactive scripts. So the imported display function is now available (document order does not matter)
<script type="module" data-reactive>
import {display, view, input} from '@bodar/dataflow/runtime.ts';
</script>
display, view,
input etc at least once if you want to use them.
<script type="module" data-reactive>
display('Using previously imported display function');
</script>If an expression evaluates to a DOM node, the node is inserted into the page. Use this to create dynamic content.
document.createTextNode("[insert chart here]")JSX is supported for creating DOM elements with a familiar syntax. Under the hood, JSX compiles directly to native DOM methods using the @bodar/jsx2dom library - no virtual DOM, no framework overhead.
For example, here's a component that displays a greeting:
const greeting = (name) => <i>Hello {name}!</i>
greeting("World")JSX attributes work as expected, including dynamic values:
const color = "blue";
<span style={`color: ${color}; font-weight: bold`}>Styled with JSX</span>Code blocks automatically re-run when referenced reactive variables change. The block below references a
now variable representing the current time in milliseconds; because now is reactive
(defined as a generator), this block runs continuously.
const now = function* () {
while (true) {
yield Date.now();
}
}
<span
style={`color: hsl(${(now / 10) % 360} 100% 50%)`}>Rainbow text!</span>The view function combines display and input to create reactive inputs. When
the input changes, dependent code re-runs.
const name = view(<input name="name" type="text" value="World"/>);`Hello, ${name}!`You can also use the @observablehq/inputs library for more sophisticated input controls.
import {range} from "@observablehq/inputs";
const slider = view(range([0, 100], {label: "Value", step: 1, value: 50}));`The slider value is: ${slider}`const a = view(<input type="number" value="10" style="width: 60px"/>);const b = view(<input type="number" value="5" style="width: 60px"/>);<span>{a} + {b} = {a + b}</span>const checked = view(<input type="checkbox" id="feature"/>);const color1 = view(<select>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>);<span style={`color: ${color1}; font-weight: bold`}>Selected color: {color1}</span>Reactive values can depend on multiple inputs. The dependent code re-runs whenever any input changes.
const width = view(<input type="range" min="50" max="300" value="150"/>);const height = view(<input type="range" min="50" max="200" value="100"/>);const color2 = view(<input type="color" value="#4a90d9"/>);<div style={`width: ${width}px; height: ${height}px; background: ${color2};
display: flex; align-items: center; justify-content: center; color: white;
text-shadow: 1px 1px 2px black`}>
{width} x {height}
</div>