1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-01 19:55:53 +02:00

🔤 adds debugging

This commit is contained in:
Jan Andrle 2025-03-04 15:31:21 +01:00
parent 56232a9f64
commit 4edc509646
8 changed files with 384 additions and 9 deletions

View File

@ -0,0 +1,14 @@
// Debugging a (derived) signal with `console.log`
import { S } from "deka-dom-el/signals";
const name= S("Alice");
const greeting = S(() => {
// log derived signals
const log = "Hello, " + name.get();
console.log(log);
return log;
});
// log signals in general
S.on(greeting, value => console.log("Greeting changed to:", value));
name.set("Bob"); // Should trigger computation and listener`)

View File

@ -0,0 +1,15 @@
import { S } from "deka-dom-el/signals";
// Debouncing signal updates
function debounce(func, wait) {
let timeout;
return (...args)=> {
clearTimeout(timeout);
timeout= setTimeout(() => func(...args), wait);
};
}
const inputSignal= S("");
const debouncedSet= debounce(value => inputSignal.set(value), 300);
// In your input handler
inputElement.addEventListener("input", e=> debouncedSet(e.target.value));

View File

@ -0,0 +1,15 @@
import { S } from "deka-dom-el/signals";
// Wrong - direct mutation doesn't trigger updates
const todos1 = S([{ text: "Learn signals", completed: false }]);
todos1.get().push({ text: "Debug signals", completed: false }); // Won't trigger updates!
// Correct - using .set() with a new array
todos1.set([...todos1.get(), { text: "Debug signals", completed: false }]);
// Better - using actions
const todos2 = S([], {
add(text) {
this.value.push({ text, completed: false });
}
});
S.action(todos2, "add", "Debug signals");

View File

@ -0,0 +1,20 @@
import { S } from "deka-dom-el/signals";
// Debugging a derived signal
const name = S('Alice');
const greeting = S(() => {
console.log('Computing greeting...');
return 'Hello, ' + name.get();
});
// Monitor the derived signal
S.on(greeting, value => console.log('Greeting changed to:', value));
// Later update the dependency
name.set('Bob'); // Should trigger computation and listener
// Console output:
// Computing greeting...
// Greeting changed to: Hello, Alice
// Computing greeting...
// Greeting changed to: Hello, Bob

View File

@ -0,0 +1,35 @@
import { el, assign } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
// Create a component with reactive elements
function ReactiveCounter() {
const count = S(0);
// Elements created with el() have data-dde attribute
const counter = el('div', {
id: 'counter',
// This element will have __dde_reactive property
textContent: count
});
const incrementBtn = el('button', {
textContent: 'Increment',
onclick: () => count.set(count.get() + 1)
});
// Dynamic section with __dde_signal property
const counterInfo = S.el(count, value =>
el('p', `Current count is ${value}`)
);
return el('div').append(
counter,
incrementBtn,
counterInfo
);
}
// In DevTools console:
// document.querySelectorAll('[data-dde]'); // Find all elements created with deka-dom-el
// const counter = document.querySelector('#counter');
// console.log(counter.__dde_reactive); // See reactive bindings

View File

