Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
95db6ce
Updated CHANGELOG and package.json
hexplus Mar 28, 2026
56080d8
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 28, 2026
7eeec49
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 28, 2026
14a9cd4
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
9487727
ci: use npm install instead of npm ci
hexplus Mar 29, 2026
6b4bd83
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
0b9a0cc
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
0777184
trusted-publisher
hexplus Mar 29, 2026
4d46e82
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
bea9788
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
825a8dc
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
55c4436
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
0d2c7e0
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
8da81e8
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
325ce5d
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Mar 29, 2026
0cad329
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 1, 2026
aea6787
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 4, 2026
00e5e88
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 7, 2026
b10a2c5
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 7, 2026
639eae0
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 9, 2026
405e4fe
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 11, 2026
ee7cf48
Updated main
hexplus Apr 11, 2026
8c77fca
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 11, 2026
da6d752
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 11, 2026
c047837
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 12, 2026
a52fffc
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 12, 2026
43b5675
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 14, 2026
44df880
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 18, 2026
aba311a
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 19, 2026
4bf3286
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Apr 19, 2026
a086428
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus May 29, 2026
e316ae0
Missing update package.json
hexplus May 29, 2026
278bfc6
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus May 29, 2026
08bc9b8
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Jun 1, 2026
7b5557d
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Jun 5, 2026
226ae51
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Jun 5, 2026
0d5c8de
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Jun 12, 2026
b0053ef
Merge branch 'main' of https://github.com/hexplus/SibuJS
hexplus Jun 12, 2026
e808e26
Reactivity survives a duplicated runtime resilience
hexplus Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ This project follows [Semantic Versioning](https://semver.org/).

---

## [3.3.1] — 2026-06-26

A robustness release. No breaking changes; no API or behavior change for normal usage.

### Fixed

- **Reactivity survives a duplicated runtime** — when a bundler materializes the reactive-core module more than once on a page (e.g. dependency pre-bundling in Vite/esbuild, which can serve the same internal chunk twice — once with an `?v=<hash>` query and once raw), `signal()` writes and reactive bindings landed in two independent worlds: a write notified one copy's queue while a binding had tracked itself through the other's. The binding then silently stopped updating, with no error thrown. The runtime now routes every duplicate copy through the first one loaded on the page, so reactivity keeps working regardless of how many copies a bundler emits — and the hot path is unchanged, so single-instance performance is identical. Raw ESM usage was never affected and is unchanged.

### Added

- **Dev warning for duplicated runtimes** — in development, loading a second copy of the reactive runtime now logs a one-time, actionable warning explaining how to de-duplicate (e.g. Vite `optimizeDeps.exclude: ['sibujs']` or `resolve.dedupe: ['sibujs']`). The warning is stripped from production builds.

---

## [3.3.0] — 2026-06-11

A security-hardening, correctness, and performance release. No breaking changes.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sibujs",
"version": "3.3.0",
"version": "3.3.1",
"description": "A lightweight, function-based frontend framework that combines the best of React, Svelte, and Vue — with zero VDOM and maximum simplicity. Designed for developers who want fine-grained reactivity and full control without compilation or magic.",
"keywords": [
"frontend",
Expand Down
4 changes: 2 additions & 2 deletions src/core/signals/derived.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactiveSignal } from "../../reactivity/signal";
import { recordDependency, retrack, track, trackingSuspended } from "../../reactivity/track";
import { isTrackingSuspended, recordDependency, retrack, track } from "../../reactivity/track";
import { devAssert } from "../dev";
import type { Accessor } from "./signal";

Expand Down Expand Up @@ -101,7 +101,7 @@ export function derived<T>(
);
}

if (trackingSuspended) {
if (isTrackingSuspended()) {
if (cs._d) {
const prev = cs._v;
evaluating = true;
Expand Down
40 changes: 37 additions & 3 deletions src/reactivity/batch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { ReactiveSignal } from "./signal";
import { drainNotificationQueue, queueSignalNotification } from "./track";

// Batch coordination state is plain module-local — the same first-copy-wins
// function-sharing as track.ts (see its file header) keeps it a single source
// of truth across duplicate runtime instances: the first copy publishes its
// batch functions on a globalThis registry and every later copy re-exports
// them, so all copies funnel through ONE copy's batchDepth / pendingSignals.
let batchDepth = 0;
const pendingSignals = new Set<ReactiveSignal>();

Expand All @@ -26,7 +31,7 @@ const pendingSignals = new Set<ReactiveSignal>();
* }); // Only one notification pass, result === "done"
* ```
*/
export function batch<T>(fn: () => T): T {
function batchImpl<T>(fn: () => T): T {
batchDepth++;
try {
return fn();
Expand All @@ -42,7 +47,7 @@ export function batch<T>(fn: () => T): T {
* Queue a signal for deferred notification during a batch.
* If not batching, returns false so the caller can notify immediately.
*/
export function enqueueBatchedSignal(signal: ReactiveSignal): boolean {
function enqueueBatchedSignalImpl(signal: ReactiveSignal): boolean {
if (batchDepth === 0) return false;
pendingSignals.add(signal);
return true;
Expand All @@ -51,7 +56,7 @@ export function enqueueBatchedSignal(signal: ReactiveSignal): boolean {
/**
* Check if we're currently inside a batch.
*/
export function isBatching(): boolean {
function isBatchingImpl(): boolean {
return batchDepth > 0;
}

Expand All @@ -74,3 +79,32 @@ function flushBatch(): void {
}
drainNotificationQueue();
}

// ---------- Shared-instance registry (see track.ts) -----------------------

interface BatchApi {
batch: typeof batchImpl;
enqueueBatchedSignal: typeof enqueueBatchedSignalImpl;
isBatching: typeof isBatchingImpl;
}

const BATCH_REGISTRY_KEY = Symbol.for("sibujs.reactive.batch.v1");

function resolveBatchApi(): BatchApi {
const g = globalThis as typeof globalThis & { [BATCH_REGISTRY_KEY]?: BatchApi };
const existing = g[BATCH_REGISTRY_KEY];
if (existing) return existing;
const local: BatchApi = {
batch: batchImpl,
enqueueBatchedSignal: enqueueBatchedSignalImpl,
isBatching: isBatchingImpl,
};
g[BATCH_REGISTRY_KEY] = local;
return local;
}

const API: BatchApi = resolveBatchApi();

export const batch: BatchApi["batch"] = API.batch;
export const enqueueBatchedSignal: BatchApi["enqueueBatchedSignal"] = API.enqueueBatchedSignal;
export const isBatching: BatchApi["isBatching"] = API.isBatching;
Loading
Loading