DEV Community

Cover image for bQuery.js Grows Up: From Tiny jQuery Tribute to a Full-Stack Framework β€” and a Brand New Home at bquery.js.org πŸŽ‰
Jonas Pfalzgraf
Jonas Pfalzgraf

Posted on

bQuery.js Grows Up: From Tiny jQuery Tribute to a Full-Stack Framework β€” and a Brand New Home at bquery.js.org πŸŽ‰

Hi everyone! πŸ‘‹

bQuery.js started last autumn with a question I couldn't stop chewing on: why was jQuery actually invented?

Strip away two decades of memes and "jQuery is dead" hot takes, and the answer is pretty simple. jQuery existed to make developer life easier under the conditions of its time β€” inconsistent browsers, a painful DOM API, no fetch, no querySelector, no module system worth speaking of. It papered over the platform because the platform needed papering.

So I started asking the modern version of that question: if you sat down today and tried to do for 2025+ what jQuery did for 2006, what would you actually build?

Not "jQuery, but updated". Not "jQuery, but TypeScript". An honest answer to: what are today's pain points, and what would a library look like that fixes them without adding new ones?

That question is where bQuery comes from. A few months later, it's not really a library anymore β€” it's a full-stack framework. And to celebrate that, the docs have officially moved into their own home:

πŸ₯‚ bquery.js.org β€” your new bookmark for everything bQuery.

Let me tell you how we got here.


πŸ“– Chapter 1 β€” Three Non-Negotiables

When I started sketching this thing last autumn, three principles were locked in from day one. Everything else had to bend around them.

1. Zero mandatory build step. You should be able to drop a single <script type="module"> into a plain HTML file and write something useful in 12 lines. No node_modules. No bundler config. No vibes-based tsconfig.json you cargo-culted from a tutorial. The platform is good now β€” the library shouldn't gatekeep you behind a toolchain.

2. Single import, with or without CDN. One entry point. You either install from npm/bun/pnpm, or you point at unpkg/jsDelivr. Either way, the API is identical. No "CDN edition" vs. "module edition" mind games.

3. Real tree-shaking. Importing bQuery shouldn't drag in the whole framework if you only wanted $. Every module is its own explicit entry point. You pay for exactly what you import β€” nothing more.

Those three rules made the design decisions almost automatic. They're also the reason it grew the way it did: when the API is honest about cost, you can keep adding without bloating anyone's bundle.

The first version looked exactly like you'd expect:

import { $, $$ } from '@bquery/bquery/core';

$('#button').on('click', () => {
  $$('.card').addClass('hovered');
});
Enter fullscreen mode Exit fullscreen mode

That's it. A typed, chainable, modern DOM helper. Zero build step required. Zero runtime dependencies. Roughly the energy of "jQuery, but it understands what unknown means".

It scratched the itch. People used it. The story could have ended there.

It didn't.


🌱 Chapter 2 β€” "Hm, this could use signals"

The first time the project stopped being a library was the day I added signals.

Once you have reactive primitives, suddenly $('#counter').text(...) isn't just a one-shot DOM write β€” it's an expression of state. And once you have that, you want components. And once you have components, you want a router. And once you have a router, you want SSR. And once you have SSR…

You see where this is going.

Crucially: none of that broke the three rules. Tree-shaking meant new modules didn't tax the people who only wanted $. The single-import contract held. The CDN entry still worked. Every release tried to keep the same jQuery-ish hand feel, but the surface kept growing:

  • Signals & effects for reactive state
  • Components built on the native Web Components API β€” no virtual DOM, no shipping a runtime renderer
  • Views & directives (bq-on, bq-html-safe, bq-memo, bq-cloak…) for declarative templates without JSX
  • Forms with validation, dirty/touched state, the whole thing
  • Router with guards, lazy routes, navigation results, and a proper useNavigation() composable
  • i18n with locale negotiation, RTL detection, relative time, and list formatting
  • A11y helpers β€” live regions, focus management, prefersReducedMotion, forced colors, scroll-lock, the lot
  • HTTP / fetch composables that plug straight into signals β€” polling, pagination, REST helpers, WebSocket and SSE
  • Drag & drop, including sortables, grid snapping, keyboard handling, viewport bounds
  • SSR with streaming, boundaries, edge handlers, response caching
  • Server β€” yes, a proper server runtime with middleware, cookies, SSE, streaming responses, app.listen()
  • Devtools, testing utilities, plugin system, media composables…

That's not a library. That's a framework. It took me a while to admit it out loud.


πŸ› οΈ Chapter 3 β€” The Three Rules, Still Holding

Here's the part I'm proudest of: bQuery grew into a framework without breaking the original promises.

You can still drop a single <script type="module"> into a plain HTML file and write 12 lines of code. You can also build a server-rendered app with streaming SSR, a typed router, signal-driven data fetching, and Web Components β€” using the same API surface. Same single import. Same tree-shaking. Same "no build step required, but works great if you want one".

// Minimal β€” just the DOM helpers
import { $, $$ } from '@bquery/bquery/core';

// Reactive
import { signal, computed, effect } from '@bquery/bquery/reactive';

// Whole kitchen sink
import {
  $, signal, component, registerDefaultComponents, defineBqueryConfig,
} from '@bquery/bquery';
Enter fullscreen mode Exit fullscreen mode

