From 3d9a9ab32d31a3a09794bffaead83b9043947dca Mon Sep 17 00:00:00 2001 From: Thor Galle Date: Thu, 17 Apr 2025 08:35:44 +0200 Subject: [PATCH 1/4] docs: correct the suggested type for custom events without detail (Svelte 4) (#15763) * docs: correct the suggested type for custom events without detail * docs: generate fixed types for the Svelte 4 event dispatcher --- .changeset/fifty-buckets-return.md | 5 +++++ packages/svelte/src/index-client.js | 2 +- packages/svelte/types/index.d.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/fifty-buckets-return.md diff --git a/.changeset/fifty-buckets-return.md b/.changeset/fifty-buckets-return.md new file mode 100644 index 0000000000..7c79f0b596 --- /dev/null +++ b/.changeset/fifty-buckets-return.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +correct the suggested type for custom events without detail diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index fd8e999da7..efd5628ae9 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -114,7 +114,7 @@ function create_custom_event(type, detail, { bubbles = false, cancelable = false * The event dispatcher can be typed to narrow the allowed event names and the type of the `detail` argument: * ```ts * const dispatch = createEventDispatcher<{ - * loaded: never; // does not take a detail argument + * loaded: null; // does not take a detail argument * change: string; // takes a detail argument of type string, which is required * optional: number | null; // takes an optional detail argument of type number * }>(); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 6f12daf187..8fc174b0a9 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -381,7 +381,7 @@ declare module 'svelte' { * The event dispatcher can be typed to narrow the allowed event names and the type of the `detail` argument: * ```ts * const dispatch = createEventDispatcher<{ - * loaded: never; // does not take a detail argument + * loaded: null; // does not take a detail argument * change: string; // takes a detail argument of type string, which is required * optional: number | null; // takes an optional detail argument of type number * }>(); From 19836e29f23a70223e3e5601b33345b9bdf617aa Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 17 Apr 2025 08:16:27 -0400 Subject: [PATCH 2/4] chore: add log_effect_tree utility (#15780) --- .../svelte/src/internal/client/dev/debug.js | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 packages/svelte/src/internal/client/dev/debug.js diff --git a/packages/svelte/src/internal/client/dev/debug.js b/packages/svelte/src/internal/client/dev/debug.js new file mode 100644 index 0000000000..f449dfa2cc --- /dev/null +++ b/packages/svelte/src/internal/client/dev/debug.js @@ -0,0 +1,109 @@ +/** @import { Derived, Effect, Value } from '#client' */ + +import { + BLOCK_EFFECT, + BOUNDARY_EFFECT, + BRANCH_EFFECT, + CLEAN, + DERIVED, + EFFECT, + MAYBE_DIRTY, + RENDER_EFFECT, + ROOT_EFFECT +} from '../constants.js'; + +/** + * + * @param {Effect} effect + */ +export function root(effect) { + while (effect.parent !== null) { + effect = effect.parent; + } + + return effect; +} + +/** + * + * @param {Effect} effect + */ +export function log_effect_tree(effect, depth = 0) { + const flags = effect.f; + + let label = '(unknown)'; + + if ((flags & ROOT_EFFECT) !== 0) { + label = 'root'; + } else if ((flags & BOUNDARY_EFFECT) !== 0) { + label = 'boundary'; + } else if ((flags & BLOCK_EFFECT) !== 0) { + label = 'block'; + } else if ((flags & BRANCH_EFFECT) !== 0) { + label = 'branch'; + } else if ((flags & RENDER_EFFECT) !== 0) { + label = 'render effect'; + } else if ((flags & EFFECT) !== 0) { + label = 'effect'; + } + + let status = + (flags & CLEAN) !== 0 ? 'clean' : (flags & MAYBE_DIRTY) !== 0 ? 'maybe dirty' : 'dirty'; + + // eslint-disable-next-line no-console + console.group(`%c${label} (${status})`, `font-weight: ${status === 'clean' ? 'normal' : 'bold'}`); + + if (depth === 0) { + const callsite = new Error().stack + ?.split('\n')[2] + .replace(/\s+at (?: \w+\(?)?(.+)\)?/, (m, $1) => $1.replace(/\?[^:]+/, '')); + + // eslint-disable-next-line no-console + console.log(callsite); + } + + if (effect.deps !== null) { + // eslint-disable-next-line no-console + console.groupCollapsed('%cdeps', 'font-weight: normal'); + + for (const dep of effect.deps) { + log_dep(dep); + } + + // eslint-disable-next-line no-console + console.groupEnd(); + } + + let child = effect.first; + while (child !== null) { + log_effect_tree(child, depth + 1); + child = child.next; + } + + // eslint-disable-next-line no-console + console.groupEnd(); +} + +/** + * + * @param {Value} dep + */ +function log_dep(dep) { + if ((dep.f & DERIVED) !== 0) { + const derived = /** @type {Derived} */ (dep); + + // eslint-disable-next-line no-console + console.groupCollapsed('%cderived', 'font-weight: normal', derived.v); + if (derived.deps) { + for (const d of derived.deps) { + log_dep(d); + } + } + + // eslint-disable-next-line no-console + console.groupEnd(); + } else { + // eslint-disable-next-line no-console + console.log('state', dep.v); + } +} From 80f62b5b100e322157e01b2cad4eb451c1d38606 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 17 Apr 2025 08:16:52 -0400 Subject: [PATCH 3/4] chore: use template_effect for html tags (#15779) --- .../src/internal/client/dom/blocks/html.js | 113 ++++++++---------- .../src/internal/client/reactivity/effects.js | 28 +++-- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/blocks/html.js b/packages/svelte/src/internal/client/dom/blocks/html.js index e332108012..92c8243478 100644 --- a/packages/svelte/src/internal/client/dom/blocks/html.js +++ b/packages/svelte/src/internal/client/dom/blocks/html.js @@ -1,6 +1,6 @@ /** @import { Effect, TemplateNode } from '#client' */ import { FILENAME, HYDRATION_ERROR } from '../../../../constants.js'; -import { block, branch, destroy_effect } from '../../reactivity/effects.js'; +import { remove_effect_dom, template_effect } from '../../reactivity/effects.js'; import { hydrate_next, hydrate_node, hydrating, set_hydrate_node } from '../hydration.js'; import { create_fragment_from_html } from '../reconciler.js'; import { assign_nodes } from '../template.js'; @@ -9,6 +9,7 @@ import { hash, sanitize_location } from '../../../../utils.js'; import { DEV } from 'esm-env'; import { dev_current_component_function } from '../../context.js'; import { get_first_child, get_next_sibling } from '../operations.js'; +import { active_effect } from '../../runtime.js'; /** * @param {Element} element @@ -44,79 +45,71 @@ export function html(node, get_value, svg = false, mathml = false, skip_warning var value = ''; - /** @type {Effect | undefined} */ - var effect; + template_effect(() => { + var effect = /** @type {Effect} */ (active_effect); - block(() => { if (value === (value = get_value() ?? '')) { - if (hydrating) { - hydrate_next(); - } + if (hydrating) hydrate_next(); return; } - if (effect !== undefined) { - destroy_effect(effect); - effect = undefined; + if (effect.nodes_start !== null) { + remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end)); + effect.nodes_start = effect.nodes_end = null; } if (value === '') return; - effect = branch(() => { - if (hydrating) { - // We're deliberately not trying to repair mismatches between server and client, - // as it's costly and error-prone (and it's an edge case to have a mismatch anyway) - var hash = /** @type {Comment} */ (hydrate_node).data; - var next = hydrate_next(); - var last = next; - - while ( - next !== null && - (next.nodeType !== 8 || /** @type {Comment} */ (next).data !== '') - ) { - last = next; - next = /** @type {TemplateNode} */ (get_next_sibling(next)); - } - - if (next === null) { - w.hydration_mismatch(); - throw HYDRATION_ERROR; - } - - if (DEV && !skip_warning) { - check_hash(/** @type {Element} */ (next.parentNode), hash, value); - } - - assign_nodes(hydrate_node, last); - anchor = set_hydrate_node(next); - return; - } + if (hydrating) { + // We're deliberately not trying to repair mismatches between server and client, + // as it's costly and error-prone (and it's an edge case to have a mismatch anyway) + var hash = /** @type {Comment} */ (hydrate_node).data; + var next = hydrate_next(); + var last = next; - var html = value + ''; - if (svg) html = `${html}`; - else if (mathml) html = `${html}`; + while (next !== null && (next.nodeType !== 8 || /** @type {Comment} */ (next).data !== '')) { + last = next; + next = /** @type {TemplateNode} */ (get_next_sibling(next)); + } - // Don't use create_fragment_with_script_from_html here because that would mean script tags are executed. - // @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons. - /** @type {DocumentFragment | Element} */ - var node = create_fragment_from_html(html); + if (next === null) { + w.hydration_mismatch(); + throw HYDRATION_ERROR; + } - if (svg || mathml) { - node = /** @type {Element} */ (get_first_child(node)); + if (DEV && !skip_warning) { + check_hash(/** @type {Element} */ (next.parentNode), hash, value); } - assign_nodes( - /** @type {TemplateNode} */ (get_first_child(node)), - /** @type {TemplateNode} */ (node.lastChild) - ); - - if (svg || mathml) { - while (get_first_child(node)) { - anchor.before(/** @type {Node} */ (get_first_child(node))); - } - } else { - anchor.before(node); + assign_nodes(hydrate_node, last); + anchor = set_hydrate_node(next); + return; + } + + var html = value + ''; + if (svg) html = `${html}`; + else if (mathml) html = `${html}`; + + // Don't use create_fragment_with_script_from_html here because that would mean script tags are executed. + // @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons. + /** @type {DocumentFragment | Element} */ + var node = create_fragment_from_html(html); + + if (svg || mathml) { + node = /** @type {Element} */ (get_first_child(node)); + } + + assign_nodes( + /** @type {TemplateNode} */ (get_first_child(node)), + /** @type {TemplateNode} */ (node.lastChild) + ); + + if (svg || mathml) { + while (get_first_child(node)) { + anchor.before(/** @type {Node} */ (get_first_child(node))); } - }); + } else { + anchor.before(node); + } }); } diff --git a/packages/svelte/src/internal/client/reactivity/effects.js b/packages/svelte/src/internal/client/reactivity/effects.js index 468bb94ab4..76b014c916 100644 --- a/packages/svelte/src/internal/client/reactivity/effects.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -427,18 +427,7 @@ export function destroy_effect(effect, remove_dom = true) { var removed = false; if ((remove_dom || (effect.f & HEAD_EFFECT) !== 0) && effect.nodes_start !== null) { - /** @type {TemplateNode | null} */ - var node = effect.nodes_start; - var end = effect.nodes_end; - - while (node !== null) { - /** @type {TemplateNode | null} */ - var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node)); - - node.remove(); - node = next; - } - + remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end)); removed = true; } @@ -480,6 +469,21 @@ export function destroy_effect(effect, remove_dom = true) { null; } +/** + * + * @param {TemplateNode | null} node + * @param {TemplateNode} end + */ +export function remove_effect_dom(node, end) { + while (node !== null) { + /** @type {TemplateNode | null} */ + var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node)); + + node.remove(); + node = next; + } +} + /** * Detach an effect from the effect tree, freeing up memory and * reducing the amount of work that happens on subsequent traversals From 189bd4788e9de8fc4f65495040e5dce6ed42b9ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 09:03:18 -0400 Subject: [PATCH 4/4] Version Packages (#15772) * Version Packages * Update packages/svelte/CHANGELOG.md --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Rich Harris --- .changeset/chatty-apples-flash.md | 5 ----- .changeset/fifty-buckets-return.md | 5 ----- .changeset/strong-pianos-promise.md | 5 ----- .changeset/sweet-adults-complain.md | 5 ----- packages/svelte/CHANGELOG.md | 12 ++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 7 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 .changeset/chatty-apples-flash.md delete mode 100644 .changeset/fifty-buckets-return.md delete mode 100644 .changeset/strong-pianos-promise.md delete mode 100644 .changeset/sweet-adults-complain.md diff --git a/.changeset/chatty-apples-flash.md b/.changeset/chatty-apples-flash.md deleted file mode 100644 index fc689a003c..0000000000 --- a/.changeset/chatty-apples-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -chore: default params for html blocks diff --git a/.changeset/fifty-buckets-return.md b/.changeset/fifty-buckets-return.md deleted file mode 100644 index 7c79f0b596..0000000000 --- a/.changeset/fifty-buckets-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -correct the suggested type for custom events without detail diff --git a/.changeset/strong-pianos-promise.md b/.changeset/strong-pianos-promise.md deleted file mode 100644 index f5214c7dcb..0000000000 --- a/.changeset/strong-pianos-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: Throw on unrendered snippets in `dev` diff --git a/.changeset/sweet-adults-complain.md b/.changeset/sweet-adults-complain.md deleted file mode 100644 index 429b034b3d..0000000000 --- a/.changeset/sweet-adults-complain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: avoid unnecessary read version increments diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index c8f0ad7ed9..4ffcb263ab 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.27.1 + +### Patch Changes + +- chore: default params for html blocks ([#15778](https://github.com/sveltejs/svelte/pull/15778)) + +- fix: correct suggested type for custom events without detail ([#15763](https://github.com/sveltejs/svelte/pull/15763)) + +- fix: Throw on unrendered snippets in `dev` ([#15766](https://github.com/sveltejs/svelte/pull/15766)) + +- fix: avoid unnecessary read version increments ([#15777](https://github.com/sveltejs/svelte/pull/15777)) + ## 5.27.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index af78d2679a..7ad4835a9a 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.27.0", + "version": "5.27.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 27a39136f8..b0424e8232 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -4,5 +4,5 @@ * The current version, as set in package.json. * @type {string} */ -export const VERSION = '5.27.0'; +export const VERSION = '5.27.1'; export const PUBLIC_VERSION = '5';