Skip to content

Commit 6cc7005

Browse files
authored
Debug signals (#681)
* Debugging signals * Batch thing * Tests * Notes * Massive improvement * Test with karma * Make the demo work * Collapse descending groups by default * Preact stuffz * Add changeset * Solve possible memory leak * More changesets * Move to a hook rather than prototype patching * Add some tests * Revert "Move to a hook rather than prototype patching" This reverts commit 23f69a4. * Readability refactor * Use _refresh for computed * Cleanup * Remove the preact names for now * options * Process feedback, hide INTERNAL_NAME and consistent object shape
1 parent eae850a commit 6cc7005

24 files changed

+897
-64
lines changed

.changeset/great-moons-bake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@preact/signals-debug": minor
3+
---
4+
5+
Initial release of `@preact/signals-debug`

.changeset/slimy-hounds-lie.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@preact/signals-core": minor
3+
"@preact/signals": minor
4+
"@preact/signals-react": minor
5+
---
6+
7+
Allow for naming your singals/computeds/effects

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Read the [announcement post](https://preactjs.com/blog/introducing-signals/) to
1414
- [`effect(fn)`](./packages/core/README.md#effectfn)
1515
- [`batch(fn)`](./packages/core/README.md#batchfn)
1616
- [`untracked(fn)`](./packages/core/README.md#untrackedfn)
17+
- [Debug Extennsion](./packages/debug/README.md#preact-integration)
1718
- [Preact Integration](./packages/preact/README.md#preact-integration)
1819
- [Hooks](./packages/preact/README.md#hooks)
1920
- [Rendering optimizations](./packages/preact/README.md#rendering-optimizations)

docs/demos/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { render } from "preact";
22
import { LocationProvider, Router, useLocation, lazy } from "preact-iso";
33
import { signal, useSignal } from "@preact/signals";
44
import { setFlashingEnabled, constrainFlashToChildren } from "./render-flasher";
5+
import "@preact/signals-debug";
56

67
// disable flashing during initial render:
78
setFlashingEnabled(false);
@@ -18,7 +19,6 @@ const demos = {
1819

1920
function Demos() {
2021
const demo = useLocation().path.replace(/^\/demos\/?/, "");
21-
2222
return (
2323
<div id="app">
2424
<header>
@@ -58,7 +58,7 @@ function displayName(name: string) {
5858
}
5959

6060
function Counter() {
61-
const count = useSignal(0);
61+
const count = useSignal(0, "counter");
6262

6363
return (
6464
<div class="card">
@@ -69,7 +69,7 @@ function Counter() {
6969
);
7070
}
7171

72-
const globalCount = signal(0);
72+
const globalCount = signal(0, "global-counter");
7373
function GlobalCounter({ explain = true }) {
7474
return (
7575
<>

docs/index.html

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<title>Docs</title>
5-
<link rel="shortcut icon" href="/favicon.ico" />
4+
<title>Demos</title>
5+
<link rel="stylesheet" href="./demos/style.css" />
66
</head>
77
<body>
8-
<h1>Docs</h1>
9-
<ul>
10-
<li><a href="/demos/">Demos</a></li>
11-
</ul>
8+
<div id="root"></div>
9+
<script type="module" src="./demos/index.tsx"></script>
1210
</body>
1311
</html>

docs/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
}
1313
},
1414
"dependencies": {
15-
"preact": "10.9.0",
15+
"preact": "^10.25.0",
1616
"preact-iso": "^2.3.0",
17-
"preact-render-to-string": "^5.2.1",
17+
"preact-render-to-string": "^6.0.0",
1818
"@preact/signals-core": "workspace:../packages/core",
19+
"@preact/signals-debug": "workspace:../packages/debug",
1920
"@preact/signals": "workspace:../packages/preact",
2021
"@preact/signals-react": "workspace:../packages/react",
2122
"react": "^18.2.0",

karma.conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ function createEsbuildPlugin(filteredPkgList) {
227227

228228
const pkgList = {
229229
core: "@preact/signals-core",
230+
debug: "@preact/signals-debug",
230231
preact: "@preact/signals",
231232
react: "@preact/signals-react",
232233
"react/utils": "@preact/signals-react/utils",

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
"private": true,
44
"scripts": {
55
"prebuild": "shx rm -rf packages/*/dist/",
6-
"build": "pnpm build:core && pnpm build:preact && pnpm build:preact-utils && pnpm build:react-runtime && pnpm build:react && pnpm build:react-transform && pnpm build:react-utils",
6+
"build": "pnpm build:core && pnpm build:debug && pnpm build:preact && pnpm build:preact-utils && pnpm build:react-runtime && pnpm build:react && pnpm build:react-transform && pnpm build:react-utils",
77
"_build": "microbundle --raw --globals @preact/signals-core=preactSignalsCore,preact/hooks=preactHooks,@preact/signals-react/runtime=reactSignalsRuntime",
88
"build:core": "pnpm _build --cwd packages/core && pnpm postbuild:core",
9+
"build:debug": "pnpm _build --cwd packages/debug && pnpm postbuild:debug",
910
"build:preact": "pnpm _build --cwd packages/preact && pnpm postbuild:preact",
1011
"build:preact-utils": "pnpm _build --cwd packages/preact/utils && pnpm postbuild:preact-utils",
1112
"build:react": "pnpm _build --cwd packages/react --external \"react,@preact/signals-react/runtime,@preact/signals-core\" && pnpm postbuild:react",
1213
"build:react-utils": "pnpm _build --cwd packages/react/utils && pnpm postbuild:react-utils",
1314
"build:react-runtime": "pnpm _build --cwd packages/react/runtime && pnpm postbuild:react-runtime",
1415
"build:react-transform": "pnpm _build --no-compress --cwd packages/react-transform",
1516
"postbuild:core": "cd packages/core/dist && shx mv -f index.d.ts signals-core.d.ts",
17+
"postbuild:debug": "cd packages/debug/dist && shx mv -f debug/src/index.d.ts signals-debug.d.ts",
1618
"postbuild:preact": "cd packages/preact/dist && shx mv -f preact/src/index.d.ts signals.d.ts && shx rm -rf preact",
1719
"postbuild:react": "cd packages/react/dist && shx mv -f react/src/index.d.ts signals.d.ts && shx rm -rf react",
1820
"postbuild:preact-utils": "cd packages/preact/utils/dist && shx mv -f preact/utils/src/index.d.ts . && shx rm -rf preact",

packages/core/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ You can also pass options to `signal()` and `computed()` to be notified when the
4949

5050
```js
5151
const counter = signal(0, {
52+
name: 'counter',
5253
watched: function () {
5354
console.log("Signal has its first subscriber");
5455
},
@@ -60,6 +61,8 @@ const counter = signal(0, {
6061

6162
These callbacks are useful for managing resources or side effects that should only be active when the signal has subscribers. For example, you might use them to start/stop expensive background operations or subscribe/unsubscribe from external event sources.
6263

64+
The `name` option will be used in the `@preact/signals-debug` package to provide meaningful names for the log output.
65+
6366
#### `signal.peek()`
6467

6568
In the rare instance that you have an effect that should write to another signal based on the previous value, but you _don't_ want the effect to be subscribed to that signal, you can read a signals's previous value via `signal.peek()`.

packages/core/src/index.ts

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,13 @@ function addDependency(signal: Signal): Node | undefined {
215215
/**
216216
* The base class for plain and computed signals.
217217
*/
218-
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
219218
//
220219
// A function with the same name is defined later, so we need to ignore TypeScript's
221220
// warning about a redeclared variable.
222221
//
223222
// The class is declared here, but later implemented with ES5-style prototypes.
224223
// This enables better control of the transpiled output size.
224+
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
225225
declare class Signal<T = any> {
226226
/** @internal */
227227
_value: unknown;
@@ -258,6 +258,8 @@ declare class Signal<T = any> {
258258

259259
subscribe(fn: (value: T) => void): () => void;
260260

261+
name?: string;
262+
261263
valueOf(): T;
262264

263265
toString(): string;
@@ -275,23 +277,24 @@ declare class Signal<T = any> {
275277
export interface SignalOptions<T = any> {
276278
watched?: (this: Signal<T>) => void;
277279
unwatched?: (this: Signal<T>) => void;
280+
name?: string;
278281
}
279282

280283
/** @internal */
281-
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
282-
//
283284
// A class with the same name has already been declared, so we need to ignore
284285
// TypeScript's warning about a redeclared variable.
285286
//
286287
// The previously declared class is implemented here with ES5-style prototypes.
287288
// This enables better control of the transpiled output size.
289+
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
288290
function Signal(this: Signal, value?: unknown, options?: SignalOptions) {
289291
this._value = value;
290292
this._version = 0;
291293
this._node = undefined;
292294
this._targets = undefined;
293295
this._watched = options?.watched;
294296
this._unwatched = options?.unwatched;
297+
this.name = options?.name;
295298
}
296299

297300
Signal.prototype.brand = BRAND_SYMBOL;
@@ -343,17 +346,19 @@ Signal.prototype._unsubscribe = function (node) {
343346
};
344347

345348
Signal.prototype.subscribe = function (fn) {
346-
return effect(() => {
347-
const value = this.value;
348-
349-
const prevContext = evalContext;
350-
evalContext = undefined;
351-
try {
352-
fn(value);
353-
} finally {
354-
evalContext = prevContext;
355-
}
356-
});
349+
return effect(
350+
() => {
351+
const value = this.value;
352+
const prevContext = evalContext;
353+
evalContext = undefined;
354+
try {
355+
fn(value);
356+
} finally {
357+
evalContext = prevContext;
358+
}
359+
},
360+
{ name: "sub" }
361+
);
357362
};
358363

359364
Signal.prototype.valueOf = function () {
@@ -539,6 +544,7 @@ function cleanupSources(target: Computed | Effect) {
539544
target._sources = head;
540545
}
541546

547+
/** @internal */
542548
declare class Computed<T = any> extends Signal<T> {
543549
_fn: () => T;
544550
_sources?: Node;
@@ -551,6 +557,7 @@ declare class Computed<T = any> extends Signal<T> {
551557
get value(): T;
552558
}
553559

560+
/** @internal */
554561
function Computed(this: Computed, fn: () => unknown, options?: SignalOptions) {
555562
Signal.call(this, undefined);
556563

@@ -560,6 +567,7 @@ function Computed(this: Computed, fn: () => unknown, options?: SignalOptions) {
560567
this._flags = OUTDATED;
561568
this._watched = options?.watched;
562569
this._unwatched = options?.unwatched;
570+
this.name = options?.name;
563571
}
564572

565573
Computed.prototype = new Signal() as Computed;
@@ -772,14 +780,16 @@ type EffectFn =
772780
| ((this: { dispose: () => void }) => void | (() => void))
773781
| (() => void | (() => void));
774782

783+
/** @internal */
775784
declare class Effect {
776785
_fn?: EffectFn;
777786
_cleanup?: () => void;
778787
_sources?: Node;
779788
_nextBatchedEffect?: Effect;
780789
_flags: number;
790+
name?: string;
781791

782-
constructor(fn: EffectFn);
792+
constructor(fn: EffectFn, options?: EffectOptions);
783793

784794
_callback(): void;
785795
_start(): () => void;
@@ -788,12 +798,18 @@ declare class Effect {
788798
dispose(): void;
789799
}
790800

791-
function Effect(this: Effect, fn: EffectFn) {
801+
export interface EffectOptions {
802+
name?: string;
803+
}
804+
805+
/** @internal */
806+
function Effect(this: Effect, fn: EffectFn, options?: EffectOptions) {
792807
this._fn = fn;
793808
this._cleanup = undefined;
794809
this._sources = undefined;
795810
this._nextBatchedEffect = undefined;
796811
this._flags = TRACKING;
812+
this.name = options?.name;
797813
}
798814

799815
Effect.prototype._callback = function () {
@@ -858,8 +874,8 @@ Effect.prototype.dispose = function () {
858874
* @param fn The effect callback.
859875
* @returns A function for disposing the effect.
860876
*/
861-
function effect(fn: EffectFn): { (): void; [Symbol.dispose](): void } {
862-
const effect = new Effect(fn);
877+
function effect(fn: EffectFn, options?: EffectOptions): () => void {
878+
const effect = new Effect(fn, options);
863879
try {
864880
effect._callback();
865881
} catch (err) {
@@ -873,4 +889,13 @@ function effect(fn: EffectFn): { (): void; [Symbol.dispose](): void } {
873889
return dispose as any;
874890
}
875891

876-
export { computed, effect, batch, untracked, Signal, ReadonlySignal };
892+
export {
893+
computed,
894+
effect,
895+
batch,
896+
untracked,
897+
Signal,
898+
ReadonlySignal,
899+
Effect,
900+
Computed,
901+
};

0 commit comments

Comments
 (0)