DEV Community

Cover image for b0nes: The Framework That Actually Fits in Your Head (Yes, Really) Or: How I Learned to Stop Worrying and Love Zero Dependencies
Iggy
Iggy

Posted on

b0nes: The Framework That Actually Fits in Your Head (Yes, Really) Or: How I Learned to Stop Worrying and Love Zero Dependencies

The Problem Nobody Talks About

You know what's wild? We've normalized this:

npm install
# *goes to make coffee*
# *comes back*
# *still installing*
# ~350 packages installed
# node_modules is now ~450MB
# half of them are security vulnerabilities
Enter fullscreen mode Exit fullscreen mode

Meanwhile, junior devs are like "I just wanted to make a website" and we're like "Cool, first install these 47 build tools, configure TypeScript, set up webpack, oh and here's 200 pages of React docs."
This is insane.

Enter b0nes 🦴

b0nes is what happens when you ask: "What if we just... didn't?"

  • Zero npm dependencies (not "minimal", not "lightweight", ZERO)
  • Pure JavaScript (no TypeScript required, but use it if you want. But we leverage JSDocs)
  • No build tools (unless you count Node.js)
  • SSR & SSG built-in
  • State management included (Redux-style, but simple)
  • FSM routing for SPAs (XState vibes, zero complexity)
  • Atomic design out of the box.

The Philosophy (aka The Rant)

1. Dependencies Are Liabilities

Every dependency is:

  • A security risk
  • A maintenance burden
  • Someone else's breaking changes
  • Another thing that can go wrong

b0nes runs on Node.js built-ins.

That's it. This code will run unchanged in 10 years.

2. Build Tools Are Often Unnecessary

You know what's a great build tool? Not having one.

// This is valid b0nes code
import { button } from './components/atoms/button/button.js';

const html = button({ 
    type: 'submit', 
    slot: 'Click Me' 
});
Enter fullscreen mode Exit fullscreen mode

No transpilation. No bundling. No webpack config from hell. It just works.

3. Complexity Should Be Optional

Want to build a blog? Here's literally the entire setup:

git clone https://github.com/iggydotdev/b0nes.git
cd b0nes
npm run dev
Enter fullscreen mode Exit fullscreen mode

That's it. No create-react-app, no next config, no "please update your node version to 18.4.2 exactly or everything breaks."

Show Me Code (Finally)

Components Are Just Functions

// src/components/atoms/badge/badge.js
import { processSlotTrusted } from '../../utils/processSlot.js';
import { normalizeClasses } from '../../utils/normalizeClasses.js';

export const badge = ({ slot, variant = 'default', className = '', attrs = '' }) => {
    attrs = attrs ? ` ${attrs}` : '';
    const classes = normalizeClasses(['badge', `badge-${variant}`, className]);
    const slotContent = processSlotTrusted(slot);

    return `<span class="${classes}"${attrs}>${slotContent}</span>`;
};
Enter fullscreen mode Exit fullscreen mode

That's it. A component is a function that returns HTML. No JSX, no virtual DOM, no reconciliation algorithm, no hydration errors.

Pages Are Just Component Configs

// src/pages/home.js
export const components = [
    {
        type: 'organism',
        name: 'hero',
        props: {
            slot: [
                { type: 'atom', name: 'text', props: { is: 'h1', slot: 'Welcome' } },
                { type: 'atom', name: 'badge', props: { slot: 'New!', variant: 'primary' } }
            ]
        }
    }
];

export const meta = { 
    title: 'Home',
    stylesheets: ['/styles/main.css']
};
Enter fullscreen mode Exit fullscreen mode

Routing Is Auto-Discovered

src/pages/
├── index.js          → /
├── about/
│   └── index.js      → /about
└── blog/
    ├── index.js      → /blog
    └── [slug].js     → /blog/:slug
Enter fullscreen mode Exit fullscreen mode

No route config. No getStaticPaths. It just works.

State Management (Because You Need It)

import { createStore } from './framework/client/store.js';

const store = createStore({
    state: { count: 0 },
    actions: {
        increment: (state) => ({ count: state.count + 1 }),
        decrement: (state) => ({ count: state.count - 1 })
    },
    getters: {
        doubled: (state) => state.count * 2
    }
});

store.dispatch('increment');
const current = store.getState();
const doubled = store.computed('doubled');
Enter fullscreen mode Exit fullscreen mode

Redux-style, but you can actually understand it. No actions/reducers/dispatch/connect/mapStateToProps ceremony.

FSM Routing (The Cool Part)

For SPAs, we don't use a traditional router. We use finite state machines.

import { createFSM } from './framework/client/fsm.js';