@ -68,8 +68,6 @@ styles.css`
/* Base styling */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
@ -160,13 +158,6 @@ h1 > a {
color: unset;
}
h2 {
font-size: 1.5rem;
border-bottom: 2px solid var(--border);
padding-bottom: 0.5rem;
color: var(--primary);
}
h3 {
font-size: 1.25rem;
color: var(--secondary);

183
docs/p07-debugging.html.js Normal file
View File

@ -0,0 +1,183 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Debugging`,
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
};
import { el } from "deka-dom-el";
import { simplePage } from "./layout/simplePage.html.js";
import { example } from "./components/example.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { code } from "./components/code.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Debugging applications with deka-dom-el`),
el("p").append(...T`
Debugging is an essential part of application development. This guide provides techniques
and best practices for debugging applications built with deka-dom-el, with a focus on signals.
`),
el(h3, t`Debugging signals`),
el("p").append(...T`
Signals are reactive primitives that update the UI when their values change. When debugging signals,
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
`),
el("h4", t`Inspecting signal values`),
el("p").append(...T`
The simplest way to debug a signal is to log its current value by calling the get method:
`),
el(code, { content: "const signal = S(0);\nconsole.log('Current value:', signal.get());", page_id }),
el("p").append(...T`
You can also monitor signal changes by adding a listener:
`),
el(code, { content: "// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));", page_id }),
el("h4", t`Debugging derived signals`),
el("p").append(...T`
With derived signals (created with S(() => computation)), debugging is a bit more complex
because the value depends on other signals. To understand why a derived signal isn't updating correctly:
`),
el("ol").append(
el("li", t`Check that all dependency signals are updating correctly`),
el("li", t`Add logging inside the computation function to see when it runs`),
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
),
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
el(h3, t`Common signal debugging issues`),
el("h4", t`Signal updates not triggering UI changes`),
el("p").append(...T`
If signal updates aren't reflected in the UI, check:
`),
el("ul").append(
el("li", t`That you're using signal.set() to update the value, not modifying objects/arrays directly`),
el("li", t`For mutable objects, ensure you're using actions or making proper copies before updating`),
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
),
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
el("h4", t`Memory leaks with signal listeners`),
el("p").append(...T`
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
to cancel listeners.
`),
el("h4", t`Performance issues with frequently updating signals`),
el("p").append(...T`
If you notice performance issues with signals that update very frequently:
`),
el("ul").append(
el("li", t`Consider debouncing or throttling signal updates`),
el("li", t`Make sure derived signals don't perform expensive calculations unnecessarily`),
el("li", t`Keep signal computations focused and minimal`)
),
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
el(h3, t`Browser DevTools tips for deka-dom-el`),
el("p").append(...T`
When debugging in the browser, deka-dom-el provides several helpful DevTools-friendly features:
`),
el("h4", t`Identifying components in the DOM`),
el("p").append(...T`
deka-dom-el marks components in the DOM with special comment nodes to help you identify component boundaries.
Components created with ${el("code", "el(ComponentFunction)")} are marked with comment nodes
${el("code", "<!--<dde:mark type=\"component\" name=\"MyComponent\" host=\"parentElement\"/>-->")} and
includes:
`),
el("ul").append(
el("li", "type - Identifies the type of marker (\"component\", \"reactive\", or \"later\")"),
el("li", "name - The name of the component function"),
el("li", "host - Indicates whether the host is \"this\" (for DocumentFragments) or \"parentElement\""),
),
el("h4", t`Finding reactive elements in the DOM`),
el("p").append(...T`
When using ${el("code", "S.el()")}, deka-dom-el creates reactive elements in the DOM
that are automatically updated when signal values change. These elements are wrapped in special
comment nodes for debugging (to be true they are also used internaly, so please do not edit them by hand):
`),
el(code, { content: "// Example of reactive element marker\n<!--<dde:mark type=\"reactive\" source=\"...\">-->\n<!-- content that updates when signal changes -->\n<!--</dde:mark>-->", page_id }),
el("p").append(...T`
This is particularly useful when debugging why a reactive section isn't updating as expected.
You can inspect the elements between the comment nodes to see their current state and the
signal connections through \`__dde_reactive\` of the host element.
`),
el("h4", t`DOM inspection properties`),
el("p").append(...T`
Elements created with the deka-dom-el library have special properties to aid in debugging:
`),
el("ul").append(
el("li").append(...T`
${el("code", "__dde_reactive")} - An array property on DOM elements that tracks signal-to-element relationships.
This allows you to quickly identify which elements are reactive and what signals they're bound to.
Each entry in the array contains:
`),
el("ul").append(
el("li", "A pair of signal and listener function: [signal, listener]"),
el("li", "Additional context information about the element or attribute"),
el("li", "Automatically managed by signal.el(), signal.observedAttributes(), and processReactiveAttribute()")
),
el("li").append(...T`
${el("code", "__dde_signal")} - A Symbol property used to identify and store the internal state of signal objects.
It contains the following information:
`),
el("ul").append(
el("li", "value: The current value of the signal"),
el("li", "listeners: A Set of functions called when the signal value changes"),
el("li", "actions: Custom actions that can be performed on the signal"),
el("li", "onclear: Functions to run when the signal is cleared"),
el("li", "host: Reference to the host element or scope"),
el("li", "defined: Stack trace information for debugging"),
el("li", "readonly: Boolean flag indicating if the signal is read-only")
),
),
el("p").append(...T`
These properties make it easier to understand the reactive structure of your application when inspecting elements.
`),
el(example, { src: fileURL("./components/examples/signals/debugging-dom.js"), page_id }),
el("h4", t`Examining signal connections`),
el("p").append(...T`
You can inspect signal relationships and bindings in the DevTools console using ${el("code", "$0.__dde_reactive")}.
In console you will see list of ${el("code", "[ [ signal, listener ], element, property ]")}, where:
`),
el("ul").append(
el("li", "signal — the signal triggering the changes"),
el("li", "listener — the listener function (this is internal function for dde)"),
el("li", "element — the DOM element that is bound to the signal"),
el("li", "property — the attribute or property name which is changing based on the signal"),
),
el("p").append(...T`
the structure of \`__dde_reactive\` use the behavior of the browser that packs the first field,
so you can see the element and property that changes in the console right away
`),
el("h4", t`Debugging with breakpoints`),
el("p").append(...T`
Effective use of breakpoints can help track signal flow:
`),
el("ul").append(
el("li").append(...T`
Set breakpoints in signal update methods to track when values change
`),
el("li").append(...T`
Use conditional breakpoints to only break when specific signals change to certain values
`),
el("li").append(...T`
Set breakpoints in your signal computation functions to see when derived signals recalculate
`),
el("li").append(...T`
Use performance profiling to identify bottlenecks in signal updates
`)
),
);
}

