From 012166ec3c2a4f199d0314444519096bc21d3715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20C=C3=A9zar?= <48573316+santiagocezar@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:48:01 -0300 Subject: [PATCH 01/38] fix: convert input value to number on hydration (#14349) * convert input value to number on hydration * add test * changeset --------- Co-authored-by: Rich Harris --- .changeset/wicked-grapes-flash.md | 5 +++++ .../client/dom/elements/bindings/input.js | 2 +- .../hydrate-modified-input-numeric/_config.js | 17 +++++++++++++++++ .../hydrate-modified-input-numeric/main.svelte | 6 ++++++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .changeset/wicked-grapes-flash.md create mode 100644 packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/main.svelte diff --git a/.changeset/wicked-grapes-flash.md b/.changeset/wicked-grapes-flash.md new file mode 100644 index 0000000000..75ff68a728 --- /dev/null +++ b/.changeset/wicked-grapes-flash.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: coerce value to number when hydrating range/number input with changed value diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/input.js b/packages/svelte/src/internal/client/dom/elements/bindings/input.js index e528d1699e..aec6f815a0 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/input.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/input.js @@ -45,7 +45,7 @@ export function bind_value(input, get, set = get) { // If we are hydrating and the value has since changed, then use the update value // from the input instead. if (hydrating && input.defaultValue !== input.value) { - set(input.value); + set(is_numberlike_input(input) ? to_number(input.value) : input.value); return; } diff --git a/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/_config.js b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/_config.js new file mode 100644 index 0000000000..f4b5037890 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/_config.js @@ -0,0 +1,17 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + skip_mode: ['client'], + + test({ assert, target, hydrate }) { + const input = /** @type {HTMLInputElement} */ (target.querySelector('input')); + input.value = '1'; + input.dispatchEvent(new window.Event('input')); + // Hydration shouldn't reset the value to empty + hydrate(); + flushSync(); + + assert.htmlEqual(target.innerHTML, '\n1 (number)'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/main.svelte b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/main.svelte new file mode 100644 index 0000000000..a10f3cec1e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydrate-modified-input-numeric/main.svelte @@ -0,0 +1,6 @@ + + + +{value} ({typeof value}) From 6e8267f46277592f6f6afb6f77d62d3d9d740605 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 19 Nov 2024 16:48:30 +0000 Subject: [PATCH 02/38] fix: correctly update dynamic member expressions (#14359) * fix: output template effect for svg xlink attribute * mark subtree dynamic in MemberExpression visitor * don't treat attributes and text nodes differently * Update .changeset/serious-spiders-bake.md --------- Co-authored-by: Rich Harris --- .changeset/serious-spiders-bake.md | 5 +++++ .../phases/2-analyze/visitors/Identifier.js | 13 ++++--------- .../phases/2-analyze/visitors/MemberExpression.js | 3 +++ .../samples/inline-expressions-3/_config.js | 5 +++++ .../samples/inline-expressions-3/main.svelte | 9 +++++++++ .../samples/inline-expressions-3/sprites.js | 3 +++ .../_expected/client/index.svelte.js | 2 +- 7 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 .changeset/serious-spiders-bake.md create mode 100644 packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/_config.js create mode 100644 packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/main.svelte create mode 100644 packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/sprites.js diff --git a/.changeset/serious-spiders-bake.md b/.changeset/serious-spiders-bake.md new file mode 100644 index 0000000000..b1f9d84a3f --- /dev/null +++ b/.changeset/serious-spiders-bake.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly update dynamic member expressions diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index 635f939c75..042c467df1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -126,16 +126,11 @@ export function Identifier(node, context) { } } - if (!can_inline && context.state.expression) { - context.state.expression.can_inline = false; - } + if (!can_inline) { + if (context.state.expression) { + context.state.expression.can_inline = false; + } - /** - * if the identifier is part of an expression tag of an attribute we want to check if it's inlinable - * before marking the subtree as dynamic. This is because if it's inlinable it will be inlined in the template - * directly making the whole thing actually static. - */ - if (!can_inline || !context.path.find((node) => node.type === 'Attribute')) { mark_subtree_dynamic(context.path); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js index adcc2da422..88adecbd35 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js @@ -4,6 +4,7 @@ import * as e from '../../../errors.js'; import * as w from '../../../warnings.js'; import { object } from '../../../utils/ast.js'; import { is_pure, is_safe_identifier } from './shared/utils.js'; +import { mark_subtree_dynamic } from './shared/fragment.js'; /** * @param {MemberExpression} node @@ -20,6 +21,8 @@ export function MemberExpression(node, context) { if (context.state.expression && !is_pure(node, context)) { context.state.expression.has_state = true; context.state.expression.can_inline = false; + + mark_subtree_dynamic(context.path); } if (!is_safe_identifier(node, context.state.scope)) { diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/_config.js new file mode 100644 index 0000000000..c229f97e07 --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: `
+ import { sprites } from './sprites.js' + + +
+ +
diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/sprites.js b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/sprites.js new file mode 100644 index 0000000000..055fdbd59a --- /dev/null +++ b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-3/sprites.js @@ -0,0 +1,3 @@ +export const sprites = { + a: 'test' +}; diff --git a/packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/client/index.svelte.js index 75e632239e..4622624424 100644 --- a/packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/client/index.svelte.js @@ -11,7 +11,7 @@ var root = $.template(` + {#each tiles as { x, y }} +
+ {/each} +
diff --git a/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js b/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js new file mode 100644 index 0000000000..ba0457b80e --- /dev/null +++ b/benchmarking/benchmarks/ssr/wrapper/wrapper_bench.js @@ -0,0 +1,36 @@ +import { render } from 'svelte/server'; +import { fastest_test, read_file, write } from '../../../utils.js'; +import { compile } from 'svelte/compiler'; + +const dir = `${process.cwd()}/benchmarking/benchmarks/ssr/wrapper`; + +async function compile_svelte() { + const output = compile(read_file(`${dir}/App.svelte`), { + generate: 'server' + }); + write(`${dir}/output/App.js`, output.js.code); + + const module = await import(`${dir}/output/App.js`); + + return module.default; +} + +export async function wrapper_bench() { + const App = await compile_svelte(); + // Do 3 loops to warm up JIT + for (let i = 0; i < 3; i++) { + render(App); + } + + const { timing } = await fastest_test(10, () => { + for (let i = 0; i < 100; i++) { + render(App); + } + }); + + return { + benchmark: 'wrapper_bench', + time: timing.time.toFixed(2), + gc_time: timing.gc_time.toFixed(2) + }; +} diff --git a/benchmarking/run.js b/benchmarking/run.js index 35a6ac307f..bd96b9c2dc 100644 --- a/benchmarking/run.js +++ b/benchmarking/run.js @@ -1,30 +1,53 @@ import * as $ from '../packages/svelte/src/internal/client/index.js'; -import { benchmarks } from './benchmarks.js'; +import { reactivity_benchmarks } from './benchmarks/reactivity/index.js'; +import { ssr_benchmarks } from './benchmarks/ssr/index.js'; let total_time = 0; let total_gc_time = 0; +const suites = [ + { benchmarks: reactivity_benchmarks, name: 'reactivity benchmarks' }, + { benchmarks: ssr_benchmarks, name: 'server-side rendering benchmarks' } +]; + // eslint-disable-next-line no-console -console.log('-- Benchmarking Started --'); +console.log('\x1b[1m', '-- Benchmarking Started --', '\x1b[0m'); $.push({}, true); try { - for (const benchmark of benchmarks) { - const results = await benchmark(); + for (const { benchmarks, name } of suites) { + let suite_time = 0; + let suite_gc_time = 0; + // eslint-disable-next-line no-console + console.log(`\nRunning ${name}...\n`); + + for (const benchmark of benchmarks) { + const results = await benchmark(); + // eslint-disable-next-line no-console + console.log(results); + total_time += Number(results.time); + total_gc_time += Number(results.gc_time); + suite_time += Number(results.time); + suite_gc_time += Number(results.gc_time); + } + + console.log(`\nFinished ${name}.\n`); + // eslint-disable-next-line no-console - console.log(results); - total_time += Number(results.time); - total_gc_time += Number(results.gc_time); + console.log({ + suite_time: suite_time.toFixed(2), + suite_gc_time: suite_gc_time.toFixed(2) + }); } } catch (e) { // eslint-disable-next-line no-console - console.error('-- Benchmarking Failed --'); + console.log('\x1b[1m', '\n-- Benchmarking Failed --\n', '\x1b[0m'); // eslint-disable-next-line no-console console.error(e); process.exit(1); } $.pop(); // eslint-disable-next-line no-console -console.log('-- Benchmarking Complete --'); +console.log('\x1b[1m', '\n-- Benchmarking Complete --\n', '\x1b[0m'); // eslint-disable-next-line no-console console.log({ total_time: total_time.toFixed(2), diff --git a/benchmarking/utils.js b/benchmarking/utils.js index db2fa753ee..684d2ee02b 100644 --- a/benchmarking/utils.js +++ b/benchmarking/utils.js @@ -1,5 +1,7 @@ import { performance, PerformanceObserver } from 'node:perf_hooks'; import v8 from 'v8-natives'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; // Credit to https://github.com/milomg/js-reactivity-benchmark for the logic for timing + GC tracking. @@ -96,3 +98,22 @@ export function assert(a) { throw new Error('Assertion failed'); } } + +/** + * @param {string} file + */ +export function read_file(file) { + return fs.readFileSync(file, 'utf-8').replace(/\r\n/g, '\n'); +} + +/** + * @param {string} file + * @param {string} contents + */ +export function write(file, contents) { + try { + fs.mkdirSync(path.dirname(file), { recursive: true }); + } catch {} + + fs.writeFileSync(file, contents); +} From 741106879bc60bb8b4e5daf3884032a04c803dd2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 19 Nov 2024 18:11:30 +0000 Subject: [PATCH 04/38] fix: ensure internal cloning can work circular values (#14347) * fix: ensure internal cloning can work circular values * better fixc * 'original' feels slightly clearer than 'json_instance' * use an optional parameter, so we can omit it in most cases * Update packages/svelte/src/internal/shared/clone.js Co-authored-by: Rich Harris --------- Co-authored-by: Rich Harris --- .changeset/cool-trains-yawn.md | 5 ++++ packages/svelte/src/internal/shared/clone.js | 15 ++++++++++-- .../samples/inspect-recursive-2/_config.js | 23 +++++++++++++++++++ .../samples/inspect-recursive-2/main.svelte | 23 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 .changeset/cool-trains-yawn.md create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte diff --git a/.changeset/cool-trains-yawn.md b/.changeset/cool-trains-yawn.md new file mode 100644 index 0000000000..d5198d118c --- /dev/null +++ b/.changeset/cool-trains-yawn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure internal cloning can work circular values diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js index bfdc9af263..cef14bc14f 100644 --- a/packages/svelte/src/internal/shared/clone.js +++ b/packages/svelte/src/internal/shared/clone.js @@ -49,9 +49,10 @@ export function snapshot(value, skip_warning = false) { * @param {Map>} cloned * @param {string} path * @param {string[]} paths + * @param {null | T} original The original value, if `value` was produced from a `toJSON` call * @returns {Snapshot} */ -function clone(value, cloned, path, paths) { +function clone(value, cloned, path, paths, original = null) { if (typeof value === 'object' && value !== null) { const unwrapped = cloned.get(value); if (unwrapped !== undefined) return unwrapped; @@ -63,6 +64,10 @@ function clone(value, cloned, path, paths) { const copy = /** @type {Snapshot} */ ([]); cloned.set(value, copy); + if (original !== null) { + cloned.set(original, copy); + } + for (let i = 0; i < value.length; i += 1) { copy.push(clone(value[i], cloned, DEV ? `${path}[${i}]` : path, paths)); } @@ -75,6 +80,10 @@ function clone(value, cloned, path, paths) { const copy = {}; cloned.set(value, copy); + if (original !== null) { + cloned.set(original, copy); + } + for (var key in value) { // @ts-expect-error copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths); @@ -92,7 +101,9 @@ function clone(value, cloned, path, paths) { /** @type {T & { toJSON(): any } } */ (value).toJSON(), cloned, DEV ? `${path}.toJSON()` : path, - paths + paths, + // Associate the instance with the toJSON clone + value ); } } diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js new file mode 100644 index 0000000000..ab49697195 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js @@ -0,0 +1,23 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + + async test({ assert, logs }) { + var a = { + a: {} + }; + a.a = a; + + var b = { + a: { + b: {} + } + }; + b.a.b = b; + + assert.deepEqual(logs, ['init', a, 'init', b]); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte new file mode 100644 index 0000000000..f7874d2192 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte @@ -0,0 +1,23 @@ + From 747d40833b024d2b5ea5cf0de8e6198cbd3af23c Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 19 Nov 2024 19:04:40 +0000 Subject: [PATCH 05/38] fix: ensure is_pure takes into account runes (#14333) * fix: ensure is_pure takes into account runes * feedback --- .changeset/slimy-islands-cry.md | 5 +++++ .../phases/2-analyze/visitors/shared/utils.js | 10 ++++++++- .../samples/inline-expressions-2/_config.js | 11 ---------- .../samples/inline-expressions/_config.js | 21 +++++++++++++++++++ .../samples/inline-expressions}/main.svelte | 1 + 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 .changeset/slimy-islands-cry.md delete mode 100644 packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/inline-expressions/_config.js rename packages/svelte/tests/{runtime-legacy/samples/inline-expressions-2 => runtime-runes/samples/inline-expressions}/main.svelte (87%) diff --git a/.changeset/slimy-islands-cry.md b/.changeset/slimy-islands-cry.md new file mode 100644 index 0000000000..f7f34b60e1 --- /dev/null +++ b/.changeset/slimy-islands-cry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure is_pure takes into account $effect.tracking() diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 2dec4361c8..8698174c6b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -4,8 +4,10 @@ /** @import { Scope } from '../../../scope' */ /** @import { NodeLike } from '../../../../errors.js' */ import * as e from '../../../../errors.js'; -import { extract_identifiers, object } from '../../../../utils/ast.js'; +import { extract_identifiers } from '../../../../utils/ast.js'; import * as w from '../../../../warnings.js'; +import * as b from '../../../../utils/builders.js'; +import { get_rune } from '../../../scope.js'; /** * @param {AssignmentExpression | UpdateExpression} node @@ -184,6 +186,7 @@ export function is_pure(node, context) { if (node.type === 'Literal') { return true; } + if (node.type === 'CallExpression') { if (!is_pure(node.callee, context)) { return false; @@ -195,10 +198,15 @@ export function is_pure(node, context) { } return true; } + if (node.type !== 'Identifier' && node.type !== 'MemberExpression') { return false; } + if (get_rune(b.call(node), context.state.scope) === '$effect.tracking') { + return false; + } + /** @type {Expression | Super | null} */ let left = node; while (left.type === 'MemberExpression') { diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js b/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js deleted file mode 100644 index 1869540b2c..0000000000 --- a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/_config.js +++ /dev/null @@ -1,11 +0,0 @@ -import { test } from '../../test'; - -export default test({ - html: ` -

Without text expression: 7.36

-

With text expression: 7.36

-

With text expression and function call: 7.36

-

With text expression and property access: 4

-

Hello name!

-

4

` -}); diff --git a/packages/svelte/tests/runtime-runes/samples/inline-expressions/_config.js b/packages/svelte/tests/runtime-runes/samples/inline-expressions/_config.js new file mode 100644 index 0000000000..c5d4a75379 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/inline-expressions/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` +

Without text expression: 7.36

+

With text expression: 7.36

+

With text expression and function call: 7.36

+

With text expression and property access: 4

+

Hello name!

+

4

+

Tracking: true

`, + + ssrHtml: ` +

Without text expression: 7.36

+

With text expression: 7.36

+

With text expression and function call: 7.36

+

With text expression and property access: 4

+

Hello name!

+

4

+

Tracking: false

` +}); diff --git a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/inline-expressions/main.svelte similarity index 87% rename from packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte rename to packages/svelte/tests/runtime-runes/samples/inline-expressions/main.svelte index 78a325dcc4..38a97415d2 100644 --- a/packages/svelte/tests/runtime-legacy/samples/inline-expressions-2/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/inline-expressions/main.svelte @@ -4,3 +4,4 @@

With text expression and property access: {"test".length}

Hello {('name').toUpperCase().toLowerCase()}!

{"test".length}

+

Tracking: {$effect.tracking()}

From 53cc60085e099352c04732f0653af03b63af624c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:05:50 -0500 Subject: [PATCH 06/38] Version Packages (#14362) Co-authored-by: github-actions[bot] --- .changeset/cool-trains-yawn.md | 5 ----- .changeset/serious-spiders-bake.md | 5 ----- .changeset/slimy-islands-cry.md | 5 ----- .changeset/wicked-grapes-flash.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/cool-trains-yawn.md delete mode 100644 .changeset/serious-spiders-bake.md delete mode 100644 .changeset/slimy-islands-cry.md delete mode 100644 .changeset/wicked-grapes-flash.md diff --git a/.changeset/cool-trains-yawn.md b/.changeset/cool-trains-yawn.md deleted file mode 100644 index d5198d118c..0000000000 --- a/.changeset/cool-trains-yawn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure internal cloning can work circular values diff --git a/.changeset/serious-spiders-bake.md b/.changeset/serious-spiders-bake.md deleted file mode 100644 index b1f9d84a3f..0000000000 --- a/.changeset/serious-spiders-bake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly update dynamic member expressions diff --git a/.changeset/slimy-islands-cry.md b/.changeset/slimy-islands-cry.md deleted file mode 100644 index f7f34b60e1..0000000000 --- a/.changeset/slimy-islands-cry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: ensure is_pure takes into account $effect.tracking() diff --git a/.changeset/wicked-grapes-flash.md b/.changeset/wicked-grapes-flash.md deleted file mode 100644 index 75ff68a728..0000000000 --- a/.changeset/wicked-grapes-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: coerce value to number when hydrating range/number input with changed value diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index d36307c80c..f01f519150 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.2.4 + +### Patch Changes + +- fix: ensure internal cloning can work circular values ([#14347](https://github.com/sveltejs/svelte/pull/14347)) + +- fix: correctly update dynamic member expressions ([#14359](https://github.com/sveltejs/svelte/pull/14359)) + +- fix: ensure is_pure takes into account $effect.tracking() ([#14333](https://github.com/sveltejs/svelte/pull/14333)) + +- fix: coerce value to number when hydrating range/number input with changed value ([#14349](https://github.com/sveltejs/svelte/pull/14349)) + ## 5.2.3 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 50319c77aa..f6ba053398 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.2.3", + "version": "5.2.4", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index b79e9a2891..3a728f3d14 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.2.3'; +export const VERSION = '5.2.4'; export const PUBLIC_VERSION = '5'; From ce471310c49695550f8422ade46025f9818106c2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Nov 2024 16:31:22 -0500 Subject: [PATCH 07/38] chore: remove some unused code (#14363) --- .../visitors/ExportNamedDeclaration.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js index 784d2fdd88..547f6ab9c7 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportNamedDeclaration.js @@ -57,23 +57,5 @@ export function ExportNamedDeclaration(node, context) { } } } - - if (!context.state.ast_type /* .svelte.js module */ || context.state.ast_type === 'module') { - for (const specified of node.specifiers) { - if (specified.local.type !== 'Identifier') continue; - - const binding = context.state.scope.get(specified.local.name); - - if (!binding) continue; - - if (binding.kind === 'derived') { - e.derived_invalid_export(node); - } - - if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) { - e.state_invalid_export(node); - } - } - } } } From 32a14538051a9ab382b467d4c5f772114809b7d1 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Tue, 19 Nov 2024 22:38:36 +0100 Subject: [PATCH 08/38] fix: include method definitions in class private fields (#14365) * fix: include method definitions in class private fields * Update packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/_config.js --------- Co-authored-by: Rich Harris --- .changeset/unlucky-icons-sit.md | 5 +++++ .../phases/3-transform/client/visitors/ClassBody.js | 2 +- .../_config.js | 3 +++ .../main.svelte | 8 ++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .changeset/unlucky-icons-sit.md create mode 100644 packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/main.svelte diff --git a/.changeset/unlucky-icons-sit.md b/.changeset/unlucky-icons-sit.md new file mode 100644 index 0000000000..7fc14cf435 --- /dev/null +++ b/.changeset/unlucky-icons-sit.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: include method definitions in class private fields diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js index 2d832b9df1..11a524d33c 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/ClassBody.js @@ -28,7 +28,7 @@ export function ClassBody(node, context) { for (const definition of node.body) { if ( - definition.type === 'PropertyDefinition' && + (definition.type === 'PropertyDefinition' || definition.type === 'MethodDefinition') && (definition.key.type === 'Identifier' || definition.key.type === 'PrivateIdentifier' || definition.key.type === 'Literal') diff --git a/packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/_config.js b/packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/main.svelte new file mode 100644 index 0000000000..92f2540498 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-disabinguate-private-method-definition/main.svelte @@ -0,0 +1,8 @@ + From 4c98c2e4a6aaa54650cc834e109a0e2dabd5a574 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:52:03 +0100 Subject: [PATCH 09/38] chore: consolidate checks for never-static attributes (#14372) We're using string checks in various places, better to have it encapsulated in one function --- .../phases/2-analyze/visitors/RegularElement.js | 6 ++---- .../3-transform/client/visitors/RegularElement.js | 4 ++-- .../3-transform/client/visitors/shared/fragment.js | 3 ++- packages/svelte/src/utils.js | 11 +++++++++++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js index 60bd1dd0c5..d5964f9ae1 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js @@ -1,6 +1,6 @@ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types' */ -import { is_mathml, is_svg, is_void } from '../../../../utils.js'; +import { cannot_be_set_statically, is_mathml, is_svg, is_void } from '../../../../utils.js'; import { is_tag_valid_with_ancestor, is_tag_valid_with_parent @@ -77,9 +77,7 @@ export function RegularElement(node, context) { if ( node.attributes.some( - (attribute) => - attribute.type === 'Attribute' && - (attribute.name === 'autofocus' || attribute.name === 'muted') + (attribute) => attribute.type === 'Attribute' && cannot_be_set_statically(attribute.name) ) ) { mark_subtree_dynamic(context.path); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 455327a712..19948464d2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -5,6 +5,7 @@ /** @import { Scope } from '../../../scope' */ import { escape_html } from '../../../../../escaping.js'; import { + cannot_be_set_statically, is_boolean_attribute, is_dom_property, is_load_error_element, @@ -262,8 +263,7 @@ export function RegularElement(node, context) { if ( !is_custom_element && - attribute.name !== 'autofocus' && - attribute.name !== 'muted' && + !cannot_be_set_statically(attribute.name) && (attribute.value === true || is_text_attribute(attribute)) ) { const name = get_attribute_name(node, attribute); diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js index ac6e0f8f9f..102988781a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/fragment.js @@ -3,6 +3,7 @@ /** @import { Scope } from '../../../../scope.js' */ /** @import { ComponentContext } from '../../types' */ import { escape_html } from '../../../../../../escaping.js'; +import { cannot_be_set_statically } from '../../../../../../utils.js'; import { is_event_attribute } from '../../../../../utils/ast.js'; import * as b from '../../../../../utils/builders.js'; import { build_template_chunk, build_update } from './utils.js'; @@ -142,7 +143,7 @@ function is_static_element(node) { return false; } - if (attribute.name === 'autofocus' || attribute.name === 'muted') { + if (cannot_be_set_statically(attribute.name)) { return false; } diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 60ec364e6f..84c7ca1efb 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -222,6 +222,17 @@ export function is_dom_property(name) { return DOM_PROPERTIES.includes(name); } +const NON_STATIC_PROPERTIES = ['autofocus', 'muted']; + +/** + * Returns `true` if the given attribute cannot be set through the template + * string, i.e. needs some kind of JavaScript handling to work. + * @param {string} name + */ +export function cannot_be_set_statically(name) { + return NON_STATIC_PROPERTIES.includes(name); +} + /** * Subset of delegated events which should be passive by default. * These two are already passive via browser defaults on window, document and body. From 4dfa0e31fe70174538ca47efe1b276ca13b2cd80 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:14:33 +0100 Subject: [PATCH 10/38] fix: tighten up `export default` validation (#14368) through #14363 I noticed our `export default` validation wasn't quite right: - missed checking for derived/state exports - the "cannot have a default export" error was only thrown if you did `export default` from the instance script, but it shouldn't matter from which component part you export it; it's never ok --- .changeset/new-houses-roll.md | 5 ++++ .../visitors/ExportDefaultDeclaration.js | 9 +++++-- .../2-analyze/visitors/ExportSpecifier.js | 25 ++----------------- .../phases/2-analyze/visitors/shared/utils.js | 21 +++++++++++++++- .../_config.js | 10 ++++++++ .../main.svelte.js | 5 ++++ .../export-default-state-indirect/_config.js | 10 ++++++++ .../main.svelte.js | 7 ++++++ .../samples/default-export-module/errors.json | 14 +++++++++++ .../default-export-module/input.svelte | 3 +++ 10 files changed, 83 insertions(+), 26 deletions(-) create mode 100644 .changeset/new-houses-roll.md create mode 100644 packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/_config.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/main.svelte.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/_config.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/main.svelte.js create mode 100644 packages/svelte/tests/validator/samples/default-export-module/errors.json create mode 100644 packages/svelte/tests/validator/samples/default-export-module/input.svelte diff --git a/.changeset/new-houses-roll.md b/.changeset/new-houses-roll.md new file mode 100644 index 0000000000..a2b8370e06 --- /dev/null +++ b/.changeset/new-houses-roll.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: tighten up `export default` validation diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js index 0a7461f155..768f1c6305 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportDefaultDeclaration.js @@ -1,13 +1,18 @@ -/** @import { ExportDefaultDeclaration, Node } from 'estree' */ +/** @import { ExportDefaultDeclaration } from 'estree' */ /** @import { Context } from '../types' */ import * as e from '../../../errors.js'; +import { validate_export } from './shared/utils.js'; /** * @param {ExportDefaultDeclaration} node * @param {Context} context */ export function ExportDefaultDeclaration(node, context) { - if (context.state.ast_type === 'instance') { + if (!context.state.ast_type /* .svelte.js module */) { + if (node.declaration.type === 'Identifier') { + validate_export(node, context.state.scope, node.declaration.name); + } + } else { e.module_illegal_default_export(node); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js index d0d1ccf932..cfb24970de 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExportSpecifier.js @@ -1,8 +1,6 @@ -/** @import { ExportSpecifier, Node } from 'estree' */ -/** @import { Binding } from '#compiler' */ +/** @import { ExportSpecifier } from 'estree' */ /** @import { Context } from '../types' */ -/** @import { Scope } from '../../scope' */ -import * as e from '../../../errors.js'; +import { validate_export } from './shared/utils.js'; /** * @param {ExportSpecifier} node @@ -30,22 +28,3 @@ export function ExportSpecifier(node, context) { validate_export(node, context.state.scope, local_name); } } - -/** - * - * @param {Node} node - * @param {Scope} scope - * @param {string} name - */ -function validate_export(node, scope, name) { - const binding = scope.get(name); - if (!binding) return; - - if (binding.kind === 'derived') { - e.derived_invalid_export(node); - } - - if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) { - e.state_invalid_export(node); - } -} diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 8698174c6b..e265637c40 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -1,4 +1,4 @@ -/** @import { AssignmentExpression, Expression, Literal, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ +/** @import { AssignmentExpression, Expression, Literal, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */ /** @import { AST, Binding } from '#compiler' */ /** @import { AnalysisState, Context } from '../../types' */ /** @import { Scope } from '../../../scope' */ @@ -263,3 +263,22 @@ export function validate_identifier_name(binding, function_depth) { } } } + +/** + * Checks that the exported name is not a derived or reassigned state variable. + * @param {Node} node + * @param {Scope} scope + * @param {string} name + */ +export function validate_export(node, scope, name) { + const binding = scope.get(name); + if (!binding) return; + + if (binding.kind === 'derived') { + e.derived_invalid_export(node); + } + + if ((binding.kind === 'state' || binding.kind === 'raw_state') && binding.reassigned) { + e.state_invalid_export(node); + } +} diff --git a/packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/_config.js b/packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/_config.js new file mode 100644 index 0000000000..4a36211769 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'derived_invalid_export', + message: + 'Cannot export derived state from a module. To expose the current derived value, export a function returning its value', + position: [61, 83] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/main.svelte.js new file mode 100644 index 0000000000..64a794492b --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-default-derived-state-indirect/main.svelte.js @@ -0,0 +1,5 @@ +let count = $state(0); + +const double = $derived(count * 2); + +export default double; diff --git a/packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/_config.js b/packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/_config.js new file mode 100644 index 0000000000..99e4faec25 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/_config.js @@ -0,0 +1,10 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'state_invalid_export', + message: + "Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties", + position: [93, 118] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/main.svelte.js new file mode 100644 index 0000000000..8e52f76533 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-default-state-indirect/main.svelte.js @@ -0,0 +1,7 @@ +let primitive = $state('nope'); + +export function update_primitive() { + primitive = 'yep'; +} + +export default primitive; diff --git a/packages/svelte/tests/validator/samples/default-export-module/errors.json b/packages/svelte/tests/validator/samples/default-export-module/errors.json new file mode 100644 index 0000000000..9fd2bb7df5 --- /dev/null +++ b/packages/svelte/tests/validator/samples/default-export-module/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "module_illegal_default_export", + "message": "A component cannot have a default export", + "start": { + "line": 2, + "column": 1 + }, + "end": { + "line": 2, + "column": 19 + } + } +] diff --git a/packages/svelte/tests/validator/samples/default-export-module/input.svelte b/packages/svelte/tests/validator/samples/default-export-module/input.svelte new file mode 100644 index 0000000000..2aeeabb2e1 --- /dev/null +++ b/packages/svelte/tests/validator/samples/default-export-module/input.svelte @@ -0,0 +1,3 @@ + From 7bd1cdf4271078c977cc39497cbe545a9de4dd62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Crozatier?= <48696601+fcrozatier@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:14:56 +0100 Subject: [PATCH 11/38] feat: add content-visibility: auto state change event (#14373) * add contentvisibilityautostatechange event * changeset * Update .changeset/kind-horses-lay.md * Update .changeset/kind-horses-lay.md --------- Co-authored-by: Rich Harris --- .changeset/kind-horses-lay.md | 5 +++++ packages/svelte/elements.d.ts | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 .changeset/kind-horses-lay.md diff --git a/.changeset/kind-horses-lay.md b/.changeset/kind-horses-lay.md new file mode 100644 index 0000000000..d08bdde6c4 --- /dev/null +++ b/.changeset/kind-horses-lay.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +add `contentvisibilityautostatechange` event to element definitions diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 8746b29e25..8b2d388bf8 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -60,6 +60,10 @@ export type AnimationEventHandler = EventHandler = EventHandler; export type MessageEventHandler = EventHandler; export type ToggleEventHandler = EventHandler; +export type ContentVisibilityAutoStateChangeEventHandler = EventHandler< + ContentVisibilityAutoStateChangeEvent, + T +>; export type FullAutoFill = | AutoFill @@ -157,6 +161,20 @@ export interface DOMAttributes { ontoggle?: ToggleEventHandler | undefined | null; ontogglecapture?: ToggleEventHandler | undefined | null; + // Content visibility Events + 'on:contentvisibilityautostatechange'?: + | ContentVisibilityAutoStateChangeEventHandler + | undefined + | null; + oncontentvisibilityautostatechange?: + | ContentVisibilityAutoStateChangeEventHandler + | undefined + | null; + oncontentvisibilityautostatechangecapture?: + | ContentVisibilityAutoStateChangeEventHandler + | undefined + | null; + // Keyboard Events 'on:keydown'?: KeyboardEventHandler | undefined | null; onkeydown?: KeyboardEventHandler | undefined | null; From 811c8d32ebcd38501ec5acf1eabe461693bc24d7 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 20 Nov 2024 15:19:17 +0000 Subject: [PATCH 12/38] fix: correctly handle srcObject attribute on video elements (#14369) * fix: correctly handle srcObject attribute on video elements * remove side-effect * side-effects agin * side-effects agin * better fix --- .changeset/bright-jokes-bow.md | 5 +++++ packages/svelte/src/utils.js | 6 ++++-- .../samples/video-src-object/_config.js | 12 ++++++++++++ .../samples/video-src-object/main.svelte | 10 ++++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 .changeset/bright-jokes-bow.md create mode 100644 packages/svelte/tests/runtime-runes/samples/video-src-object/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/video-src-object/main.svelte diff --git a/.changeset/bright-jokes-bow.md b/.changeset/bright-jokes-bow.md new file mode 100644 index 0000000000..f70076b869 --- /dev/null +++ b/.changeset/bright-jokes-bow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly handle srcObject attribute on video elements diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 84c7ca1efb..919660fd6a 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -192,7 +192,8 @@ const ATTRIBUTE_ALIASES = { ismap: 'isMap', nomodule: 'noModule', playsinline: 'playsInline', - readonly: 'readOnly' + readonly: 'readOnly', + srcobject: 'srcObject' }; /** @@ -212,7 +213,8 @@ const DOM_PROPERTIES = [ 'readOnly', 'value', 'inert', - 'volume' + 'volume', + 'srcObject' ]; /** diff --git a/packages/svelte/tests/runtime-runes/samples/video-src-object/_config.js b/packages/svelte/tests/runtime-runes/samples/video-src-object/_config.js new file mode 100644 index 0000000000..a31b98cac6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/video-src-object/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + test({ assert, target }) { + const video = target.querySelector('video'); + + // @ts-ignore + assert.deepEqual(video?.srcObject, {}); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/video-src-object/main.svelte b/packages/svelte/tests/runtime-runes/samples/video-src-object/main.svelte new file mode 100644 index 0000000000..f4059b028a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/video-src-object/main.svelte @@ -0,0 +1,10 @@ + + + + From f616c2205399090965d6cf11e9e9b7d5c542fafc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:30:25 -0500 Subject: [PATCH 13/38] Version Packages (#14366) Co-authored-by: github-actions[bot] --- .changeset/bright-jokes-bow.md | 5 ----- .changeset/kind-horses-lay.md | 5 ----- .changeset/new-houses-roll.md | 5 ----- .changeset/unlucky-icons-sit.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/bright-jokes-bow.md delete mode 100644 .changeset/kind-horses-lay.md delete mode 100644 .changeset/new-houses-roll.md delete mode 100644 .changeset/unlucky-icons-sit.md diff --git a/.changeset/bright-jokes-bow.md b/.changeset/bright-jokes-bow.md deleted file mode 100644 index f70076b869..0000000000 --- a/.changeset/bright-jokes-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly handle srcObject attribute on video elements diff --git a/.changeset/kind-horses-lay.md b/.changeset/kind-horses-lay.md deleted file mode 100644 index d08bdde6c4..0000000000 --- a/.changeset/kind-horses-lay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -add `contentvisibilityautostatechange` event to element definitions diff --git a/.changeset/new-houses-roll.md b/.changeset/new-houses-roll.md deleted file mode 100644 index a2b8370e06..0000000000 --- a/.changeset/new-houses-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: tighten up `export default` validation diff --git a/.changeset/unlucky-icons-sit.md b/.changeset/unlucky-icons-sit.md deleted file mode 100644 index 7fc14cf435..0000000000 --- a/.changeset/unlucky-icons-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: include method definitions in class private fields diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index f01f519150..f5b581ab0e 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.2.5 + +### Patch Changes + +- fix: correctly handle srcObject attribute on video elements ([#14369](https://github.com/sveltejs/svelte/pull/14369)) + +- add `contentvisibilityautostatechange` event to element definitions ([#14373](https://github.com/sveltejs/svelte/pull/14373)) + +- fix: tighten up `export default` validation ([#14368](https://github.com/sveltejs/svelte/pull/14368)) + +- fix: include method definitions in class private fields ([#14365](https://github.com/sveltejs/svelte/pull/14365)) + ## 5.2.4 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f6ba053398..2205990fff 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.2.4", + "version": "5.2.5", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 3a728f3d14..5014c88702 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.2.4'; +export const VERSION = '5.2.5'; export const PUBLIC_VERSION = '5'; From 9d12fd1a01d7020855122a8bfb5a35a3dd4e1c8f Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 20 Nov 2024 17:46:53 +0000 Subject: [PATCH 14/38] chore: remove template expression inlining (#14374) * chore: remove template expression inlining * missed some * fix * feedback * feedback * Update packages/svelte/src/compiler/phases/3-transform/client/utils.js Co-authored-by: Rich Harris * Update packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js Co-authored-by: Rich Harris * Update packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js Co-authored-by: Rich Harris * Update packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js Co-authored-by: Rich Harris * Update packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js Co-authored-by: Rich Harris * Update packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js Co-authored-by: Rich Harris * fix * Update .changeset/calm-mice-perform.md Co-authored-by: Rich Harris --------- Co-authored-by: Rich Harris --- .changeset/calm-mice-perform.md | 5 + .../phases/2-analyze/visitors/Attribute.js | 9 +- .../2-analyze/visitors/CallExpression.js | 2 - .../2-analyze/visitors/ExpressionTag.js | 5 + .../phases/2-analyze/visitors/Identifier.js | 17 +--- .../2-analyze/visitors/MemberExpression.js | 3 - .../2-analyze/visitors/RegularElement.js | 10 +- .../visitors/TaggedTemplateExpression.js | 1 - .../phases/3-transform/client/utils.js | 10 +- .../3-transform/client/visitors/Fragment.js | 10 +- .../client/visitors/RegularElement.js | 72 ++++----------- .../client/visitors/shared/fragment.js | 65 +++---------- .../client/visitors/shared/utils.js | 92 ++++++++----------- packages/svelte/src/compiler/phases/nodes.js | 3 +- packages/svelte/src/compiler/types/index.d.ts | 2 - packages/svelte/src/internal/client/index.js | 1 - .../samples/slot-non-identifier/output.svelte | 2 +- .../_expected/client/index.svelte.js | 17 ---- .../_expected/server/index.svelte.js | 12 --- .../samples/inline-module-vars/index.svelte | 20 ---- 20 files changed, 97 insertions(+), 261 deletions(-) create mode 100644 .changeset/calm-mice-perform.md delete mode 100644 packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/client/index.svelte.js delete mode 100644 packages/svelte/tests/snapshot/samples/inline-module-vars/_expected/server/index.svelte.js delete mode 100644 packages/svelte/tests/snapshot/samples/inline-module-vars/index.svelte diff --git a/.changeset/calm-mice-perform.md b/.changeset/calm-mice-perform.md new file mode 100644 index 0000000000..f43c7fcf0e --- /dev/null +++ b/.changeset/calm-mice-perform.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: remove template expression inlining diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js index ca0ea55d1f..6c050d966a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -1,7 +1,7 @@ /** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */ /** @import { AST, DelegatedEvent, SvelteNode } from '#compiler' */ /** @import { Context } from '../types' */ -import { is_boolean_attribute, is_capture_event, is_delegated } from '../../../../utils.js'; +import { cannot_be_set_statically, is_capture_event, is_delegated } from '../../../../utils.js'; import { get_attribute_chunks, get_attribute_expression, @@ -30,12 +30,12 @@ export function Attribute(node, context) { } } - if (node.name.startsWith('on')) { + if (is_event_attribute(node)) { mark_subtree_dynamic(context.path); } - if (parent.type === 'RegularElement' && is_boolean_attribute(node.name.toLowerCase())) { - node.metadata.expression.can_inline = false; + if (cannot_be_set_statically(node.name)) { + mark_subtree_dynamic(context.path); } if (node.value !== true) { @@ -51,7 +51,6 @@ export function Attribute(node, context) { node.metadata.expression.has_state ||= chunk.metadata.expression.has_state; node.metadata.expression.has_call ||= chunk.metadata.expression.has_call; - node.metadata.expression.can_inline &&= chunk.metadata.expression.can_inline; } if (is_event_attribute(node)) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js index fe3f638a69..c7ade4856b 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js @@ -179,8 +179,6 @@ export function CallExpression(node, context) { if (!is_pure(node.callee, context) || context.state.expression.dependencies.size > 0) { context.state.expression.has_call = true; context.state.expression.has_state = true; - context.state.expression.can_inline = false; - mark_subtree_dynamic(context.path); } } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js index f59b7fc569..32c8d2ca36 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/ExpressionTag.js @@ -2,6 +2,7 @@ /** @import { Context } from '../types' */ import { is_tag_valid_with_parent } from '../../../../html-tree-validation.js'; import * as e from '../../../errors.js'; +import { mark_subtree_dynamic } from './shared/fragment.js'; /** * @param {AST.ExpressionTag} node @@ -14,5 +15,9 @@ export function ExpressionTag(node, context) { } } + // TODO ideally we wouldn't do this here, we'd just do it on encountering + // an `Identifier` within the tag. But we currently need to handle `{42}` etc + mark_subtree_dynamic(context.path); + context.next({ ...context.state, expression: node.metadata.expression }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index 042c467df1..79dccd5a7c 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -1,4 +1,5 @@ /** @import { Expression, Identifier } from 'estree' */ +/** @import { EachBlock } from '#compiler' */ /** @import { Context } from '../types' */ import is_reference from 'is-reference'; import { should_proxy } from '../../3-transform/client/utils.js'; @@ -19,6 +20,8 @@ export function Identifier(node, context) { return; } + mark_subtree_dynamic(context.path); + // If we are using arguments outside of a function, then throw an error if ( node.name === 'arguments' && @@ -84,12 +87,6 @@ export function Identifier(node, context) { } } - // no binding means global, and we can't inline e.g. `{location}` - // because it could change between component renders. if there _is_ a - // binding and it is outside module scope, the expression cannot - // be inlined (TODO allow inlining in more cases - e.g. primitive consts) - let can_inline = !!binding && !binding.scope.parent && binding.kind === 'normal'; - if (binding) { if (context.state.expression) { context.state.expression.dependencies.add(binding); @@ -125,12 +122,4 @@ export function Identifier(node, context) { w.reactive_declaration_module_script_dependency(node); } } - - if (!can_inline) { - if (context.state.expression) { - context.state.expression.can_inline = false; - } - - mark_subtree_dynamic(context.path); - } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js index 88adecbd35..1cc20c96da 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js @@ -20,9 +20,6 @@ export function MemberExpression(node, context) { if (context.state.expression && !is_pure(node, context)) { context.state.expression.has_state = true; - context.state.expression.can_inline = false; - - mark_subtree_dynamic(context.path); } if (!is_safe_identifier(node, context.state.scope)) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js index d5964f9ae1..7d1c4aaeaa 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/RegularElement.js @@ -1,6 +1,6 @@ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types' */ -import { cannot_be_set_statically, is_mathml, is_svg, is_void } from '../../../../utils.js'; +import { is_mathml, is_svg, is_void } from '../../../../utils.js'; import { is_tag_valid_with_ancestor, is_tag_valid_with_parent @@ -75,14 +75,6 @@ export function RegularElement(node, context) { node.attributes.push(create_attribute('value', child.start, child.end, [child])); } - if ( - node.attributes.some( - (attribute) => attribute.type === 'Attribute' && cannot_be_set_statically(attribute.name) - ) - ) { - mark_subtree_dynamic(context.path); - } - const binding = context.state.scope.get(node.name); if ( binding !== null && diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js index 724b9af311..eacb8a342a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/TaggedTemplateExpression.js @@ -10,7 +10,6 @@ export function TaggedTemplateExpression(node, context) { if (context.state.expression && !is_pure(node.tag, context)) { context.state.expression.has_call = true; context.state.expression.has_state = true; - context.state.expression.can_inline = false; } if (node.tag.type === 'Identifier') { diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 910f173f79..0b49d18ee9 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -3,16 +3,16 @@ /** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */ /** @import { Analysis } from '../../types.js' */ /** @import { Scope } from '../../scope.js' */ +import * as b from '../../../utils/builders.js'; +import { extract_identifiers, is_simple_expression } from '../../../utils/ast.js'; import { - PROPS_IS_BINDABLE, - PROPS_IS_IMMUTABLE, PROPS_IS_LAZY_INITIAL, + PROPS_IS_IMMUTABLE, PROPS_IS_RUNES, - PROPS_IS_UPDATED + PROPS_IS_UPDATED, + PROPS_IS_BINDABLE } from '../../../../constants.js'; import { dev } from '../../../state.js'; -import { extract_identifiers, is_simple_expression } from '../../../utils/ast.js'; -import * as b from '../../../utils/builders.js'; import { get_value } from './visitors/shared/declarations.js'; /** diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js index 111516ce0a..0e6ea29614 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/Fragment.js @@ -141,14 +141,14 @@ export function Fragment(node, context) { const id = b.id(context.state.scope.generate('fragment')); const use_space_template = - trimmed.every((node) => node.type === 'Text' || node.type === 'ExpressionTag') && - trimmed.some((node) => node.type === 'ExpressionTag' && !node.metadata.expression.can_inline); + trimmed.some((node) => node.type === 'ExpressionTag') && + trimmed.every((node) => node.type === 'Text' || node.type === 'ExpressionTag'); if (use_space_template) { // special case — we can use `$.text` instead of creating a unique template const id = b.id(context.state.scope.generate('text')); - process_children(trimmed, () => id, null, { + process_children(trimmed, () => id, false, { ...context, state }); @@ -158,12 +158,12 @@ export function Fragment(node, context) { } else { if (is_standalone) { // no need to create a template, we can just use the existing block's anchor - process_children(trimmed, () => b.id('$$anchor'), null, { ...context, state }); + process_children(trimmed, () => b.id('$$anchor'), false, { ...context, state }); } else { /** @type {(is_text: boolean) => Expression} */ const expression = (is_text) => b.call('$.first_child', id, is_text && b.true); - process_children(trimmed, expression, null, { ...context, state }); + process_children(trimmed, expression, false, { ...context, state }); let flags = TEMPLATE_FRAGMENT; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js index 19948464d2..85df92e8bf 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js @@ -1,9 +1,8 @@ -/** @import { Expression, ExpressionStatement, Identifier, Literal, MemberExpression, ObjectExpression, Statement } from 'estree' */ +/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */ /** @import { AST } from '#compiler' */ /** @import { SourceLocation } from '#shared' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */ /** @import { Scope } from '../../../scope' */ -import { escape_html } from '../../../../../escaping.js'; import { cannot_be_set_statically, is_boolean_attribute, @@ -11,6 +10,7 @@ import { is_load_error_element, is_void } from '../../../../../utils.js'; +import { escape_html } from '../../../../../escaping.js'; import { dev, is_ignored, locator } from '../../../../state.js'; import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js'; import * as b from '../../../../utils/builders.js'; @@ -18,13 +18,12 @@ import { is_custom_element_node } from '../../../nodes.js'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; import { build_getter, create_derived } from '../utils.js'; import { + get_attribute_name, build_attribute_value, build_class_directives, - build_set_attributes, build_style_directives, - get_attribute_name + build_set_attributes } from './shared/element.js'; -import { visit_event_attribute } from './shared/events.js'; import { process_children } from './shared/fragment.js'; import { build_render_statement, @@ -32,6 +31,7 @@ import { build_update, build_update_assignment } from './shared/utils.js'; +import { visit_event_attribute } from './shared/events.js'; /** * @param {AST.RegularElement} node @@ -352,32 +352,30 @@ export function RegularElement(node, context) { // special case — if an element that only contains text, we don't need // to descend into it if the text is non-reactive - const is_text = trimmed.every((node) => node.type === 'Text' || node.type === 'ExpressionTag'); - // in the rare case that we have static text that can't be inlined // (e.g. `{location}`), set `textContent` programmatically const use_text_content = - is_text && + trimmed.every((node) => node.type === 'Text' || node.type === 'ExpressionTag') && trimmed.every((node) => node.type === 'Text' || !node.metadata.expression.has_state) && - trimmed.some((node) => node.type === 'ExpressionTag' && !node.metadata.expression.can_inline); + trimmed.some((node) => node.type === 'ExpressionTag'); if (use_text_content) { - let { value } = build_template_chunk(trimmed, context.visit, child_state); - child_state.init.push( - b.stmt(b.assignment('=', b.member(context.state.node, 'textContent'), value)) + b.stmt( + b.assignment( + '=', + b.member(context.state.node, 'textContent'), + build_template_chunk(trimmed, context.visit, child_state).value + ) + ) ); } else { /** @type {Expression} */ let arg = context.state.node; // If `hydrate_node` is set inside the element, we need to reset it - // after the element has been hydrated (we don't need to reset if it's been inlined) - let needs_reset = !trimmed.every( - (node) => - node.type === 'Text' || - (node.type === 'ExpressionTag' && node.metadata.expression.can_inline) - ); + // after the element has been hydrated + let needs_reset = trimmed.some((node) => node.type !== 'Text'); // The same applies if it's a `