Skip to content

bQuery/bQuery

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

190 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bQuery.js Logo

bQuery.js

Repo Stars Issues License npm Bundle Size unpkg CodeFactor JsDelivr

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, async data composables, Web Components, motion utilities, routing, stores, declarative views, accessibility helpers, forms, i18n, media signals, drag-and-drop, plugins, devtools, testing utilities, and SSR — without a mandatory build step.

Highlights

  • Zero-build capable: runs directly in the browser; build tools are optional.
  • Async data built-in: fetch and async state composables integrate directly with signals.
  • 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.
  • Storybook-ready: default components can be previewed and developed in Storybook with dedicated story template helpers.

Installation

Via npm/bun/pnpm

# npm
npm install @bquery/bquery

# bun
bun add @bquery/bquery

# pnpm
pnpm add @bquery/bquery

Via 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,
  registerDefaultComponents,
  defineBqueryConfig,
} 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';

// Reactive only
import {
  signal,
  computed,
  effect,
  linkedSignal,
  persistedSignal,
  useAsyncData,
  useFetch,
  createUseFetch,
} from '@bquery/bquery/reactive';

// Components only
import {
  bool,
  component,
  defineComponent,
  html,
  registerDefaultComponents,
} from '@bquery/bquery/component';

// Motion only
import { transition, spring, animate, timeline } from '@bquery/bquery/motion';

// Security only
import { sanitize, sanitizeHtml, trusted } from '@bquery/bquery/security';

// Platform only
import { storage, cache, useCookie, definePageMeta, useAnnouncer } from '@bquery/bquery/platform';

// Router, Store, View
import { createRouter, navigate } from '@bquery/bquery/router';
import { createStore, defineStore } from '@bquery/bquery/store';
import { mount, createTemplate } from '@bquery/bquery/view';

// Forms, i18n, accessibility, drag & drop, media
import { createForm, required, email } from '@bquery/bquery/forms';
import { createI18n } from '@bquery/bquery/i18n';
import { trapFocus, rovingTabIndex } from '@bquery/bquery/a11y';
import { draggable, droppable, sortable } from '@bquery/bquery/dnd';
import { mediaQuery, useViewport, clipboard } from '@bquery/bquery/media';

// Plugins, devtools, testing, SSR
import { use } from '@bquery/bquery/plugin';
import { enableDevtools, inspectSignals } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent, waitFor } from '@bquery/bquery/testing';
import { renderToString, hydrateMount, serializeStoreState } from '@bquery/bquery/ssr';

// Storybook helpers
import { storyHtml, when } from '@bquery/bquery/storybook';

Modules at a glance

Module Description
Core Selectors, DOM manipulation, events, traversal, and typed utilities
Reactive signal, computed, effect, batching, async data, and fetch composables
Component Typed Web Components with scoped reactivity and configurable Shadow DOM
Storybook Safe story template helpers with boolean-attribute shorthand
Motion View transitions, FLIP, morphing, parallax, typewriter, springs, and timelines
Security HTML sanitization, Trusted Types, CSP helpers, and trusted fragment composition
Platform Storage, cache, cookies, page metadata, announcers, and shared runtime config
Router SPA routing, constrained params, redirects, guards, useRoute(), and <bq-link>
Store Signal-based state management, persistence, migrations, and action hooks
View Declarative DOM bindings, directives, and plugin-provided custom directives
Forms Reactive form state with sync/async validation and submit handling
i18n Reactive locales, interpolation, pluralization, lazy loading, and Intl formatting
A11y Focus traps, live-region announcements, roving tabindex, skip links, and audits
DnD Draggable elements, droppable zones, and sortable lists
Media Reactive browser/device signals for viewport, network, battery, geolocation, and more
Plugin Global plugin registration for custom directives and Web Components
Devtools Runtime inspection helpers for signals, stores, components, and timelines
Testing Component mounting, mock signals/router helpers, and async test utilities
SSR Server-side rendering, hydration, and store-state serialization

Storybook authoring helpers are also available as a dedicated entry point via @bquery/bquery/storybook.

Quick examples

Core – DOM & events

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

$('#save').on('click', (event) => {
  console.log('Saved', event.type);
});

$('#list').delegate('click', '.item', (event, target) => {
  console.log('Item clicked', target.textContent);
});

$('#box').addClass('active').css({ opacity: '0.8' }).attr('data-state', 'ready');

const color = $('#box').css('color');

if ($('#el').is('.active')) {
  console.log('Element is active');
}

