@ibrahimkhan/flux
v1.0.0
Published
⚡ A tiny reactive web UI framework with no JSX and no virtual DOM.
Maintainers
Readme
⚡ Flux
A tiny, reactive web UI framework with no JSX and no virtual DOM. Write components as plain functions, compose UI with tagged template literals, and get fast, fine-grained updates with a minimal API.
🚀 Features
- ⚡ No build step required — runs directly in the browser
- 🧩 Zero external dependencies
- 💡 Familiar API —
html,on,mount,hooks,context,router,portals - 🔁 Direct DOM patching with smart diffing (no VDOM)
- 🪶 Lightweight (< 5 KB gzipped)
- 🧠 React-like hooks with Solid-like reactivity
📦 Installation
Via NPM (recommended)
npm install @Ibrahimkhan/fluxThen import in your app:
import { html, mount, useState } from "@Ibrahimkhan/flux";Via CDN
Use Flux directly in the browser with no build tools:
<script type="module">
import { html, mount, useState } from "https://cdn.jsdelivr.net/npm/@Ibrahimkhan/flux/dist/flux.esm.js";
</script>🏁 Quick Start
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Flux App</title>
<script src="flux.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { html, on, mount, useState } = window.Flux;
function Counter() {
const [count, setCount] = useState(0);
return html`
<section>
<h1>Count: ${count}</h1>
<div class="flex gap-2">
<button ${on('click', () => setCount((c) => c - 1))}>-</button>
<button ${on('click', () => setCount(0))}>Reset</button>
<button ${on('click', () => setCount((c) => c + 1))}>+</button>
</div>
</section>
`;
}
mount(Counter, '#app');
</script>
</body>
</html>🧠 Core Concepts
Components
Components are plain functions. They may receive a single props object.
function Hello({ name = 'World' }) {
return html`<h1>Hello, ${name}!</h1>`;
}Templates: html
Use tagged template literals to construct UI declaratively.
const view = html`
<article>
<h2>${title}</h2>
<p>${content}</p>
</article>
`;Events: on
Attach events using the on(type, handler) helper.
html`
<button ${on('click', handleClick)}>Click</button>
<input ${on('input', (e) => setValue(e.target.value))} />
`;Mounting: mount
Render a component into a DOM node or selector.
mount(App, '#root');
mount(Widget, document.getElementById('widget'));🔄 Hooks
useState
Reactive state with functional updates.
const [count, setCount] = useState(0);
setCount((c) => c + 1);useReducer
Reducer-based state for complex transitions.
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: 'inc' });useEffect
Run side effects when dependencies change.
useEffect(() => {
const id = setInterval(() => setTick((t) => t + 1), 1000);
return () => clearInterval(id);
}, [enabled]);useMemo / useRef
const total = useMemo(() => items.reduce((a, b) => a + b, 0), [items]);
const inputRef = useRef(null);
html`<input ${ref(inputRef)} />`;🌐 Context
const ThemeContext = createContext('light');
function Toolbar() {
const theme = useContext(ThemeContext);
return html`<div data-theme="${theme}">Toolbar</div>`;
}
function App() {
return html`${ThemeContext.Provider({ value: 'dark', children: Toolbar })}`;
}🧩 Lists & Conditionals
html`
<ul>
${each(users, (u) => u.id, (u) => html`<li>${u.name}</li>`)}
</ul>
${when(loading, html`<p>Loading…</p>`, html`<p>Done</p>`)}
`;🧰 Utilities
const btn = classes('btn', active && 'btn-active');
html`<button class="${btn}">Save</button>`;
html`<div ${style({ width: '100px', background: '#eee' })}></div>`;🧭 Router
createRouter([
{ path: '/', component: HomePage },
{ path: '/about', component: AboutPage },
]);
function Header() {
const route = useRoute();
return html`<p>Path: ${route.pathname}</p>`;
}🌀 Portals
function Modal({ open, onClose, children }) {
return open
? createPortal(
html`
<div class="backdrop" ${on('click', onClose)}></div>
<div class="modal">${children}</div>
`,
document.body
)
: '';
}🧱 Error Boundaries
const ErrorBoundary = createErrorBoundary((error) =>
html`<pre class="text-red-600">${String(error)}</pre>`
);
function App() {
return html`${ErrorBoundary({ children: BuggyComponent })}`;
}⚙️ TypeScript Support
Flux ships with flux.d.ts, offering:
- Full IntelliSense for all core APIs
- Strongly typed
useState,useEffect, etc. - JSX/TSX support for editors
⚡ Comparison
| Feature | Flux | React | Solid | | ----------------------- | :--: | :---: | :---: | | Build step required | ❌ | ✅ | ✅ | | Virtual DOM | ❌ | ✅ | ❌ | | Fine-grained reactivity | ✅ | ❌ | ✅ | | File size (<5 KB) | ✅ | ❌ | ✅ | | Hooks API | ✅ | ✅ | ✅ | | Template literals | ✅ | ❌ | ✅ |
🧪 Benchmarks (Synthetic)
| Operation | Elements | Avg. Update Time | | ---------------------- | -------- | ---------------- | | Re-render (keyed list) | 1,000 | ~0.5 ms | | Replace text node | 10,000 | ~4 ms | | Diff & patch DOM | 5,000 | ~2 ms |
Tested on Chrome 122 / M1 Air, micro-benchmarks only.
🧱 Project Structure
flux/
├─ dist/
│ ├─ flux.js
│ ├─ flux.esm.js
│ └─ flux.d.ts
├─ src/
│ ├─ flux.js
│ ├─ flux.esm.js
├─ package.json
└─ README.md💡 Patterns & Best Practices
- Use functional updates:
setCount(c => c + 1) - Always provide stable keys for lists
- Keep effects pure and narrowly scoped
- Avoid recreating handlers inside templates unnecessarily
- Use
useMemoto optimize expensive computations
🛠️ Known Limitations
- Single render root per mount
- Hooks must be called unconditionally in order
- No built-in async data primitives (use effects)
- No SSR yet (planned)
🧰 Known Issues & Recommended Improvements
- Optimize effect dependency tracking
- Add re-entrancy guards for nested renders
- Improve router update isolation
- Warn on duplicate
createRoutercalls - Add development diagnostics
🤝 Contributing
Pull requests and suggestions are welcome!
- Fork the repo
- Create a new branch
- Commit and push your changes
- Submit a PR
🙏 Credits
Flux draws inspiration from:
- React for the hooks model
- SolidJS for fine-grained reactivity
- lit-html for tagged template rendering
🕒 Changelog
v0.1.0
- Initial release with
html,on,mount, and core hooks.
📄 License
MIT © Ibrahim Khan