Explicit entry points. TypeScript-first. Truly tree-shakeable. Same vocabulary across the whole framework.

The CDN entry isn't a marketing afterthought β€” it's a core feature, in the same way the build-based install is. The moment you force people into a toolchain, you've already lost the "I just want to make a thing" crowd. And that crowd is sacred.

<script type="module">
  import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';

  const count = signal(0);
  $('#btn').on('click', () => count.value++);
  effect(() => $('#out').text(`Count: ${count.value}`));
</script>
Enter fullscreen mode Exit fullscreen mode

That's a reactive counter. No bundler. No node_modules. No vibes-based config file. Just the platform.


πŸ”’ Quick Aside: Security That Isn't Optional

If you remember the jQuery era, you also remember .html(userInput) shipping XSS into production. Hard pass.

bQuery sanitizes DOM writes by default and ships first-class helpers for Trusted Types and CSP. There's a bq-html-safe directive for templates that need to render markup explicitly. The defaults are the safe ones β€” you have to opt in to the dangerous thing, not opt out. That's the way it should have been all along.


πŸŽ‰ Chapter 4 β€” Welcome to bquery.js.org

Okay. The actual reason for this post.

The docs used to live wherever I could shove them β€” README, GitHub Pages under a long URL, scattered Markdown files. That worked when the surface was "here are 8 functions and a chain API". It does not work when the surface is a full framework with 20+ modules, hundreds of composables, SSR, a server runtime, and a plugin system.

So I did the obvious thing. The docs got their own home:

πŸ‘‰ bquery.js.org

What you'll find there:

  • A proper getting started path β€” from "drop a <script> tag" to "scaffold a full SSR app"
  • Module-by-module reference with every signal, composable, directive, and helper
  • A searchable API reference generated straight from the source
  • Migration notes if you've been riding the 1.x train
  • A changelog that doesn't try to hide breaking changes in a footnote

The domain change is also a small but real symbolic thing. bquery.js.org is on the js.org registry β€” a community-run home for JavaScript projects. It feels right. It's where bQuery belongs now that it's stopped pretending to be small.


πŸ“¦ Where We Are Today (v1.14.1)

For the people who like a quick state-of-the-union:

Area What's in it
Core $, $$, chainable DOM API, sane defaults
Reactive Signals, computed, effects, stores
Components Web Components-based, no VDOM runtime
Views Directives, bq-on modifier system, memoization
Router Guards, lazy routes, useNavigation, dynamic routes
Forms Validation, dirty/touched, field-level signals
HTTP Fetch composables, polling, pagination, REST, WebSocket, SSE
i18n Locale negotiation, RTL, relative time, list & display name formatting
A11y Live regions, focus-visible, forced-colors, reduced-motion, scroll-lock
Drag & Drop Sortables, grids, keyboard, viewport bounds
Media 25+ reactive composables for preferences, page state, observers, pointer/scroll, clipboard
SSR Streaming, boundaries, edge handlers, response caching
Server HTTP runtime, middleware, cookies, SSE, streaming responses
Plugin system Hook bus, DI container, namespaced directives, lifecycle
Devtools Timeline ring buffer, signal/store diffs, traces, snapshots, browser bridge
Testing Auto cleanup, userEvent, shadow-DOM-aware queries, module mocks, a11y helpers

All of that, and the 1.x API surface is still backwards compatible going back to 1.0. I'm a bit smug about that one ^^ .


πŸ€” But Is It Still… jQuery-y?

Yes. That's the whole point.

You can absolutely write a 2026 app using nothing but $('#thing').addClass(...).on('click', ...). The framework parts are opt-in. If you want to write tiny scripts, write tiny scripts. If you want to ship an SSR app with reactive forms and a typed server, that's the same install. Same imports. Same API conventions.

The whole thesis goes back to that original question β€” what would jQuery look like if it were designed today, for today's pain points? The platform is genuinely good now. We shouldn't have to choose between "ergonomic" and "uses what the browser already gives you". And we shouldn't have to choose between "powerful framework" and "drop a script tag and it works". bQuery is the bet that you can have all of it, in one import.


πŸ™Œ Contribute, Star, Yell at Me

bQuery is open source under the same spirit as everything else I publish. If you want to help:

  • ⭐ Star the GitHub repo β€” it does more than you'd think
  • πŸ› Open issues when things break
  • ✨ PRs are welcome, especially for the parts you actually use
  • πŸ“ Docs improvements over at bquery.js.org are very welcome β€” even a typo fix is a typo someone else won't trip over

If you'd previously written off "jQuery-style" as a museum piece, this might be a good moment to look again. The vibes are familiar. The internals are not.


🎯 TL;DR

  • bQuery.js started last autumn with one question: what would jQuery look like if you designed it today, for today's pain points?
  • Three rules locked in from day one: zero mandatory build step, single import (CDN or npm), real tree-shaking
  • It has since grown into a full-stack framework β€” signals, components, SSR, routing, a server runtime, forms, i18n, a11y, devtools, testing utilities β€” without breaking any of those rules
  • πŸŽ‰ New docs home: bquery.js.org β€” go bookmark it
  • Latest release is v1.14.1, backwards compatible all the way back to 1.0

The jQuery for the modern web platform is no longer just jQuery for the modern web platform.

Cheers, and see you on bquery.js.org. πŸ₯‚

β€” Josun

Top comments (0)