Experimental Framework · Open Source · v0.1
Flow-Arch/Vanilla is an exploration of what frontend development looks like when you take pure functions, declarative style, and Web Components seriously — with zero dependencies and zero compromise.
// 01 — Introduction
Modern frontend is drowning in abstraction. Frameworks grow larger, dependency trees deeper, and the distance between a developer's intent and the actual browser grows wider every year.
Flow-Arch/Vanilla asks a simple question: what if we stripped everything back? No virtual DOM. No build step. No framework runtime. Just the platform — and a set of disciplined ideas about how to organise it.
Flow-Arch turns complex engineering into standardized assembly. No magic, just logic. If it breaks, unscrew and replace.
This project is a long-term public exploration. Every demo, every experiment, and every failure is documented. The goal is not a production framework — it is a record of thinking.
Every piece of logic that can be pure, must be pure. Side effects are real — but they belong at the edges, not the core.
Describe what the UI should look like given a state. Don't write step-by-step DOM surgery. View = f(State).
Every dependency is a liability. The Web Platform is the framework. Use it directly and deliberately.
Data flows one way. State changes only through explicit actions. No two-way binding. No hidden channels.
Components own their styles and structure. No CSS leaks. No global conflicts. Real isolation.
Web Components render on the server. Declarative Shadow DOM means no flash of unstyled content. The web should still be crawlable.
// 02 — Methodology
These are not suggestions. They are the constraints that make Flow-Arch what it is. Constraints breed clarity.
All component state lives in one plain JS object. No classes, no proxies, no observables. Just data.
The reducer is a pure function:
(state, action) → newState. No exceptions. No shortcuts.
The view function takes state and returns HTML. It has no memory, no side effects, no access to the outside world.
DOM manipulation, timers, fetch calls — all of this belongs in the Web Component lifecycle methods, never inside reducers or view functions.
Use spread operators and immutable patterns. The original state object is never modified — a new one is always returned.
Flow-Arch runs directly in the browser. No Webpack, no Vite, no transpilation required. ES Modules only.
This is an exploration, not a product. Known problems are recorded openly. Failure is data.
// 03 — Core Pattern
Every Flow-Arch component follows the same loop. This is the entire mental model.
// ─── 1. STATE MODEL (pure data) ─────────────────────── const createInitialState = () => ({ count: 0 }) // ─── 2. REDUCER (pure function) ──────────────────────── // (state, action) → newState // Never mutates. Always returns new object. const reducer = (state, action) => { switch (action.type) { case "INCREMENT": return { ...state, count: state.count + 1 } case "DECREMENT": return { ...state, count: state.count - 1 } default: return state } } // ─── 3. VIEW (pure function) ─────────────────────────── // View = f(State) // State in → HTML string out. Nothing else. const view = (state) => ` <div class="count">${state.count}</div> <button data-action="DECREMENT">−</button> <button data-action="INCREMENT">+</button> ` // ─── 4. WEB COMPONENT (side-effect boundary) ────────── // The only place where DOM mutation is allowed. class FlowCounter extends HTMLElement { constructor() { super() this.attachShadow({ mode: "open" }) this.state = createInitialState() this.dispatch = (action) => { this.state = reducer(this.state, action) this.render() } } connectedCallback() { this.render() this.shadowRoot.addEventListener("click", (e) => { const type = e.target.dataset.action if (type) this.dispatch({ type }) }) } render() { this.shadowRoot.innerHTML = view(this.state) } } customElements.define("flow-counter", FlowCounter)
// 04 — Technology Stack
No invented abstractions. Every layer maps directly to a web standard or a mathematical concept.
Native custom elements. No polyfill. No runtime overhead. Just the platform.
True style and DOM encapsulation. Components are islands. No leaks possible.
Reducer + View are stateless, testable, deterministic. Inspired by Elm and Haskell.
State → View → Action → State. One loop. No exceptions. Predictable by design.
No server to maintain. No compatibility debt. Business logic lives in pure functions.
The data schema is the real backend. Infrastructure is just plumbing around it.
// 05 — Roadmap
This is a learning project. The roadmap is a record of intent, not a promise.
Minimal complete example. State + Reducer + View + Web Component. The loop in its simplest form.
Introduces async side effects (setInterval) and component cleanup lifecycle.
Exploring observedAttributes to pass initial state into components from HTML attributes.
Custom events as the messaging layer between sibling components.
First complex state example. Lists, filtering, persistence.
Replacing innerHTML with a minimal diffing approach to avoid full re-renders.
Pure function APIs on Cloudflare Workers. Data model design without server complexity.
// 06 — Known Limitations
Intellectual honesty is part of the methodology.
These are real problems, openly recorded.
Currently the entire Shadow DOM is rebuilt on each state change. This is expensive for complex components and loses input focus. A diffing strategy is needed.
Components are currently isolated islands. There is no global store or event bus yet.
Reducers are synchronous. There is no middleware or effect system for async operations like fetch calls.
Unlike Haskell or Elm, JS cannot enforce that reducers and view functions are truly pure. Discipline is required from the developer.