@bquery/bquery
v1.3.0
Published
The jQuery for the Modern Web Platform - Zero build, TypeScript-first library with reactivity, components, and motion
Maintainers
Readme
The jQuery for the modern Web Platform.
bQuery.js is a slim, TypeScript-first library that combines jQuery's direct DOM workflow with modern features like reactivity, Web Components, and motion utilities — without a mandatory build step.
Highlights
- Zero‑build capable: runs directly in the browser; build tools are optional.
- Security‑focused: DOM writes are sanitized by default; Trusted Types supported.
- Modular: the core stays small; extra modules are opt‑in.
- TypeScript‑first: clear types and strong IDE support.
- Tree-shakeable: import only what you need.
Installation
Via npm/bun/pnpm
# npm
npm install @bquery/bquery
# bun
bun add @bquery/bquery
# pnpm
pnpm add @bquery/bqueryVia CDN (Zero-build)
ES Modules (recommended)
<script type="module">
import { $, signal } from 'https://unpkg.com/@bquery/bquery@1/dist/full.es.mjs';
const count = signal(0);
$('#counter').text(`Count: ${count.value}`);
</script>UMD (global variable)
<script src="https://unpkg.com/@bquery/bquery@1/dist/full.umd.js"></script>
<script>
const { $, signal } = bQuery;
const count = signal(0);
</script>IIFE (self-executing)
<script src="https://unpkg.com/@bquery/bquery@1/dist/full.iife.js"></script>
<script>
const { $, $$ } = bQuery;
$$('.items').addClass('loaded');
</script>Import Strategies
// Full bundle (all modules)
import { $, signal, component } from '@bquery/bquery';
// Core only
import { $, $$ } from '@bquery/bquery/core';
// Core utilities (named exports, tree-shakeable)
import { debounce, merge, uid, once, utils } from '@bquery/bquery/core';
// À la carte (individual modules)
import { signal, computed, effect, linkedSignal, persistedSignal } from '@bquery/bquery/reactive';
import { component, defineComponent, html } from '@bquery/bquery/component';
import { transition, spring, animate, timeline } from '@bquery/bquery/motion';
import { sanitize } from '@bquery/bquery/security';
import { storage, cache } from '@bquery/bquery/platform';
import { createRouter, navigate } from '@bquery/bquery/router';
import { createStore, defineStore } from '@bquery/bquery/store';
import { mount, createTemplate } from '@bquery/bquery/view';Modules at a glance
| Module | Description | Size (gzip) |
| ------------- | -------------------------------------------------- | ----------- |
| Core | Selectors, DOM manipulation, events, utilities | ~11.3 KB |
| Reactive | signal, computed, effect, batch | ~0.3 KB |
| Component | Lightweight Web Components with props | ~1.9 KB |
| Motion | View transitions, FLIP, timelines, scroll, springs | ~4.0 KB |
| Security | HTML sanitizing, Trusted Types, CSP | ~0.7 KB |
| Platform | Storage, cache, notifications, buckets | ~2.2 KB |
| Router | SPA routing, navigation guards, history API | ~2.2 KB |
| Store | Signal-based state management, persistence | ~0.3 KB |
| View | Declarative DOM bindings, directives | ~4.3 KB |
Quick examples
Core – DOM & events
import { $, $$ } from '@bquery/bquery/core';
// jQuery-style selectors
$('#save').on('click', (event) => {
console.log('Saved', event.type);
});
// Event delegation for dynamic content
$('#list').delegate('click', '.item', (event, target) => {
console.log('Item clicked', target.textContent);
});
// Method chaining
$('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');
// DOM manipulation
$('#content').wrap('div');
$('#content').unwrap(); // Remove parent wrapper
// Attribute helpers
$('#dialog').toggleAttr('open');
// Smooth scrolling
$('#section').scrollTo({ behavior: 'smooth' });
// Form serialization
const formData = $('form').serialize(); // Returns object
const queryString = $('form').serializeString(); // Returns URL-encoded string
// Collections
$$('.items').addClass('highlight');
$$('.items').append('<li class="item">New</li>');Reactive – signals
import {
signal,
computed,
effect,
batch,
watch,
readonly,
linkedSignal,
} from '@bquery/bquery/reactive';
const count = signal(0);
const doubled = computed(() => count.value * 2);
effect(() => {
console.log('Count changed', count.value);
});
// Watch with cleanup support
const stop = watch(count, (newVal, oldVal) => {
console.log(`Changed from ${oldVal} to ${newVal}`);
});
// Read-only signal wrapper
const readOnlyCount = readonly(count);
// Batch updates for performance
batch(() => {
count.value++;
count.value++;
});
// Writable computed (linked signal)
const first = signal('Ada');
const last = signal('Lovelace');
const fullName = linkedSignal(
() => `${first.value} ${last.value}`,
(next) => {
const [nextFirst, nextLast] = next.split(' ');
first.value = nextFirst ?? '';
last.value = nextLast ?? '';
}
);
fullName.value = 'Grace Hopper';Components – Web Components
import { component, defineComponent, html } from '@bquery/bquery/component';
component('user-card', {
props: {
username: { type: String, required: true },
age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
},
// Extended lifecycle hooks
beforeMount() {
console.log('About to mount');
},
connected() {
console.log('Mounted');
},
beforeUpdate(props) {
// Return false to prevent update
return props.username !== '';
},
onError(error) {
console.error('Component error:', error);
},
render({ props }) {
return html`<div>Hello ${props.username}</div>`;
},
});
// Optional: create the class without auto-registration
const UserCard = defineComponent('user-card', {
props: { username: { type: String, required: true } },
render: ({ props }) => html`<div>Hello ${props.username}</div>`,
});
customElements.define('user-card', UserCard);Motion – animations
import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';
// View transitions (with fallback)
await transition(() => {
$('#content').text('Updated');
});
// Web Animations helper
await animate(card, {
keyframes: keyframePresets.pop(),
options: { duration: 240, easing: 'ease-out' },
});
// Spring physics
const x = spring(0, { stiffness: 120, damping: 14 });
x.onChange((value) => {
element.style.transform = `translateX(${value}px)`;
});
await x.to(100);Security – sanitizing
Internally modularized (sanitize core, Trusted Types, CSP helpers) — the public API remains unchanged. For legacy deep imports, @bquery/bquery/security/sanitize also re-exports generateNonce() and isTrustedTypesSupported().
import { sanitize, escapeHtml } from '@bquery/bquery/security';
// Sanitize HTML (removes dangerous elements like script, iframe, svg)
const safeHtml = sanitize(userInput);
// DOM clobbering protection (reserved IDs are blocked)
const safe = sanitize('<form id="cookie">...</form>'); // id stripped
// Unicode bypass protection in URLs
const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
// Automatic link security (adds rel="noopener noreferrer" to external/target="_blank" links)
const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
// Escape for text display
const escaped = escapeHtml('<script>alert(1)</script>');Platform – storage & APIs
import { storage, notifications } from '@bquery/bquery/platform';
// Unified storage API
const local = storage.local();
await local.set('theme', 'dark');
const theme = await local.get<string>('theme');
// Notifications
const permission = await notifications.requestPermission();
if (permission === 'granted') {
notifications.send('Build complete', {
body: 'Your docs are ready.',
});
}Router – SPA navigation
Internally, the router has been split into focused submodules (matching, navigation, state, links, utilities) with no public API changes.
import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';
// Create router with routes
const router = createRouter({
routes: [
{ path: '/', name: 'home', component: HomePage },
{ path: '/user/:id', name: 'user', component: UserPage },
{ path: '*', component: NotFound },
],
});
// Navigation guards
router.beforeEach(async (to, from) => {
if (to.path === '/admin' && !isAuthenticated()) {
await navigate('/login'); // Redirect
return false; // Cancel original navigation
}
});
// Navigate programmatically
await navigate('/user/42');
await navigate('/search?q=bquery'); // Query params in path
await navigate('/login', { replace: true }); // Replace history entry
// Reactive current route
effect(() => {
console.log('Current path:', currentRoute.value.path);
});Store – state management
import { createStore, createPersistedStore } from '@bquery/bquery/store';
// Create a store (returns the store instance directly)
const counterStore = createStore({
id: 'counter',
state: () => ({ count: 0, name: 'Counter' }),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
async fetchCount() {
this.count = await api.getCount();
},
},
});
// Use the store
counterStore.increment();
console.log(counterStore.doubled); // Reactive getter
// Persisted store (localStorage)
const settingsStore = createPersistedStore({
id: 'settings',
state: () => ({ theme: 'dark', language: 'en' }),
});
// Factory-style store definition (Pinia-style)
import { defineStore, mapGetters, watchStore } from '@bquery/bquery/store';
const useCounter = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubled: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});
const counter = useCounter();
const getters = mapGetters(counter, ['doubled']);
watchStore(
counter,
(state) => state.count,
(value) => {
console.log('Count changed:', value, getters.doubled);
}
);View – declarative bindings
Internally modularized into focused submodules; the public API remains unchanged.
import { mount, createTemplate } from '@bquery/bquery/view';
import { signal } from '@bquery/bquery/reactive';
// Mount reactive bindings to DOM
const count = signal(0);
const items = signal(['Apple', 'Banana', 'Cherry']);
const app = mount('#app', {
count,
items,
increment: () => count.value++,
});
// In HTML:
// <p bq-text="count"></p>
// <button bq-on:click="increment">+1</button>
// <ul><li bq-for="item in items" bq-text="item"></li></ul>
// <input bq-model="count" type="number" />
// <div bq-if="count > 5">Count is high!</div>
// <div bq-class="{ active: count > 0 }"></div>Browser Support
| Browser | Version | Support | | ------- | ------- | ------- | | Chrome | 90+ | ✅ Full | | Firefox | 90+ | ✅ Full | | Safari | 15+ | ✅ Full | | Edge | 90+ | ✅ Full |
No IE support by design.
Documentation
- Getting Started: docs/guide/getting-started.md
- Core API: docs/guide/api-core.md
- Agents: docs/guide/agents.md
- Components: docs/guide/components.md
- Reactivity: docs/guide/reactive.md
- Motion: docs/guide/motion.md
- Security: docs/guide/security.md
- Platform: docs/guide/platform.md
- Router: docs/guide/router.md
- Store: docs/guide/store.md
- View: docs/guide/view.md
Local Development
# Install dependencies
bun install
# Start VitePress docs
bun run dev
# Run Vite playground
bun run playground
# Run tests
bun test
# Build library
bun run build
# Generate API documentation
bun run docs:apiProject Structure
bQuery.js
├── src/
│ ├── core/ # Selectors, DOM ops, events, utils
│ ├── reactive/ # Signals, computed, effects
│ ├── component/ # Web Components helper
│ ├── motion/ # View transitions, FLIP, springs
│ ├── security/ # Sanitizer, CSP, Trusted Types
│ ├── platform/ # Storage, cache, notifications
│ ├── router/ # SPA routing, navigation guards
│ ├── store/ # State management, persistence
│ └── view/ # Declarative DOM bindings
├── docs/ # VitePress documentation
├── playground/ # Vite demo app
├── tests/ # bun:test suites
└── dist/ # Built files (ESM, UMD, IIFE)Contributing
See CONTRIBUTING.md for guidelines.
License
MIT – See LICENSE.md for details.
