{"id":19486892,"url":"https://github.com/archan937/dust","last_synced_at":"2026-04-11T02:40:40.300Z","repository":{"id":219560583,"uuid":"749335852","full_name":"archan937/dust","owner":"archan937","description":"A minimalistic reactive Javascript library for building dynamic component-based interfaces","archived":false,"fork":false,"pushed_at":"2024-10-30T06:52:59.000Z","size":646,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-08T08:16:05.453Z","etag":null,"topics":["bun","components","esbuild","jsx","minimalistic","nextjs","react","reactive","router","styled","usestate"],"latest_commit_sha":null,"homepage":"https://archan937.github.io/dust/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/archan937.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-28T09:25:55.000Z","updated_at":"2024-10-30T06:53:02.000Z","dependencies_parsed_at":"2024-10-18T15:14:46.915Z","dependency_job_id":"7bb53451-6138-41f7-af7b-c873c43a59dd","html_url":"https://github.com/archan937/dust","commit_stats":null,"previous_names":["archan937/hydrogen.js","archan937/dust"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archan937%2Fdust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archan937%2Fdust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archan937%2Fdust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/archan937%2Fdust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/archan937","download_url":"https://codeload.github.com/archan937/dust/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240728330,"owners_count":19848115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bun","components","esbuild","jsx","minimalistic","nextjs","react","reactive","router","styled","usestate"],"created_at":"2024-11-10T20:42:19.243Z","updated_at":"2026-04-11T02:40:40.294Z","avatar_url":"https://github.com/archan937.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dust\n\n```\n  ·    *  ·     ✦   ·    ·  *   ·\n·   ✧     ·  ·    *    ✦     ·\n\n  ██████╗ ██╗   ██╗███████╗████████╗\n  ██╔══██╗██║   ██║██╔════╝╚══██╔══╝\n  ██║  ██║██║   ██║███████╗   ██║\n  ██║  ██║██║   ██║╚════██║   ██║\n  ██████╔╝╚██████╔╝███████║   ██║\n  ╚═════╝  ╚═════╝ ╚══════╝   ╚═╝\n\n    React's API. Zero re-renders.\n\n·   ✧  ·     *   ✦  ·    ·\n  ·    ·   *    ✧     ·   ✦   ·\n```\n\nDust gives you `useState`, `useEffect`, `useRef`, `createContext`, JSX, and routing — all the patterns you know — but without a virtual DOM or diffing engine. When state changes, only the exact DOM node that depends on it updates. No re-renders, no reconciliation, no overhead.\n\n## Why Dust?\n\nMost reactive frameworks re-render components on state change and reconcile a virtual DOM tree to figure out what to update. Dust skips that entirely.\n\n`useState` returns a **Getter** — a callable proxy that records which DOM nodes depend on it. When you update state, those nodes update directly. A component function runs once to build the initial DOM; it never runs again.\n\n```jsx\nconst [count, setCount] = useState(0);\n\n// Each of these is a live, independently-updating DOM text node:\n\u003cp\u003eTotal: {count}\u003c/p\u003e\n\u003cp\u003eDouble: {count() * 2}\u003c/p\u003e\n```\n\nThe transpiler automatically wraps bare JSX expressions in arrow functions, so writing `{count}` works out of the box — no `.value`, no `$`, no magic syntax to learn.\n\n## Features\n\n- **Surgical DOM updates** — state change → one node updates, nothing else touches the DOM\n- **Proxy-based nested reactivity** — `user.name()` is reactive automatically, no selectors needed\n- **Familiar API** — `useState`, `useEffect`, `useRef`, `createContext` / `useContext`\n- **ReactiveList** — identity-based list reconciliation via `.map()`, each item gets its own local state\n- **`css` tagged template + `cx` utility** — zero-cost scoped styles, no build plugin needed\n- **JSX auto-wiring** — bare identifiers in JSX are wrapped by the transpiler; write `{count}`, not `{count()}`\n- **File-system routing** (`DirectoryRouter`) and declarative JSX routing (`BrowserRouter`)\n- **Batteries-included CLI** — dev server with HMR, production bundler, preview server\n\n## Requirements\n\n[Bun](https://bun.sh) runtime.\n\n## Getting started\n\nCreate a project that depends on Dust:\n\n```json\n{\n  \"dependencies\": {\n    \"dust\": \"^1.0.0\"\n  }\n}\n```\n\nAdd an `index.html` with a JSX entry point:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n    \u003ctitle\u003eMy App\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n    \u003cscript type=\"text/jsx\" src=\"./src/main.jsx\"\u003e\u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n```jsx\n// src/main.jsx\nimport Dust, { createRoot, useState } from 'dust';\n\nconst App = () =\u003e {\n  const [count, setCount] = useState(0);\n  return \u003cbutton onClick={() =\u003e setCount((n) =\u003e n + 1)}\u003eCount: {count}\u003c/button\u003e;\n};\n\ncreateRoot(document.getElementById('root')).render(\u003cApp /\u003e);\n```\n\n```sh\ndust dev      # start dev server with HMR at http://localhost:3000\ndust build    # bundle to dist/\ndust preview  # serve dist/\n```\n\n## How reactivity works\n\n`useState` returns a **Getter** — a callable Proxy that doubles as a subscription handle. Calling it (`count()`) reads the current value. When called inside a reactive context (a DOM text node, a reactive attribute, a `useEffect`), Dust records the dependency. On the next `setCount`, only the DOM nodes that read `count` are updated — in place, with no diffing.\n\n### JSX auto-wiring\n\nThe Babel transpiler wraps bare identifiers and zero-argument calls in JSX children in an arrow function before `Dust.createElement` sees them:\n\n```jsx\n// What you write:\n\u003cp\u003eCount: {count}\u003c/p\u003e;\n\n// What the transpiler emits:\ncreateElement('p', null, 'Count: ', () =\u003e count);\n```\n\nThe runtime detects the function child, calls it with tracking enabled, and subscribes the resulting text node to `count` directly. When `setCount` fires, only that text node updates — the component function is never called again.\n\n| Context                               | How to read state                    |\n| ------------------------------------- | ------------------------------------ |\n| JSX children / attributes             | `{count}` or `{count()}` — both work |\n| `useEffect`, event handlers, plain JS | `count()` — call explicitly          |\n\n```jsx\n// JSX — transpiler handles it\n\u003cp\u003e{count}\u003c/p\u003e;\n\n// Outside JSX — call explicitly\nuseEffect(() =\u003e {\n  document.title = `Count: ${count()}`;\n}, [count]);\n```\n\n## API\n\n### useState\n\n```jsx\nconst [count, setCount] = useState(0);\n\ncount(); // read value (tracks as dependency inside JSX / useEffect)\nsetCount(1); // set directly\nsetCount((n) =\u003e n + 1); // set via updater\n\n// Nested objects — each property becomes a reactive Getter automatically\nconst [user, setUser] = useState({ name: 'Alice', age: 30 });\n\u003cp\u003e{user.name}\u003c/p\u003e; // updates only when user.name changes\n```\n\n### useEffect\n\nRuns the callback when any listed dependency changes. The optional return value is a cleanup function called before the next run or on unmount.\n\n```jsx\nconst [running, setRunning] = useState(false);\n\nuseEffect(() =\u003e {\n  if (!running()) return;\n  const id = setInterval(() =\u003e tick(), 1000);\n  return () =\u003e clearInterval(id);\n}, [running]);\n```\n\n### useRef\n\n```jsx\nconst inputRef = useRef(null);\n\u003cinput ref={inputRef} /\u003e;\n\n// Later:\ninputRef.current.focus();\n```\n\n### createContext / useContext\n\n```jsx\nconst ThemeCtx = createContext('light');\n\nfunction App() {\n  return (\n    \u003cThemeCtx.Provider value=\"dark\"\u003e\n      \u003cChild /\u003e\n    \u003c/ThemeCtx.Provider\u003e\n  );\n}\n\nfunction Child() {\n  const theme = useContext(ThemeCtx); // 'dark'\n  return \u003cp\u003e{theme}\u003c/p\u003e;\n}\n```\n\n### ReactiveList\n\nCalling `.map()` on a state Getter returns a `ReactiveList`. DOM nodes are reconciled by item identity when the array changes — nodes for unchanged items are reused, not recreated. Each item's render function can create its own independent local state.\n\n```jsx\nconst [items, setItems] = useState([{ id: 1, text: 'Buy milk' }]);\n\nconst list = items.map((item) =\u003e {\n  const [done, setDone] = useState(false);\n  return (\n    \u003cli onClick={() =\u003e setDone((v) =\u003e !v)}\u003e\n      {done() ? \u003cs\u003e{item.text}\u003c/s\u003e : item.text}\n    \u003c/li\u003e\n  );\n});\n\n\u003cul\u003e{list}\u003c/ul\u003e;\n```\n\n### css / cx\n\n`css` injects scoped styles into a single `\u003cstyle\u003e` tag and returns a stable class name. `cx` joins class names, skipping falsy values — useful for conditional styling without string interpolation.\n\n```jsx\nimport Dust, { css, cx } from 'dust';\n\nconst btn = css`\n  padding: 0.5rem 1rem;\n  border-radius: 4px;\n`;\n\nconst btnPrimary = css`\n  background: royalblue;\n  color: white;\n`;\n\nconst Button = ({ primary, label }) =\u003e (\n  \u003cbutton className={cx(btn, primary \u0026\u0026 btnPrimary)}\u003e{label}\u003c/button\u003e\n);\n```\n\n## Routing\n\n### DirectoryRouter (file-system based)\n\nPlace pages under `src/pages/`. The dev server discovers and injects them automatically — no imports to write.\n\n```\nsrc/pages/index.jsx       → /\nsrc/pages/about.jsx       → /about\nsrc/pages/blog/:id.jsx    → /blog/:id\n```\n\n```jsx\nimport Dust, { createRoot, DirectoryRouter } from 'dust';\ncreateRoot(document.getElementById('root')).render(\u003cDirectoryRouter /\u003e);\n```\n\n### BrowserRouter (declarative)\n\n```jsx\nimport Dust, { createRoot, BrowserRouter, Route } from 'dust';\nimport Home from './pages/Home';\nimport About from './pages/About';\n\ncreateRoot(document.getElementById('root')).render(\n  \u003cBrowserRouter\u003e\n    \u003cRoute path=\"/\" component={Home} /\u003e\n    \u003cRoute path=\"/about\" component={About} /\u003e\n  \u003c/BrowserRouter\u003e,\n);\n```\n\nBoth routers use the History API — navigation is SPA-style with no full-page reloads. Use `useParams()` to read dynamic path segments.\n\n## Examples\n\nTwo full example apps live in `examples/`:\n\n- **`nextjs-like-routing`** — `DirectoryRouter` with file-system pages\n- **`reactrouter-like-routing`** — `BrowserRouter` with declarative routes\n\nEach example includes a Playground page that demonstrates `useState`, `useEffect`, `useRef`, `createContext` / `useContext`, `ReactiveList`, `css`, and `cx` side-by-side.\n\n## Contact\n\nFor support, remarks, and requests: [pm_engel@icloud.com](mailto:pm_engel@icloud.com)\n\n## License\n\nCopyright (c) 2026 Paul Engel, released under the MIT License\n\nhttp://github.com/archan937 — [pm_engel@icloud.com](mailto:pm_engel@icloud.com)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchan937%2Fdust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchan937%2Fdust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchan937%2Fdust/lists"}