View File

@ -0,0 +1,102 @@
import { T, t } from "./utils/index.js";
export const info= {
title: t`Debugging`,
description: t`Techniques for debugging applications using deka-dom-el, especially signals.`,
};
import { el } from "deka-dom-el";
import { simplePage } from "./layout/simplePage.html.js";
import { example } from "./components/example.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { code } from "./components/code.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("h2", t`Debugging applications with deka-dom-el`),
el("p").append(...T`
Debugging is an essential part of application development. This guide provides techniques
and best practices for debugging applications built with deka-dom-el, with a focus on signals.
`),
el(h3, t`Debugging signals`),
el("p").append(...T`
Signals are reactive primitives that update the UI when their values change. When debugging signals,
you need to track their values, understand their dependencies, and identify why updates are or aren't happening.
`),
el("h4", t`Inspecting signal values`),
el("p").append(...T`
The simplest way to debug a signal is to log its current value by calling the get method:
`),
el("pre").append(
el("code", "const signal = S(0);\nconsole.log('Current value:', signal.get());")
),
el("p").append(...T`
You can also monitor signal changes by adding a listener:
`),
el("pre").append(
el("code", "// Log every time the signal changes\nS.on(signal, value => console.log('Signal changed:', value));")
),
el("h4", t`Debugging derived signals`),
el("p").append(...T`
With derived signals (created with S(() => computation)), debugging is a bit more complex
because the value depends on other signals. To understand why a derived signal isn't updating correctly:
`),
el("ol").append(
el("li", t`Check that all dependency signals are updating correctly`),
el("li", t`Add logging inside the computation function to see when it runs`),
el("li", t`Verify that the computation function actually accesses the signal values with .get()`)
),
el(example, { src: fileURL("./components/examples/debugging/consoleLog.js"), page_id }),
el(h3, t`Common signal debugging issues`),
el("h4", t`Signal updates not triggering UI changes`),
el("p").append(...T`
If signal updates aren't reflected in the UI, check:
`),
el("ul").append(
el("li", t`That you're using signal.set() to update the value, not modifying objects/arrays directly`),
el("li", t`For mutable objects, ensure you're using actions or making proper copies before updating`),
el("li", t`That the signal is actually connected to the DOM element (check your S.el or attribute binding code)`)
),
el(code, { src: fileURL("./components/examples/debugging/mutations.js"), page_id }),
el("h4", t`Memory leaks with signal listeners`),
el("p").append(...T`
Signal listeners can cause memory leaks if not properly cleaned up. Always use AbortSignal
to cancel listeners.
`),
el("h4", t`Performance issues with frequently updating signals`),
el("p").append(...T`
If you notice performance issues with signals that update very frequently:
`),
el("ul").append(
el("li", t`Consider debouncing or throttling signal updates`),
el("li", t`Make sure derived signals don't perform expensive calculations unnecessarily`),
el("li", t`Keep signal computations focused and minimal`)
),
el(code, { src: fileURL("./components/examples/debugging/debouncing.js"), page_id }),
el(h3, t`Browser DevTools tips for deka-dom-el`),
el("p").append(...T`
When debugging in the browser, here are some helpful techniques:
`),
el("ul").append(
el("li").append(...T`
Use the Elements panel to inspect the DOM structure created by deka-dom-el
`),
el("li").append(...T`
Set breakpoints in your signal handlers and actions
`),
el("li").append(...T`
Use performance profiling to identify bottlenecks in signal updates
`),
),
);
}