$$('.container').find('.item').addClass('found');

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(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`);
});

const readOnlyCount = readonly(count);

batch(() => {
  count.value++;
  count.value++;
});

count.dispose();

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';

Reactive – async data & fetch

import { signal, useFetch, createUseFetch } from '@bquery/bquery/reactive';

const userId = signal(1);

const user = useFetch<{ id: number; name: string }>(() => `/users/${userId.value}`, {
  baseUrl: 'https://api.example.com',
  watch: [userId],
  query: { include: 'profile' },
});

const useApiFetch = createUseFetch({
  baseUrl: 'https://api.example.com',
  headers: { 'x-client': 'bquery-readme' },
});

const settings = useApiFetch<{ theme: string }>('/settings');

console.log(user.pending.value, user.data.value, settings.error.value);

Components – Web Components

import {
  bool,
  component,
  defineComponent,
  html,
  registerDefaultComponents,
  safeHtml,
} from '@bquery/bquery/component';
import { sanitizeHtml, trusted } from '@bquery/bquery/security';

const badge = trusted(sanitizeHtml('<span class="badge">Active</span>'));

component('user-card', {
  props: {
    username: { type: String, required: true },
    age: { type: Number, validator: (v) => v >= 0 && v <= 150 },
  },
  state: { count: 0 },
  beforeMount() {
    console.log('About to mount');
  },
  connected() {
    console.log('Mounted');
  },
  beforeUpdate(newProps, oldProps) {
    return newProps.username !== oldProps.username;
  },
  updated(change) {
    console.log('Updated because of', change?.name ?? 'state/signal change');
  },
  onError(error) {
    console.error('Component error:', error);
  },
  render({ props, state }) {
    return safeHtml`
      <button class="user-card" ${bool('disabled', state.count > 3)}>
        ${badge}
        <span>Hello ${props.username}</span>
      </button>
    `;
  },
});

const UserCard = defineComponent('user-card-manual', {
  props: { username: { type: String, required: true } },
  render: ({ props }) => html`<div>Hello ${props.username}</div>`,
});

customElements.define('user-card-manual', UserCard);

const tags = registerDefaultComponents({ prefix: 'ui' });
console.log(tags.button); // ui-button

Storybook – authoring helpers

import { storyHtml, when } from '@bquery/bquery/storybook';

export const Primary = {
  args: { disabled: false, label: 'Save' },
  render: ({ disabled, label }) =>
    storyHtml`
      <ui-card>
        <ui-button ?disabled=${disabled}>${label}</ui-button>
        ${when(!disabled, '<small>Ready to submit</small>')}
      </ui-card>
    `,
};

Motion – animations

import { animate, keyframePresets, spring, transition } from '@bquery/bquery/motion';

await transition({
  update: () => {
    $('#content').text('Updated');
  },
  classes: ['page-transition'],
  types: ['navigation'],
  skipOnReducedMotion: true,
});

await animate(card, {
  keyframes: keyframePresets.pop(),
  options: { duration: 240, easing: 'ease-out' },
});

const x = spring(0, { stiffness: 120, damping: 14 });
x.onChange((value) => {
  element.style.transform = `translateX(${value}px)`;
});
await x.to(100);

Security – sanitizing

import { sanitize, escapeHtml, sanitizeHtml, trusted } from '@bquery/bquery/security';
import { safeHtml } from '@bquery/bquery/component';

const safeMarkup = sanitize(userInput);
const safe = sanitize('<form id="cookie">...</form>');
const urlSafe = sanitize('<a href="java\u200Bscript:alert(1)">click</a>');
const secureLink = sanitize('<a href="https://external.com" target="_blank">Link</a>');
const safeSrcset = sanitize('<img srcset="safe.jpg 1x, javascript:alert(1) 2x">');
const safeForm = sanitize('<form action="javascript:alert(1)">...</form>');
const escaped = escapeHtml('<script>alert(1)</script>');
const icon = trusted(sanitizeHtml('<span class="icon">♥</span>'));
const button = safeHtml`<button>${icon}<span>Save</span></button>`;

Platform – config, cookies & accessibility

import {
  defineBqueryConfig,
  useCookie,
  definePageMeta,
  useAnnouncer,
  storage,
  notifications,
} from '@bquery/bquery/platform';

defineBqueryConfig({
  fetch: { baseUrl: 'https://api.example.com' },
  transitions: { skipOnReducedMotion: true, classes: ['page-transition'] },
  components: { prefix: 'ui' },
});

const theme = useCookie<'light' | 'dark'>('theme', { defaultValue: 'light' });
const cleanupMeta = definePageMeta({ title: 'Dashboard' });
const announcer = useAnnouncer();

theme.value = 'dark';
announcer.announce('Preferences saved');
cleanupMeta();

const local = storage.local();
await local.set('theme', theme.value);

const permission = await notifications.requestPermission();
if (permission === 'granted') {
  notifications.send('Build complete', {
    body: 'Your docs are ready.',
  });
}

Router – SPA navigation

import { effect } from '@bquery/bquery/reactive';
import { createRouter, navigate, currentRoute } from '@bquery/bquery/router';

const router = createRouter({
  routes: [
    { path: '/', name: 'home', component: HomePage },
    { path: '/user/:id', name: 'user', component: UserPage },
    { path: '*', component: NotFound },
  ],
});

router.beforeEach(async (to) => {
  if (to.path === '/admin' && !isAuthenticated()) {
    await navigate('/login');
    return false;
  }
});

effect(() => {
  console.log('Current path:', currentRoute.value.path);
});

Forms – reactive validation

import { createForm, email, required } from '@bquery/bquery/forms';

const form = createForm({
  fields: {
    name: { initialValue: '', validators: [required()] },
    email: { initialValue: '', validators: [required(), email()] },
  },
  onSubmit: async (values) => {
    await fetch('/api/signup', {
      method: 'POST',
      body: JSON.stringify(values),
    });
  },
});

await form.handleSubmit();
console.log(form.isValid.value, form.fields.email.error.value);

i18n – locale-aware content

import { createI18n } from '@bquery/bquery/i18n';

const i18n = createI18n({
  locale: 'en',
  fallbackLocale: 'en',
  messages: {
    en: { greeting: 'Hello, {name}!' },
    de: { greeting: 'Hallo, {name}!' },
  },
});

console.log(i18n.t('greeting', { name: 'Ada' }));
i18n.$locale.value = 'de';

Accessibility, media, and drag & drop

import { trapFocus, announceToScreenReader } from '@bquery/bquery/a11y';
import { mediaQuery, useViewport } from '@bquery/bquery/media';
import { draggable } from '@bquery/bquery/dnd';

const modalTrap = trapFocus(document.querySelector('#dialog')!);
announceToScreenReader('Dialog opened');

const isDark = mediaQuery('(prefers-color-scheme: dark)');
const viewport = useViewport();
const drag = draggable(document.querySelector('#card')!, { bounds: 'parent' });

console.log(isDark.value, viewport.value.width);

drag.destroy();
modalTrap.release();

Plugins, devtools, testing, and SSR

import { use } from '@bquery/bquery/plugin';
import { enableDevtools, getTimeline } from '@bquery/bquery/devtools';
import { renderComponent, fireEvent } from '@bquery/bquery/testing';
import { renderToString } from '@bquery/bquery/ssr';

use({
  name: 'focus-plugin',
  install(ctx) {
    ctx.directive('focus', (el) => (el as HTMLElement).focus());
  },
});

enableDevtools(true, { logToConsole: true });
console.log(getTimeline());

const mounted = renderComponent('ui-button', { props: { variant: 'primary' } });
fireEvent(mounted.el, 'click');

const { html } = renderToString('<p bq-text="label"></p>', { label: 'Hello SSR' });
console.log(html);

mounted.unmount();

Store – state management

import {
  createStore,
  createPersistedStore,
  defineStore,
  mapGetters,
  watchStore,
} from '@bquery/bquery/store';

const counterStore = createStore({
  id: 'counter',
  state: () => ({ count: 0, name: 'Counter' }),
  getters: {
    doubled: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++;
    },
  },
});

const settingsStore = createPersistedStore({
  id: 'settings',
  state: () => ({ theme: 'dark', language: 'en' }),
});

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

import { mount, createTemplate } from '@bquery/bquery/view';
import { signal } from '@bquery/bquery/reactive';

const count = signal(0);
const items = signal(['Apple', 'Banana', 'Cherry']);

mount('#app', {
  count,
  items,
  increment: () => count.value++,
});

Browser Support

Browser Version Support
Chrome 90+ ✅ Full
Firefox 90+ ✅ Full
Safari 15+ ✅ Full
Edge 90+ ✅ Full

No IE support by design.

Documentation

Local Development

# Install dependencies
bun install

# Start VitePress docs
bun run dev

# Run Storybook
bun run storybook

# Run tests
bun test

# Build library
bun run build

# Build docs
bun run build:docs

# Generate API documentation
bun run docs:api

Project Structure

bQuery.js
├── src/
│   ├── core/       # Selectors, DOM ops, events, utils
│   ├── reactive/   # Signals, computed, effects, async data
│   ├── component/  # Web Components helper + default library
│   ├── storybook/  # Story template helpers
│   ├── motion/     # View transitions, FLIP, springs
│   ├── security/   # Sanitizer, CSP, Trusted Types
│   ├── platform/   # Storage, cache, cookies, meta, config
│   ├── router/     # SPA routing, navigation guards
│   ├── store/      # State management, persistence
│   ├── view/       # Declarative DOM bindings
│   ├── forms/      # Reactive forms + validators
│   ├── i18n/       # Internationalization + formatting
│   ├── a11y/       # Accessibility utilities
│   ├── dnd/        # Drag & drop helpers
│   ├── media/      # Browser and device reactive signals
│   ├── plugin/     # Global plugin system
│   ├── devtools/   # Runtime inspection helpers
│   ├── testing/    # Test utilities
│   └── ssr/        # Server-side rendering + hydration
├── docs/           # VitePress documentation
├── .storybook/     # Storybook config
├── stories/        # Component stories
├── tests/          # bun:test suites
└── dist/           # Built files (ESM, UMD, IIFE)

Contributing

See CONTRIBUTING.md for guidelines.

AI Agent Support

This project provides dedicated context files for AI coding agents:

License

MIT – See LICENSE.md for details.

About

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.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

 
 
 

Contributors

Languages