From 587c1b1b6879c64574be15dd1bd24d3c3747b7f0 Mon Sep 17 00:00:00 2001 From: paoloricciuti Date: Sun, 13 Apr 2025 10:49:51 +0200 Subject: [PATCH 1/2] fix: correctly validate head snippets on the server --- .changeset/dirty-zebras-do.md | 5 +++ packages/svelte/src/internal/server/dev.js | 8 +++- .../svelte/src/internal/server/payload.js | 41 +++++++++++++------ .../head-payload-validation/_config.js | 11 +++++ .../head-payload-validation/main.svelte | 7 ++++ 5 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 .changeset/dirty-zebras-do.md create mode 100644 packages/svelte/tests/runtime-runes/samples/head-payload-validation/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/head-payload-validation/main.svelte diff --git a/.changeset/dirty-zebras-do.md b/.changeset/dirty-zebras-do.md new file mode 100644 index 000000000000..f0b266b09caa --- /dev/null +++ b/.changeset/dirty-zebras-do.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly validate head snippets on the server diff --git a/packages/svelte/src/internal/server/dev.js b/packages/svelte/src/internal/server/dev.js index 34849196b7b6..157f22f929c9 100644 --- a/packages/svelte/src/internal/server/dev.js +++ b/packages/svelte/src/internal/server/dev.js @@ -6,7 +6,7 @@ import { } from '../../html-tree-validation.js'; import { current_component } from './context.js'; import { invalid_snippet_arguments } from '../shared/errors.js'; -import { Payload } from './payload.js'; +import { HeadPayload, Payload } from './payload.js'; /** * @typedef {{ @@ -105,7 +105,11 @@ export function pop_element() { * @param {Payload} payload */ export function validate_snippet_args(payload) { - if (typeof payload !== 'object' || !(payload instanceof Payload)) { + if ( + typeof payload !== 'object' || + // for some reason typescript consider the type of payload as never after the first instanceof + !(payload instanceof Payload || /** @type {any} */ (payload) instanceof HeadPayload) + ) { invalid_snippet_arguments(); } } diff --git a/packages/svelte/src/internal/server/payload.js b/packages/svelte/src/internal/server/payload.js index 03bcaf492ea9..08a7c30be170 100644 --- a/packages/svelte/src/internal/server/payload.js +++ b/packages/svelte/src/internal/server/payload.js @@ -1,16 +1,36 @@ +export class HeadPayload { + /** @type {Set<{ hash: string; code: string }>} */ + css = new Set(); + out = ''; + uid = () => ''; + title = ''; + + constructor(css = new Set(), out = '', title = '', uid = () => '') { + this.css = css; + this.out = out; + this.title = title; + this.uid = uid; + } + + clone() { + const payload = new HeadPayload(); + + payload.out = this.out; + payload.css = new Set(this.css); + payload.title = this.title; + payload.uid = this.uid; + + return payload; + } +} + export class Payload { /** @type {Set<{ hash: string; code: string }>} */ css = new Set(); out = ''; uid = () => ''; - head = { - /** @type {Set<{ hash: string; code: string }>} */ - css: new Set(), - title: '', - out: '', - uid: () => '' - }; + head = new HeadPayload(); constructor(id_prefix = '') { this.uid = props_id_generator(id_prefix); @@ -30,12 +50,7 @@ export function copy_payload({ out, css, head, uid }) { payload.css = new Set(css); payload.uid = uid; - payload.head = { - title: head.title, - out: head.out, - css: new Set(head.css), - uid: head.uid - }; + payload.head = head.clone(); return payload; } diff --git a/packages/svelte/tests/runtime-runes/samples/head-payload-validation/_config.js b/packages/svelte/tests/runtime-runes/samples/head-payload-validation/_config.js new file mode 100644 index 000000000000..7c609205df9b --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/head-payload-validation/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true + }, + mode: ['server'], + async test({ errors, assert }) { + assert.equal(errors, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/head-payload-validation/main.svelte b/packages/svelte/tests/runtime-runes/samples/head-payload-validation/main.svelte new file mode 100644 index 000000000000..7eb31d3a9ee9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/head-payload-validation/main.svelte @@ -0,0 +1,7 @@ +{#snippet head()} + Cool +{/snippet} + + + {@render head()} + \ No newline at end of file From 4f121e337e801581dd871ca67a606c242065cc8c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Apr 2025 06:06:18 -0400 Subject: [PATCH 2/2] put the logic in copy_payload so it gets treeshaken in most cases --- packages/svelte/src/internal/server/payload.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/packages/svelte/src/internal/server/payload.js b/packages/svelte/src/internal/server/payload.js index 08a7c30be170..8df5787ba4a7 100644 --- a/packages/svelte/src/internal/server/payload.js +++ b/packages/svelte/src/internal/server/payload.js @@ -11,17 +11,6 @@ export class HeadPayload { this.title = title; this.uid = uid; } - - clone() { - const payload = new HeadPayload(); - - payload.out = this.out; - payload.css = new Set(this.css); - payload.title = this.title; - payload.uid = this.uid; - - return payload; - } } export class Payload { @@ -50,7 +39,11 @@ export function copy_payload({ out, css, head, uid }) { payload.css = new Set(css); payload.uid = uid; - payload.head = head.clone(); + payload.head = new HeadPayload(); + payload.head.out = head.out; + payload.head.css = new Set(head.css); + payload.head.title = head.title; + payload.head.uid = head.uid; return payload; }