const authFSM = createFSM({
    initial: 'logged-out',
    states: {
        'logged-out': {
            on: { LOGIN: 'logging-in' }
        },
        'logging-in': {
            on: { 
                SUCCESS: 'logged-in',
                FAILURE: 'logged-out'
            }
        },
        'logged-in': {
            on: { LOGOUT: 'logged-out' }
        }
    }
});

authFSM.send('LOGIN');
authFSM.is('logging-in'); // true
Enter fullscreen mode Exit fullscreen mode

Why FSM?

  • Impossible states become impossible (no "loading while logged out")
  • All transitions are explicit (no mystery navigation)
  • Easy to visualize and test
  • Self-documenting code

For SPAs, we have a router FSM:

import { createRouterFSM, connectFSMtoDOM } from './framework/client/fsm.js';

const routes = [
    { name: 'home', url: '/', template: '<h1>Home</h1>' },
    { name: 'about', url: '/about', template: '<h1>About</h1>' }
];

const { fsm } = createRouterFSM(routes);
connectFSMtoDOM(fsm, document.getElementById('app'), routes);

// Navigate
fsm.send('GOTO_ABOUT');
Enter fullscreen mode Exit fullscreen mode

Interactive Components (The "But Does It Even Work?" Part)

Yes, we have interactive components. Tabs, modals, dropdowns - all with zero dependencies.

{
    type: 'molecule',
    name: 'tabs',
    props: {
        tabs: [
            { label: 'Tab 1', content: '<p>Content 1</p>' },
            { label: 'Tab 2', content: '<p>Content 2</p>' }
        ]
    }
}
Enter fullscreen mode Exit fullscreen mode

The runtime is small and handles:

  • Event delegation
  • Memory management
  • Progressive enhancement
  • Keyboard navigation

All the stuff React Router gives you, but in smaller than 500KB.

What b0nes Is NOT

Let's be real:
Not trying to replace React for complex SPAs with thousands of components
Not for real-time apps (use WebSockets and your favorite framework. Still need to think about this one)
Not opinionated about CSS (bring your own Tailwind/vanilla/whatever)
Not production-tested at scale (it's v0.2.0, we're getting there)

What b0nes IS

A complete toolkit with everything you need
Zero dependencies means rock-solid stability
SSG/SSR for content-heavy sites
State management and FSM routing for SPAs
Progressive enhancement (works without JS, better with it)
For devs who want to understand their framework

The Current State of Things (Honesty Hour)

Look, I'm not gonna lie - I'm still working out some kinks with the SPA stuff. The FSM router is solid, but template compilation and asset paths are... let's say "a work in progress."
Some routes might 404. Some assets might not load. The SPA demo might crash your browser (kidding... mostly).
But here's the thing: This is version 0.2.0. Still learning. Still iterating. And unlike most frameworks, you can actually read the source code and understand what's broken.

Try It (If You're Brave)

git clone https://github.com/iggydotdev/b0nes.git
cd b0nes
npm run dev
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:5000 and see a working site. Try the talk (slides) example at /examples/talk (and report bugs please 🙏).
Build a static site:

npm run build
# Outputs to public/
# Deploy anywhere - Netlify, Vercel, GitHub Pages
Enter fullscreen mode Exit fullscreen mode

The Real Pitch

We've been overengineering web development for years. Junior devs are drowning in tooling. Senior devs are exhausted by constant churn.
What if we just... built websites?
No 847 dependencies. No webpack configs. No "this breaking change affects 0.01% of users but you must update immediately."
Just:

  • Write components (functions that return HTML)
  • Compose pages (arrays of components)
  • Ship static files

Is b0nes perfect? No.
Is it production-ready for everything? Not yet.
Will it replace React? Probably not.

But it might just make web development fun again.

Links

--
🐙 GitHub
📦 npm
📖 Docs (it's the README, we're indie like that)
🐛 Issues (please report bugs!)

Final Thoughts

If you've read this far, you're either:

  1. Interested in zero-dependency frameworks
  2. Hate-reading while using your 847-package React app
  3. My work colleagues (yow!)

Either way, give b0nes a try. Break it. Report bugs. Contribute. Learn.
Or don't. Keep using React. It's fine. We're all just trying to ship websites here.
But if you're tired of npm install taking longer than your actual dev work... well, you know where to find b0nes.
Iggy

Creator of b0nes, destroyer of node_modules folders

P.S. Yes, the SPA stuff is janky right now. We're working on it. PRs welcome. Please don't roast me too hard on Twitter.

P.P.S. - If you're from Vercel or Netlify and want to sponsor this, my DMs are open.

P.P.P.S. I did a presentation at DDDBrisbane 2025 and created a slides component to be used live. You can find the presso in the /examples/talk folder in the repo.

Ready to pick a b0ne?

Top comments (0)