From cc6155922b4a17a70a2dd54cbc0618bc908c6442 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 11:54:52 +0000 Subject: [PATCH 01/90] Version Packages (next) (#10601) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 8 ++++++++ packages/svelte/CHANGELOG.md | 20 ++++++++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 0b3bce6575..1a5f1cdba4 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -20,6 +20,7 @@ "big-eggs-flash", "big-eyes-carry", "big-geese-act", + "blue-rules-juggle", "blue-timers-film", "brave-points-sleep", "brave-shrimps-kiss", @@ -29,6 +30,7 @@ "bright-snakes-sing", "brown-months-fry", "brown-spoons-boil", + "chatty-beans-divide", "chatty-cups-drop", "chatty-taxis-juggle", "chilled-pumas-invite", @@ -38,6 +40,7 @@ "clever-chefs-relate", "clever-rockets-burn", "cold-birds-own", + "cold-masks-learn", "cool-ants-leave", "cool-rabbits-tickle", "cool-roses-trade", @@ -83,6 +86,7 @@ "forty-peaches-unite", "forty-suns-smile", "four-flies-hammer", + "fresh-impalas-bow", "fresh-weeks-trade", "friendly-candles-relate", "friendly-lies-camp", @@ -92,6 +96,7 @@ "gentle-spies-happen", "giant-moons-own", "giant-roses-press", + "good-buses-reply", "good-cars-visit", "good-pianos-jump", "good-rivers-yawn", @@ -151,6 +156,7 @@ "moody-carrots-lay", "moody-frogs-exist", "moody-owls-cry", + "moody-sheep-type", "nasty-lions-double", "nasty-yaks-peel", "neat-boats-shake", @@ -184,6 +190,7 @@ "pretty-ties-help", "purple-dragons-peel", "quiet-apricots-dream", + "quiet-berries-end", "quiet-camels-mate", "quiet-crabs-nail", "quiet-timers-speak", @@ -202,6 +209,7 @@ "rotten-bags-type", "rotten-buckets-develop", "rotten-experts-relax", + "rude-ghosts-tickle", "selfish-dragons-knock", "selfish-tools-hide", "serious-kids-deliver", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 16e2323727..b9f6ebc995 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,25 @@ # svelte +## 5.0.0-next.66 + +### Patch Changes + +- fix: don't clear date input on temporarily invalid value ([#10616](https://github.com/sveltejs/svelte/pull/10616)) + +- fix: use safe-equals comparison for `@const` tags in legacy mode ([#10606](https://github.com/sveltejs/svelte/pull/10606)) + +- fix: improve proxy effect dependency tracking ([#10605](https://github.com/sveltejs/svelte/pull/10605)) + +- fix: prevent window listeners from triggering events twice ([#10611](https://github.com/sveltejs/svelte/pull/10611)) + +- feat: allow dynamic `type` attribute with `bind:value` ([#10608](https://github.com/sveltejs/svelte/pull/10608)) + +- fix: make `bind_this` implementation more robust ([#10598](https://github.com/sveltejs/svelte/pull/10598)) + +- fix: tweak initial `bind:clientWidth/clientHeight/offsetWidth/offsetHeight` update timing ([#10512](https://github.com/sveltejs/svelte/pull/10512)) + +- fix: correctly handle proxied signal writes before reads ([#10612](https://github.com/sveltejs/svelte/pull/10612)) + ## 5.0.0-next.65 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 249f407987..2d8d9f0786 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.0.0-next.65", + "version": "5.0.0-next.66", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 9ac8ccda11..e11795b510 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.65'; +export const VERSION = '5.0.0-next.66'; export const PUBLIC_VERSION = '5'; From 351d463bc4e16782a7c4265c06f8e5bac56134e5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 23 Feb 2024 12:15:24 +0000 Subject: [PATCH 02/90] chore: remove array.at usage from runtime (#10619) --- packages/svelte/src/internal/client/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 22d85545a0..ed08d9e974 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2540,7 +2540,7 @@ export function hydrate(component, options) { ); remove(hydration_fragment); first_child.remove(); - hydration_fragment.at(-1)?.nextSibling?.remove(); + hydration_fragment[hydration_fragment.length - 1]?.nextSibling?.remove(); set_current_hydration_fragment(null); return mount(component, options); } else { From b4a70ea38a180e435aed9f5797d38e4b1e39a077 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 23 Feb 2024 12:49:40 +0000 Subject: [PATCH 03/90] fix: improve event delegation with shadowed bindings (#10620) * fix: improve event delegation with shadowed bindings * fix: improve event delegation with shadowed bindings --- .changeset/little-pans-jog.md | 5 +++++ .../src/compiler/phases/2-analyze/index.js | 6 ++++++ .../event-attribute-delegation-6/_config.js | 16 ++++++++++++++++ .../samples/event-attribute-delegation-6/log.js | 2 ++ .../event-attribute-delegation-6/main.svelte | 12 ++++++++++++ 5 files changed, 41 insertions(+) create mode 100644 .changeset/little-pans-jog.md create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/log.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/main.svelte diff --git a/.changeset/little-pans-jog.md b/.changeset/little-pans-jog.md new file mode 100644 index 0000000000..891f2ea88f --- /dev/null +++ b/.changeset/little-pans-jog.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve event delegation with shadowed bindings diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 8b2b806ec7..f2e2a3dec2 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -176,6 +176,12 @@ function get_delegated_event(event_name, handler, context) { return non_hoistable; } const binding = scope.get(reference); + const local_binding = context.state.scope.get(reference); + + // If we are referencing a binding that is shadowed in another scope then bail out. + if (local_binding !== null && binding !== null && local_binding.node !== binding.node) { + return non_hoistable; + } // If we have multiple references to the same store using $ prefix, bail out. if ( diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/_config.js b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/_config.js new file mode 100644 index 0000000000..bc16bf031f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/_config.js @@ -0,0 +1,16 @@ +import { test } from '../../test'; +import { log } from './log.js'; + +export default test({ + before_test() { + log.length = 0; + }, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + + btn?.click(); + await Promise.resolve(); + assert.deepEqual(log, ['method']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/log.js b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/log.js new file mode 100644 index 0000000000..d3df521f4d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/log.js @@ -0,0 +1,2 @@ +/** @type {any[]} */ +export const log = []; diff --git a/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/main.svelte new file mode 100644 index 0000000000..0a365990ec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attribute-delegation-6/main.svelte @@ -0,0 +1,12 @@ + +{#each methods as {method}} + +{/each} From f658ca0d548cc0e4eb0e33f1c4a7586f3dd8ccec Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 23 Feb 2024 18:28:56 +0000 Subject: [PATCH 04/90] feat: add reactive Date object to svelte/reactivity (#10622) * feat: add reactive Date object to svelte/reactivity * add type safety, fix revealed typos --------- Co-authored-by: Rich Harris --- .changeset/new-rabbits-flow.md | 5 + packages/svelte/package.json | 4 + packages/svelte/src/reactivity/index.js | 103 ++++++++++++++++++ .../runtime-runes/samples/date/_config.js | 37 +++++++ .../runtime-runes/samples/date/main.svelte | 21 ++++ 5 files changed, 170 insertions(+) create mode 100644 .changeset/new-rabbits-flow.md create mode 100644 packages/svelte/src/reactivity/index.js create mode 100644 packages/svelte/tests/runtime-runes/samples/date/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/date/main.svelte diff --git a/.changeset/new-rabbits-flow.md b/.changeset/new-rabbits-flow.md new file mode 100644 index 0000000000..273496b2ae --- /dev/null +++ b/.changeset/new-rabbits-flow.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: add reactive Date object to svelte/reactivity diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 2d8d9f0786..490ba7249f 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -62,6 +62,10 @@ "types": "./types/index.d.ts", "default": "./src/motion/index.js" }, + "./reactivity": { + "types": "./types/index.d.ts", + "default": "./src/reactivity/index.js" + }, "./server": { "types": "./types/index.d.ts", "default": "./src/server/index.js" diff --git a/packages/svelte/src/reactivity/index.js b/packages/svelte/src/reactivity/index.js new file mode 100644 index 0000000000..ce865c5a3e --- /dev/null +++ b/packages/svelte/src/reactivity/index.js @@ -0,0 +1,103 @@ +import { source } from '../internal/client/reactivity/sources'; +import { get, set } from '../internal/client/runtime'; + +/** @type {Array} */ +const read = [ + 'getDate', + 'getDay', + 'getFullYear', + 'getHours', + 'getMilliseconds', + 'getMinutes', + 'getMonth', + 'getSeconds', + 'getTime', + 'getTimezoneOffset', + 'getUTCDate', + 'getUTCDay', + 'getUTCFullYear', + 'getUTCHours', + 'getUTCMilliseconds', + 'getUTCMinutes', + 'getUTCMonth', + 'getUTCSeconds', + // @ts-expect-error this is deprecated + 'getYear', + 'toDateString', + 'toISOString', + 'toJSON', + 'toLocaleDateString', + 'toLocaleString', + 'toLocaleTimeString', + 'toString', + 'toTimeString', + 'toUTCString' +]; + +/** @type {Array} */ +const write = [ + 'setDate', + 'setFullYear', + 'setHours', + 'setMilliseconds', + 'setMinutes', + 'setMonth', + 'setSeconds', + 'setTime', + 'setUTCDate', + 'setUTCFullYear', + 'setUTCHours', + 'setUTCMilliseconds', + 'setUTCMinutes', + 'setUTCMonth', + 'setUTCSeconds', + // @ts-expect-error this is deprecated + 'setYear' +]; + +class ReactiveDate extends Date { + #raw_time = source(super.getTime()); + static #inited = false; + + // We init as part of the first instance so that we can treeshake this class + #init() { + if (!ReactiveDate.#inited) { + ReactiveDate.#inited = true; + const proto = ReactiveDate.prototype; + const date_proto = Date.prototype; + + for (const method of read) { + // @ts-ignore + proto[method] = function () { + get(this.#raw_time); + // @ts-ignore + return date_proto[method].call(this); + }; + } + + for (const method of write) { + // @ts-ignore + proto[method] = function (/** @type {any} */ ...args) { + // @ts-ignore + const v = date_proto[method].apply(this, args); + const time = date_proto.getTime.call(this); + if (time !== this.#raw_time.v) { + set(this.#raw_time, time); + } + return v; + }; + } + } + } + + /** + * @param {any[]} values + */ + constructor(...values) { + // @ts-ignore + super(...values); + this.#init(); + } +} + +export { ReactiveDate as Date }; diff --git a/packages/svelte/tests/runtime-runes/samples/date/_config.js b/packages/svelte/tests/runtime-runes/samples/date/_config.js new file mode 100644 index 0000000000..8bd2327683 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/date/_config.js @@ -0,0 +1,37 @@ +import { flushSync } from '../../../../src/main/main-client'; +import { test } from '../../test'; + +export default test({ + html: `
getSeconds: 0
getMinutes: 0
getHours: 15
getTime: 1708700400000
toDateString: Fri Feb 23 2024
`, + + test({ assert, target }) { + const [btn, btn2, btn3] = target.querySelectorAll('button'); + + flushSync(() => { + btn?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
getSeconds: 1
getMinutes: 0
getHours: 15
getTime: 1708700401000
toDateString: Fri Feb 23 2024
` + ); + + flushSync(() => { + btn2?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
getSeconds: 1
getMinutes: 1
getHours: 15
getTime: 1708700461000
toDateString: Fri Feb 23 2024
` + ); + + flushSync(() => { + btn3?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + `
getSeconds: 1
getMinutes: 1
getHours: 16
getTime: 1708704061000
toDateString: Fri Feb 23 2024
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/date/main.svelte b/packages/svelte/tests/runtime-runes/samples/date/main.svelte new file mode 100644 index 0000000000..bbb6ae5bbe --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/date/main.svelte @@ -0,0 +1,21 @@ + + +
getSeconds: {date.getSeconds()}
+
getMinutes: {date.getMinutes()}
+
getHours: {date.getHours()}
+
getTime: {date.getTime()}
+
toDateString: {date.toDateString()}
+ + + + From 1822396a5522003a9e11e7c8d7afe27884303a75 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:56:15 -0500 Subject: [PATCH 05/90] Version Packages (next) (#10621) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 2 ++ packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 1a5f1cdba4..2968e1cd20 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -141,6 +141,7 @@ "light-days-clean", "light-humans-hang", "light-pens-watch", + "little-pans-jog", "long-buckets-lay", "long-crews-return", "long-lobsters-mate", @@ -163,6 +164,7 @@ "neat-dingos-clap", "nervous-spoons-relax", "new-boats-wait", + "new-rabbits-flow", "ninety-dingos-walk", "odd-buckets-lie", "odd-needles-joke", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index b9f6ebc995..c868ffe6f9 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.0.0-next.67 + +### Patch Changes + +- fix: improve event delegation with shadowed bindings ([#10620](https://github.com/sveltejs/svelte/pull/10620)) + +- feat: add reactive Date object to svelte/reactivity ([#10622](https://github.com/sveltejs/svelte/pull/10622)) + ## 5.0.0-next.66 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 490ba7249f..1da3ab364f 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.0.0-next.66", + "version": "5.0.0-next.67", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index e11795b510..033bb1efbc 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.66'; +export const VERSION = '5.0.0-next.67'; export const PUBLIC_VERSION = '5'; From aa28ac3c7a9d97154e9dab0a2958c49e3aad8258 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 23 Feb 2024 21:52:23 +0000 Subject: [PATCH 06/90] fix: improve deep_read performance (#10624) --- .changeset/wise-apples-care.md | 5 +++++ packages/svelte/src/internal/client/render.js | 2 +- packages/svelte/src/internal/client/runtime.js | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .changeset/wise-apples-care.md diff --git a/.changeset/wise-apples-care.md b/.changeset/wise-apples-care.md new file mode 100644 index 0000000000..1583d753ec --- /dev/null +++ b/.changeset/wise-apples-care.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve deep_read performance diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index ed08d9e974..0861acdfce 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -1929,12 +1929,12 @@ export function out(dom, get_transition_fn, props, global = false) { export function action(dom, action, value_fn) { /** @type {undefined | import('./types.js').ActionPayload

} */ let payload = undefined; + let needs_deep_read = false; // Action could come from a prop, therefore could be a signal, therefore untrack // TODO we could take advantage of this and enable https://github.com/sveltejs/svelte/issues/6942 effect(() => { if (value_fn) { const value = value_fn(); - let needs_deep_read = false; untrack(() => { if (payload === undefined) { payload = action(dom, value) || {}; diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 6aae3eedbb..79eed38d25 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1252,7 +1252,13 @@ export function pop(component) { * @returns {void} */ export function deep_read(value, visited = new Set()) { - if (typeof value === 'object' && value !== null && !visited.has(value)) { + if ( + typeof value === 'object' && + value !== null && + // We don't want to traverse DOM elements + !(value instanceof EventTarget) && + !visited.has(value) + ) { visited.add(value); for (let key in value) { try { From 512edaad6fbef8e934dadef4ff2ea0c26eb54d2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 21:57:26 +0000 Subject: [PATCH 07/90] Version Packages (next) (#10625) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 1 + packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 2968e1cd20..0df60db632 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -311,6 +311,7 @@ "wicked-doors-train", "wicked-hairs-cheer", "wild-foxes-wonder", + "wise-apples-care", "wise-dancers-hang", "wise-donkeys-marry", "wise-jobs-admire", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index c868ffe6f9..5b242259ff 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.0.0-next.68 + +### Patch Changes + +- fix: improve deep_read performance ([#10624](https://github.com/sveltejs/svelte/pull/10624)) + ## 5.0.0-next.67 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 1da3ab364f..cc5c26844d 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.0.0-next.67", + "version": "5.0.0-next.68", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 033bb1efbc..95399ed6f3 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.67'; +export const VERSION = '5.0.0-next.68'; export const PUBLIC_VERSION = '5'; From 83b92372c22da2f8a0f8350fa856087a7170275b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Fri, 23 Feb 2024 23:18:37 +0000 Subject: [PATCH 08/90] fix: date working in repl (#10627) --- packages/svelte/src/reactivity/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/reactivity/index.js b/packages/svelte/src/reactivity/index.js index ce865c5a3e..b68cef7be3 100644 --- a/packages/svelte/src/reactivity/index.js +++ b/packages/svelte/src/reactivity/index.js @@ -1,5 +1,5 @@ -import { source } from '../internal/client/reactivity/sources'; -import { get, set } from '../internal/client/runtime'; +import { source } from '../internal/client/reactivity/sources.js'; +import { get, set } from '../internal/client/runtime.js'; /** @type {Array} */ const read = [ From ee4b1f2c75e7a3a3701d5312c86035b1f7b60651 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 26 Feb 2024 11:48:57 +0200 Subject: [PATCH 09/90] fix test (#10634) --- .../svelte/tests/runtime-runes/samples/date/main.svelte | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/svelte/tests/runtime-runes/samples/date/main.svelte b/packages/svelte/tests/runtime-runes/samples/date/main.svelte index bbb6ae5bbe..10083615e0 100644 --- a/packages/svelte/tests/runtime-runes/samples/date/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/date/main.svelte @@ -1,12 +1,12 @@ -

getSeconds: {date.getSeconds()}
-
getMinutes: {date.getMinutes()}
-
getHours: {date.getHours()}
+
getSeconds: {date.getUTCSeconds()}
+
getMinutes: {date.getUTCMinutes()}
+
getHours: {date.getUTCHours()}
getTime: {date.getTime()}
toDateString: {date.toDateString()}
From a4a789db4dbd43c6fc39de93b4d610ae85a63423 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 26 Feb 2024 15:27:25 +0000 Subject: [PATCH 10/90] fix: improve ssr output of dynamic textarea elements (#10638) * fix: improve ssr output of dynamic textarea elements * format --- .changeset/nice-avocados-move.md | 5 +++ .../compiler/phases/2-analyze/validation.js | 16 +++++++++ .../3-transform/server/transform-server.js | 36 +++++++++---------- packages/svelte/src/internal/server/index.js | 25 +++++++++++++ .../dynamic-element-textarea/_config.js | 14 ++++++++ .../dynamic-element-textarea/main.svelte | 1 + .../_expected/client/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 13 +------ 9 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 .changeset/nice-avocados-move.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/dynamic-element-textarea/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/dynamic-element-textarea/main.svelte diff --git a/.changeset/nice-avocados-move.md b/.changeset/nice-avocados-move.md new file mode 100644 index 0000000000..49f6ed2169 --- /dev/null +++ b/.changeset/nice-avocados-move.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve ssr output of dynamic textarea elements diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 67d45226f3..b43e5bea65 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -609,6 +609,22 @@ const validation = { error(arg, 'invalid-render-spread-argument'); } } + const is_inside_textarea = context.path.find((n) => { + return ( + n.type === 'SvelteElement' && + n.name === 'svelte:element' && + n.tag.type === 'Literal' && + n.tag.value === 'textarea' + ); + }); + if (is_inside_textarea) { + error( + node, + 'invalid-tag-placement', + 'inside ', + + test({ assert, target }) { + assert.htmlEqual( + target.innerHTML, + ` + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/dynamic-element-textarea/main.svelte b/packages/svelte/tests/runtime-legacy/samples/dynamic-element-textarea/main.svelte new file mode 100644 index 0000000000..0ac8ee1e2d --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/dynamic-element-textarea/main.svelte @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js index c9ba1e7c73..4cc594e9d0 100644 --- a/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js @@ -1,4 +1,4 @@ /* index.svelte.js generated by Svelte VERSION */ import * as $ from "svelte/internal"; -export const object = $.proxy({ ok: true }); +export const object = $.proxy({ ok: true }); \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js index d7bc4c8f33..a3b619df6e 100644 --- a/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js @@ -1,4 +1,4 @@ /* index.svelte.js generated by Svelte VERSION */ import * as $ from "svelte/internal/server"; -export const object = { ok: true }; +export const object = { ok: true }; \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js index 405a452271..88a0ca6dcd 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js @@ -9,18 +9,7 @@ export default function Svelte_element($$payload, $$props) { const anchor = $.create_anchor($$payload); $$payload.out += `${anchor}`; - - if (tag) { - const anchor_1 = $.create_anchor($$payload); - - $$payload.out += `<${tag}>`; - - if (!$.VoidElements.has(tag)) { - $$payload.out += `${anchor_1}`; - $$payload.out += `${anchor_1}`; - } - } - + if (tag) $.element($$payload, tag, () => {}, () => {}); $$payload.out += `${anchor}`; $.bind_props($$props, { tag }); $.pop(); From 749d3aa4133f79306e8663010e570a0f6d542149 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:51:56 +0100 Subject: [PATCH 11/90] fix: add `$set` and `$on` methods in legacy compat mode (#10642) People could've done bind:this and called instance methods on the instance - a rare case, but not impossible. This shims $set and $on when in legacy compat mode. $destroy is never shimmed because you shouldn't manually destroy a component, ever, and there's no way to make that work in the new world. closes #10420 --- .changeset/tough-radios-punch.md | 5 ++ .../src/compiler/phases/2-analyze/index.js | 6 ++- .../3-transform/client/transform-client.js | 54 +++++++++++++++++++ packages/svelte/src/internal/client/render.js | 27 ++++++++++ .../_config.js | 17 ++++++ .../main.svelte | 16 ++++++ .../sub.svelte | 8 +++ .../03-appendix/02-breaking-changes.md | 2 +- 8 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 .changeset/tough-radios-punch.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/main.svelte create mode 100644 packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/sub.svelte diff --git a/.changeset/tough-radios-punch.md b/.changeset/tough-radios-punch.md new file mode 100644 index 0000000000..7eeaac38f0 --- /dev/null +++ b/.changeset/tough-radios-punch.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: add `$set` and `$on` methods in legacy compat mode diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index f2e2a3dec2..6a00e81f58 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -357,7 +357,11 @@ export function analyze_component(root, options) { uses_component_bindings: false, custom_element: options.customElement, inject_styles: options.css === 'injected' || !!options.customElement, - accessors: options.customElement ? true : !!options.accessors, + accessors: options.customElement + ? true + : !!options.accessors || + // because $set method needs accessors + !!options.legacy?.componentApi, reactive_statements: new Map(), binding_groups: new Map(), slot_names: new Set(), diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 16b7b476fe..ed7da2333b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -258,6 +258,60 @@ export function client_component(source, analysis, options) { } } + if (options.legacy.componentApi) { + properties.push( + b.init('$set', b.id('$.update_legacy_props')), + b.init( + '$on', + b.arrow( + [b.id('$$event_name'), b.id('$$event_cb')], + b.call( + '$.add_legacy_event_listener', + b.id('$$props'), + b.id('$$event_name'), + b.id('$$event_cb') + ) + ) + ) + ); + } else if (options.dev) { + properties.push( + b.init( + '$set', + b.thunk( + b.block([ + b.throw_error( + `The component shape you get when doing bind:this changed. Updating its properties via $set is no longer valid in Svelte 5. ` + + 'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information' + ) + ]) + ) + ), + b.init( + '$on', + b.thunk( + b.block([ + b.throw_error( + `The component shape you get when doing bind:this changed. Listening to events via $on is no longer valid in Svelte 5. ` + + 'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information' + ) + ]) + ) + ), + b.init( + '$destroy', + b.thunk( + b.block([ + b.throw_error( + `The component shape you get when doing bind:this changed. Destroying such a component via $destroy is no longer valid in Svelte 5. ` + + 'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information' + ) + ]) + ) + ) + ); + } + const push_args = [b.id('$$props'), b.literal(analysis.runes)]; if (options.dev) push_args.push(b.id(analysis.name)); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 0861acdfce..6cac0c7bad 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2929,3 +2929,30 @@ export function bubble_event($$props, event) { fn.call(this, event); } } + +/** + * Used to simulate `$on` on a component instance when `legacy.componentApi` is `true` + * @param {Record} $$props + * @param {string} event_name + * @param {Function} event_callback + */ +export function add_legacy_event_listener($$props, event_name, event_callback) { + $$props.$$events ||= {}; + $$props.$$events[event_name] ||= []; + $$props.$$events[event_name].push(event_callback); +} + +/** + * Used to simulate `$set` on a component instance when `legacy.componentApi` is `true`. + * Needs component accessors so that it can call the setter of the prop. Therefore doesn't + * work for updating props in `$$props` or `$$restProps`. + * @this {Record} + * @param {Record} $$new_props + */ +export function update_legacy_props($$new_props) { + for (const key in $$new_props) { + if (key in this) { + this[key] = $$new_props[key]; + } + } +} diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/_config.js b/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/_config.js new file mode 100644 index 0000000000..a4ab247250 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/_config.js @@ -0,0 +1,17 @@ +import { tick } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + legacy: { + componentApi: true + } + }, + html: '', + async test({ assert, target }) { + const button = target.querySelector('button'); + await button?.click(); + await tick(); + assert.htmlEqual(target.innerHTML, ''); + } +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/main.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/main.svelte new file mode 100644 index 0000000000..f3f19f60f5 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/main.svelte @@ -0,0 +1,16 @@ + + + diff --git a/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/sub.svelte b/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/sub.svelte new file mode 100644 index 0000000000..b1a6201a56 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/binding-this-legacy-component-api/sub.svelte @@ -0,0 +1,8 @@ + + + diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md index 2f804fc982..205bdd2bf2 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md @@ -70,7 +70,7 @@ import App from './App.svelte' export default app; ``` -If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component). +If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`. ### Server API changes From 77b1f2fe516f699bf916697db8805a7d3b259c37 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 26 Feb 2024 21:06:12 +0000 Subject: [PATCH 12/90] chore: remove more .at() usage from runtime (#10648) --- packages/svelte/src/internal/client/dev/ownership.js | 4 ++-- packages/svelte/src/internal/client/dom/blocks/each.js | 6 ++++-- packages/svelte/src/internal/client/operations.js | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/ownership.js b/packages/svelte/src/internal/client/dev/ownership.js index 000d6194ae..f57c8dce5d 100644 --- a/packages/svelte/src/internal/client/dev/ownership.js +++ b/packages/svelte/src/internal/client/dev/ownership.js @@ -75,8 +75,8 @@ export function mark_module_end() { const end = get_stack()?.[2]; if (end) { - // @ts-expect-error - boundaries[end.file].at(-1).end = end; + const boundaries_file = boundaries[end.file]; + boundaries_file[boundaries_file.length - 1].end = end; } } diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 9f93b6db5a..8c40d6c774 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -393,7 +393,8 @@ function reconcile_indexed_array( b_blocks[index] = block; hydrating_node = /** @type {import('../../types.js').TemplateNode} */ ( - /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling + /** @type {Node} */ (/** @type {Node} */ (fragment[fragment.length - 1]).nextSibling) + .nextSibling ); } @@ -517,7 +518,8 @@ function reconcile_tracked_array( // Get the tag of the next item in the list // The fragment array can be empty if each block has no content hydrating_node = /** @type {import('../../types.js').TemplateNode} */ ( - /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling + /** @type {Node} */ ((fragment[fragment.length - 1] || hydrating_node).nextSibling) + .nextSibling ); } diff --git a/packages/svelte/src/internal/client/operations.js b/packages/svelte/src/internal/client/operations.js index cfa7628c86..5e73eb4d1a 100644 --- a/packages/svelte/src/internal/client/operations.js +++ b/packages/svelte/src/internal/client/operations.js @@ -274,10 +274,10 @@ function capture_fragment_from_node(node) { if ( node.nodeType === 8 && /** @type {Comment} */ (node).data.startsWith('ssr:') && - current_hydration_fragment.at(-1) !== node + current_hydration_fragment[current_hydration_fragment.length - 1] !== node ) { const fragment = /** @type {Array} */ (get_hydration_fragment(node)); - const last_child = fragment.at(-1) || node; + const last_child = fragment[fragment.length - 1] || node; const target = /** @type {Node} */ (last_child.nextSibling); // @ts-ignore target.$$fragment = fragment; From 6625c1e080409e64821ff409d3db03b0cc5bf160 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:34:39 +0100 Subject: [PATCH 13/90] fix: warn when `$props` rune not called (#10655) It's a warning because even when typing it out and knowing what to do you'll always be in a state where the validation kicks in, and it would be too distracting to always see a compiler error during that short time frame. closes #10374 --- .changeset/slow-kids-sparkle.md | 5 +++++ .../src/compiler/phases/2-analyze/validation.js | 7 ++++++- packages/svelte/src/compiler/warnings.js | 5 ++++- .../samples/runes-props-not-called/_config.js | 3 +++ .../samples/runes-props-not-called/input.svelte | 3 +++ .../samples/runes-props-not-called/warnings.json | 14 ++++++++++++++ 6 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .changeset/slow-kids-sparkle.md create mode 100644 packages/svelte/tests/validator/samples/runes-props-not-called/_config.js create mode 100644 packages/svelte/tests/validator/samples/runes-props-not-called/input.svelte create mode 100644 packages/svelte/tests/validator/samples/runes-props-not-called/warnings.json diff --git a/.changeset/slow-kids-sparkle.md b/.changeset/slow-kids-sparkle.md new file mode 100644 index 0000000000..11f3ca69a8 --- /dev/null +++ b/.changeset/slow-kids-sparkle.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: warn when `$props` rune not called diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index b43e5bea65..1790e073ec 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -1002,7 +1002,12 @@ export const validation_runes = merge(validation, a11y_validators, { const init = node.init; const rune = get_rune(init, state.scope); - if (rune === null) return; + if (rune === null) { + if (init?.type === 'Identifier' && init.name === '$props' && !state.scope.get('props')) { + warn(state.analysis.warnings, node, path, 'invalid-props-declaration'); + } + return; + } const args = /** @type {import('estree').CallExpression} */ (init).arguments; diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index 2f8308e27f..20375bca76 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -27,7 +27,10 @@ const runes = { /** @param {string} name */ 'non-state-reference': (name) => `${name} is updated, but is not declared with $state(...). Changing its value will not correctly trigger updates.`, - 'derived-iife': () => `Use \`$derived.by(() => {...})\` instead of \`$derived((() => {...})());\`` + 'derived-iife': () => + `Use \`$derived.by(() => {...})\` instead of \`$derived((() => {...})());\``, + 'invalid-props-declaration': () => + `Component properties are declared using $props() in runes mode. Did you forget to call the function?` }; /** @satisfies {Warnings} */ diff --git a/packages/svelte/tests/validator/samples/runes-props-not-called/_config.js b/packages/svelte/tests/validator/samples/runes-props-not-called/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/validator/samples/runes-props-not-called/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/validator/samples/runes-props-not-called/input.svelte b/packages/svelte/tests/validator/samples/runes-props-not-called/input.svelte new file mode 100644 index 0000000000..ec13f5ca10 --- /dev/null +++ b/packages/svelte/tests/validator/samples/runes-props-not-called/input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/svelte/tests/validator/samples/runes-props-not-called/warnings.json b/packages/svelte/tests/validator/samples/runes-props-not-called/warnings.json new file mode 100644 index 0000000000..d1900a3085 --- /dev/null +++ b/packages/svelte/tests/validator/samples/runes-props-not-called/warnings.json @@ -0,0 +1,14 @@ +[ + { + "code": "invalid-props-declaration", + "message": "Component properties are declared using $props() in runes mode. Did you forget to call the function?", + "start": { + "column": 5, + "line": 2 + }, + "end": { + "column": 19, + "line": 2 + } + } +] From 3fe4940a9da82b8e95db7074c0d6171c685fc911 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 27 Feb 2024 14:55:31 +0100 Subject: [PATCH 14/90] perf: bail early when traversing non-state (#10654) This has a lot of overhead for large lists, and we can at least diminish in the "no state proxy" case by applying a sensible heuristic: - If the value passed is a state proxy, read it - If not, and if the value is an array, then bail because an array of state proxies is highly unlikely - Traverse the first level of properties of the object and look if these are state, if not bail. State proxies nested further down are highly unlikely, too part of #10637 --- .changeset/chatty-sloths-allow.md | 5 ++++ .../client/visitors/javascript-legacy.js | 2 +- packages/svelte/src/internal/client/render.js | 28 +++++++++++-------- .../svelte/src/internal/client/runtime.js | 23 +++++++++++++++ packages/svelte/src/internal/index.js | 3 +- 5 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 .changeset/chatty-sloths-allow.md diff --git a/.changeset/chatty-sloths-allow.md b/.changeset/chatty-sloths-allow.md new file mode 100644 index 0000000000..09146ea12c --- /dev/null +++ b/.changeset/chatty-sloths-allow.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +perf: bail early when traversing non-state diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js index 02f4895ae9..9ffeb1df8b 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-legacy.js @@ -170,7 +170,7 @@ export const javascript_visitors_legacy = { // If the binding is a prop, we need to deep read it because it could be fine-grained $state // from a runes-component, where mutations don't trigger an update on the prop as a whole. if (name === '$$props' || name === '$$restProps' || binding.kind === 'prop') { - serialized = b.call('$.deep_read', serialized); + serialized = b.call('$.deep_read_state', serialized); } sequence.push(serialized); diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 6cac0c7bad..fe3db6c0eb 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -44,11 +44,11 @@ import { push, pop, current_component_context, - deep_read, get, set, is_signals_recorded, - inspect_fn + inspect_fn, + deep_read_state } from './runtime.js'; import { render_effect, @@ -1950,7 +1950,7 @@ export function action(dom, action, value_fn) { // This works in legacy mode because of mutable_source being updated as a whole, but when using $state // together with actions and mutation, it wouldn't notice the change without a deep read. if (needs_deep_read) { - deep_read(value); + deep_read_state(value); } } else { untrack(() => (payload = action(dom))); @@ -2858,10 +2858,12 @@ export function init() { if (!callbacks) return; // beforeUpdate - pre_effect(() => { - observe_all(context); - callbacks.b.forEach(run); - }); + if (callbacks.b.length) { + pre_effect(() => { + observe_all(context); + callbacks.b.forEach(run); + }); + } // onMount (must run before afterUpdate) user_effect(() => { @@ -2876,10 +2878,12 @@ export function init() { }); // afterUpdate - user_effect(() => { - observe_all(context); - callbacks.a.forEach(run); - }); + if (callbacks.a.length) { + user_effect(() => { + observe_all(context); + callbacks.a.forEach(run); + }); + } } /** @@ -2892,7 +2896,7 @@ function observe_all(context) { for (const signal of context.d) get(signal); } - deep_read(context.s); + deep_read_state(context.s); } /** diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 79eed38d25..1aa116076b 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1244,6 +1244,29 @@ export function pop(component) { return component || /** @type {T} */ ({}); } +/** + * Possibly traverse an object and read all its properties so that they're all reactive in case this is `$state`. + * Does only check first level of an object for performance reasons (heuristic should be good for 99% of all cases). + * @param {any} value + * @returns {void} + */ +export function deep_read_state(value) { + if (typeof value !== 'object' || !value || value instanceof EventTarget) { + return; + } + + if (STATE_SYMBOL in value) { + deep_read(value); + } else if (!Array.isArray(value)) { + for (let key in value) { + const prop = value[key]; + if (typeof prop === 'object' && prop && STATE_SYMBOL in prop) { + deep_read(prop); + } + } + } +} + /** * Deeply traverse an object and read all its properties * so that they're all reactive in case this is `$state` diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 741391ebc6..1c232cf3f2 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -18,7 +18,8 @@ export { inspect, unwrap, freeze, - deep_read + deep_read, + deep_read_state } from './client/runtime.js'; export * from './client/dev/ownership.js'; export { await_block as await } from './client/dom/blocks/await.js'; From 99e1665ce18410c4a3266ca6cd489a53b1d7fcf3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 28 Feb 2024 09:16:31 +0000 Subject: [PATCH 15/90] feat: improve ssr html mismatch validation (#10658) * feat: improve ssr html mismatch validation * update types * Update packages/svelte/src/internal/server/index.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * Update packages/svelte/src/compiler/validate-options.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * feedback --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/hungry-singers-share.md | 5 + .../src/compiler/phases/1-parse/utils/html.js | 41 +---- .../compiler/phases/2-analyze/validation.js | 127 +------------- .../3-transform/server/transform-server.js | 18 ++ packages/svelte/src/constants.js | 161 ++++++++++++++++++ packages/svelte/src/internal/server/index.js | 71 +++++++- packages/svelte/src/legacy/legacy-client.js | 2 +- .../svelte/tests/runtime-legacy/shared.ts | 3 +- .../samples/invalid-html-ssr/Component.svelte | 1 + .../samples/invalid-html-ssr/_config.js | 41 +++++ .../samples/invalid-html-ssr/main.svelte | 7 + packages/svelte/types/index.d.ts | 2 +- 12 files changed, 313 insertions(+), 166 deletions(-) create mode 100644 .changeset/hungry-singers-share.md create mode 100644 packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/Component.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/invalid-html-ssr/main.svelte diff --git a/.changeset/hungry-singers-share.md b/.changeset/hungry-singers-share.md new file mode 100644 index 0000000000..e804144119 --- /dev/null +++ b/.changeset/hungry-singers-share.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: improve ssr html mismatch validation diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/html.js b/packages/svelte/src/compiler/phases/1-parse/utils/html.js index 1c80aa3554..3cc9a5a207 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/html.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/html.js @@ -1,3 +1,4 @@ +import { interactive_elements } from '../../../../constants.js'; import entities from './entities.js'; const windows_1252 = [ @@ -121,16 +122,6 @@ function validate_code(code) { // based on http://developers.whatwg.org/syntax.html#syntax-tag-omission -// while `input` is also an interactive element, it is never moved by the browser, so we don't need to check for it -export const interactive_elements = new Set([ - 'a', - 'button', - 'iframe', - 'embed', - 'select', - 'textarea' -]); - /** @type {Record>} */ const disallowed_contents = { li: new Set(['li']), @@ -153,36 +144,6 @@ const disallowed_contents = { th: new Set(['td', 'th', 'tr']) }; -export const disallowed_parapgraph_contents = [ - 'address', - 'article', - 'aside', - 'blockquote', - 'details', - 'div', - 'dl', - 'fieldset', - 'figcapture', - 'figure', - 'footer', - 'form', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'header', - 'hr', - 'menu', - 'nav', - 'ol', - 'pre', - 'section', - 'table', - 'ul' -]; - for (const interactive_element of interactive_elements) { disallowed_contents[interactive_element] = interactive_elements; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 1790e073ec..55d523ffdc 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -1,3 +1,8 @@ +import { + disallowed_parapgraph_contents, + interactive_elements, + is_tag_valid_with_parent +} from '../../../constants.js'; import { error } from '../../errors.js'; import { extract_identifiers, @@ -8,7 +13,6 @@ import { } from '../../utils/ast.js'; import { warn } from '../../warnings.js'; import fuzzymatch from '../1-parse/utils/fuzzymatch.js'; -import { disallowed_parapgraph_contents, interactive_elements } from '../1-parse/utils/html.js'; import { binding_properties } from '../bindings.js'; import { ContentEditableBindings, EventModifiers, SVGElements } from '../constants.js'; import { is_custom_element_node } from '../nodes.js'; @@ -226,127 +230,6 @@ function validate_slot_attribute(context, attribute) { } } -// https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags -const implied_end_tags = ['dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt']; - -/** - * @param {string} tag - * @param {string} parent_tag - * @returns {boolean} - */ -function is_tag_valid_with_parent(tag, parent_tag) { - // First, let's check if we're in an unusual parsing mode... - switch (parent_tag) { - // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect - case 'select': - return tag === 'option' || tag === 'optgroup' || tag === '#text'; - case 'optgroup': - return tag === 'option' || tag === '#text'; - // Strictly speaking, seeing an