From 65fdcec55df996f06bf5cc162a1e16e9ed35cddf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Dec 2024 15:33:07 -0500 Subject: [PATCH 001/389] chore: regenerate types (#14592) --- packages/svelte/types/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index f6b5b21f80..0d761919a8 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1671,6 +1671,7 @@ declare module 'svelte/motion' { * * * ``` + * @since 5.8.0 */ export class Spring { constructor(value: T, options?: SpringOpts); From 98286349b23e88be77533df577bc8998bc15cc46 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:40:52 -0500 Subject: [PATCH 002/389] Version Packages (#14593) Co-authored-by: github-actions[bot] --- .changeset/large-humans-report.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/large-humans-report.md diff --git a/.changeset/large-humans-report.md b/.changeset/large-humans-report.md deleted file mode 100644 index 6ba09ef148..0000000000 --- a/.changeset/large-humans-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: reinstate missing prefersReducedMotion export diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 8613474924..4031110fa7 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.8.1 + +### Patch Changes + +- fix: reinstate missing prefersReducedMotion export ([#14586](https://github.com/sveltejs/svelte/pull/14586)) + ## 5.8.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 97dedffbfc..c751a598db 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.8.0", + "version": "5.8.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index f0574202b5..3061318cb0 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.8.0'; +export const VERSION = '5.8.1'; export const PUBLIC_VERSION = '5'; From 08e2cf25b03707f4ca6e44a1e6ca0480190c14a1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Dec 2024 16:08:58 -0500 Subject: [PATCH 003/389] chore: fix sandbox (#14596) --- playgrounds/sandbox/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playgrounds/sandbox/package.json b/playgrounds/sandbox/package.json index 944e79621e..654a517c9f 100644 --- a/playgrounds/sandbox/package.json +++ b/playgrounds/sandbox/package.json @@ -6,7 +6,7 @@ "scripts": { "prepare": "node scripts/create-app-svelte.js", "dev": "vite --host", - "ssr": "node ./ssr-dev.js", + "ssr": "node --conditions=development ./ssr-dev.js", "build": "vite build --outDir dist/client && vite build --outDir dist/server --ssr ssr-prod.js", "prod": "npm run build && node dist/server/ssr-prod", "preview": "vite preview" From 1a0b822f4826b82454962a94c072519530b7c126 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 6 Dec 2024 17:41:41 -0500 Subject: [PATCH 004/389] fix: always run `if` block code the first time (#14597) * fix: always run `if` block code the first time * fix --- .changeset/rare-cheetahs-laugh.md | 5 +++++ .../svelte/src/internal/client/dom/blocks/if.js | 8 ++++---- .../samples/if-block-mismatch-2/_config.js | 15 +++++++++++++++ .../samples/if-block-mismatch-2/_expected.html | 1 + .../samples/if-block-mismatch-2/main.svelte | 13 +++++++++++++ .../samples/if-block-mismatch/_expected.html | 1 + .../samples/if-block-mismatch/main.svelte | 2 +- 7 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 .changeset/rare-cheetahs-laugh.md create mode 100644 packages/svelte/tests/hydration/samples/if-block-mismatch-2/_config.js create mode 100644 packages/svelte/tests/hydration/samples/if-block-mismatch-2/_expected.html create mode 100644 packages/svelte/tests/hydration/samples/if-block-mismatch-2/main.svelte create mode 100644 packages/svelte/tests/hydration/samples/if-block-mismatch/_expected.html diff --git a/.changeset/rare-cheetahs-laugh.md b/.changeset/rare-cheetahs-laugh.md new file mode 100644 index 0000000000..2637b50b3c --- /dev/null +++ b/.changeset/rare-cheetahs-laugh.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: always run `if` block code the first time diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 6a880f28bc..36790c05c1 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -9,7 +9,7 @@ import { set_hydrating } from '../hydration.js'; import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js'; -import { HYDRATION_START_ELSE } from '../../../../constants.js'; +import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js'; /** * @param {TemplateNode} node @@ -30,8 +30,8 @@ export function if_block(node, fn, elseif = false) { /** @type {Effect | null} */ var alternate_effect = null; - /** @type {boolean | null} */ - var condition = null; + /** @type {UNINITIALIZED | boolean | null} */ + var condition = UNINITIALIZED; var flags = elseif ? EFFECT_TRANSPARENT : 0; @@ -54,7 +54,7 @@ export function if_block(node, fn, elseif = false) { if (hydrating) { const is_else = /** @type {Comment} */ (anchor).data === HYDRATION_START_ELSE; - if (condition === is_else) { + if (!!condition === is_else) { // Hydration mismatch: remove everything inside the anchor and start fresh. // This could happen with `{#if browser}...{/if}`, for example anchor = remove_nodes(); diff --git a/packages/svelte/tests/hydration/samples/if-block-mismatch-2/_config.js b/packages/svelte/tests/hydration/samples/if-block-mismatch-2/_config.js new file mode 100644 index 0000000000..ffde9ee303 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/if-block-mismatch-2/_config.js @@ -0,0 +1,15 @@ +import { test } from '../../test'; + +// even {#if true} or {#if false} should be kept as an if block, because it could be {#if browser} originally, +// which is then different between client and server. +export default test({ + server_props: { + condition: true + }, + + props: { + condition: false + }, + + trim_whitespace: false +}); diff --git a/packages/svelte/tests/hydration/samples/if-block-mismatch-2/_expected.html b/packages/svelte/tests/hydration/samples/if-block-mismatch-2/_expected.html new file mode 100644 index 0000000000..08a3809de9 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/if-block-mismatch-2/_expected.html @@ -0,0 +1 @@ +
hello diff --git a/packages/svelte/tests/hydration/samples/if-block-mismatch-2/main.svelte b/packages/svelte/tests/hydration/samples/if-block-mismatch-2/main.svelte new file mode 100644 index 0000000000..3136406698 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/if-block-mismatch-2/main.svelte @@ -0,0 +1,13 @@ + + +{#if condition} + +{/if} + +
+ +
+ +hello diff --git a/packages/svelte/tests/hydration/samples/if-block-mismatch/_expected.html b/packages/svelte/tests/hydration/samples/if-block-mismatch/_expected.html new file mode 100644 index 0000000000..79cf2cf35f --- /dev/null +++ b/packages/svelte/tests/hydration/samples/if-block-mismatch/_expected.html @@ -0,0 +1 @@ +

foo

diff --git a/packages/svelte/tests/hydration/samples/if-block-mismatch/main.svelte b/packages/svelte/tests/hydration/samples/if-block-mismatch/main.svelte index c6799c5f95..552c434101 100644 --- a/packages/svelte/tests/hydration/samples/if-block-mismatch/main.svelte +++ b/packages/svelte/tests/hydration/samples/if-block-mismatch/main.svelte @@ -2,7 +2,7 @@ let { condition } = $props(); -{#if true} +{#if condition}

foo

{:else}

bar

From 5771b455c0caf860bb063499feb2100acad4fbd5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sun, 8 Dec 2024 12:28:37 +0000 Subject: [PATCH 005/389] feat: add support for bind getter/setters (#14307) * feat: add support for bind getters/setters * different direction * oops * oops * build * add changeset and tests * move validation * add comment * build * bind:group error * simpler to just keep it as a SequenceExpression * fix * lint * fix * move validation to visitor * fix * no longer needed * fix * parser changes are no longer needed * simplify * simplify * update messages * docs --------- Co-authored-by: Rich Harris Co-authored-by: Simon Holthausen --- .changeset/slimy-donkeys-hang.md | 5 + .../docs/03-template-syntax/11-bind.md | 24 ++ .../98-reference/.generated/compile-errors.md | 14 +- .../messages/compile-errors/template.md | 10 +- packages/svelte/src/compiler/errors.js | 23 +- .../2-analyze/visitors/BindDirective.js | 214 ++++++++++-------- .../client/visitors/BindDirective.js | 74 +++--- .../client/visitors/RegularElement.js | 10 +- .../client/visitors/shared/component.js | 113 +++++---- .../client/visitors/shared/utils.js | 12 +- .../server/visitors/shared/component.js | 52 +++-- .../server/visitors/shared/element.js | 18 +- .../src/compiler/types/legacy-nodes.d.ts | 5 +- .../svelte/src/compiler/types/template.d.ts | 5 +- .../samples/bind-getter-setter-2/Child.svelte | 11 + .../samples/bind-getter-setter-2/_config.js | 9 + .../samples/bind-getter-setter-2/main.svelte | 11 + .../samples/bind-getter-setter/Child.svelte | 12 + .../samples/bind-getter-setter/_config.js | 20 ++ .../samples/bind-getter-setter/main.svelte | 16 ++ .../bind_group_invalid_expression/errors.json | 14 ++ .../input.svelte | 12 + packages/svelte/types/index.d.ts | 4 +- 23 files changed, 471 insertions(+), 217 deletions(-) create mode 100644 .changeset/slimy-donkeys-hang.md create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte create mode 100644 packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json create mode 100644 packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte diff --git a/.changeset/slimy-donkeys-hang.md b/.changeset/slimy-donkeys-hang.md new file mode 100644 index 0000000000..d63141660e --- /dev/null +++ b/.changeset/slimy-donkeys-hang.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +feat: add support for bind getters/setters diff --git a/documentation/docs/03-template-syntax/11-bind.md b/documentation/docs/03-template-syntax/11-bind.md index fe3cf727e2..90046c8c45 100644 --- a/documentation/docs/03-template-syntax/11-bind.md +++ b/documentation/docs/03-template-syntax/11-bind.md @@ -12,10 +12,34 @@ The general syntax is `bind:property={expression}`, where `expression` is an _lv ``` + Svelte creates an event listener that updates the bound value. If an element already has a listener for the same event, that listener will be fired before the bound value is updated. Most bindings are _two-way_, meaning that changes to the value will affect the element and vice versa. A few bindings are _readonly_, meaning that changing their value will have no effect on the element. +## Function bindings + +You can also use `bind:property={get, set}`, where `get` and `set` are functions, allowing you to perform validation and transformation: + +```svelte + value, + (v) => value = v.toLowerCase()} +/> +``` + +In the case of readonly bindings like [dimension bindings](#Dimensions), the `get` value should be `null`: + +```svelte +
...
+``` + +> [!NOTE] +> Function bindings are available in Svelte 5.9.0 and newer. + ## `` A `bind:value` directive on an `` element binds the input's `value` property: diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index 3bd162d8d7..d726d25fa1 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -78,10 +78,16 @@ Sequence expressions are not allowed as attribute/directive values in runes mode Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression ``` +### bind_group_invalid_expression + +``` +`bind:group` can only bind to an Identifier or MemberExpression +``` + ### bind_invalid_expression ``` -Can only bind to an Identifier or MemberExpression +Can only bind to an Identifier or MemberExpression or a `{get, set}` pair ``` ### bind_invalid_name @@ -94,6 +100,12 @@ Can only bind to an Identifier or MemberExpression `bind:%name%` is not a valid binding. %explanation% ``` +### bind_invalid_parens + +``` +`bind:%name%={get, set}` must not have surrounding parentheses +``` + ### bind_invalid_target ``` diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index 9621a6457b..02961b61fc 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -50,9 +50,13 @@ > Attribute values containing `{...}` must be enclosed in quote marks, unless the value only contains the expression +## bind_group_invalid_expression + +> `bind:group` can only bind to an Identifier or MemberExpression + ## bind_invalid_expression -> Can only bind to an Identifier or MemberExpression +> Can only bind to an Identifier or MemberExpression or a `{get, set}` pair ## bind_invalid_name @@ -60,6 +64,10 @@ > `bind:%name%` is not a valid binding. %explanation% +## bind_invalid_parens + +> `bind:%name%={get, set}` must not have surrounding parentheses + ## bind_invalid_target > `bind:%name%` can only be used with %elements% diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 901ea1983e..1a4525ef5c 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -716,12 +716,21 @@ export function attribute_unquoted_sequence(node) { } /** - * Can only bind to an Identifier or MemberExpression + * `bind:group` can only bind to an Identifier or MemberExpression + * @param {null | number | NodeLike} node + * @returns {never} + */ +export function bind_group_invalid_expression(node) { + e(node, "bind_group_invalid_expression", "`bind:group` can only bind to an Identifier or MemberExpression"); +} + +/** + * Can only bind to an Identifier or MemberExpression or a `{get, set}` pair * @param {null | number | NodeLike} node * @returns {never} */ export function bind_invalid_expression(node) { - e(node, "bind_invalid_expression", "Can only bind to an Identifier or MemberExpression"); + e(node, "bind_invalid_expression", "Can only bind to an Identifier or MemberExpression or a `{get, set}` pair"); } /** @@ -735,6 +744,16 @@ export function bind_invalid_name(node, name, explanation) { e(node, "bind_invalid_name", explanation ? `\`bind:${name}\` is not a valid binding. ${explanation}` : `\`bind:${name}\` is not a valid binding`); } +/** + * `bind:%name%={get, set}` must not have surrounding parentheses + * @param {null | number | NodeLike} node + * @param {string} name + * @returns {never} + */ +export function bind_invalid_parens(node, name) { + e(node, "bind_invalid_parens", `\`bind:${name}={get, set}\` must not have surrounding parentheses`); +} + /** * `bind:%name%` can only be used with %elements% * @param {null | number | NodeLike} node diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js index 5b56d9ddac..b062365380 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js @@ -17,102 +17,6 @@ import { is_content_editable_binding, is_svg } from '../../../../utils.js'; * @param {Context} context */ export function BindDirective(node, context) { - validate_no_const_assignment(node, node.expression, context.state.scope, true); - - const assignee = node.expression; - const left = object(assignee); - - if (left === null) { - e.bind_invalid_expression(node); - } - - const binding = context.state.scope.get(left.name); - - if (assignee.type === 'Identifier') { - // reassignment - if ( - node.name !== 'this' && // bind:this also works for regular variables - (!binding || - (binding.kind !== 'state' && - binding.kind !== 'raw_state' && - binding.kind !== 'prop' && - binding.kind !== 'bindable_prop' && - binding.kind !== 'each' && - binding.kind !== 'store_sub' && - !binding.updated)) // TODO wut? - ) { - e.bind_invalid_value(node.expression); - } - - if (context.state.analysis.runes && binding?.kind === 'each') { - e.each_item_invalid_assignment(node); - } - - if (binding?.kind === 'snippet') { - e.snippet_parameter_assignment(node); - } - } - - if (node.name === 'group') { - if (!binding) { - throw new Error('Cannot find declaration for bind:group'); - } - - // Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group, - // i.e. one of their declarations is referenced in the binding. This allows group bindings to work - // correctly when referencing a variable declared in an EachBlock by using the index of the each block - // entries as keys. - const each_blocks = []; - const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression); - let ids = expression_ids; - - let i = context.path.length; - while (i--) { - const parent = context.path[i]; - - if (parent.type === 'EachBlock') { - const references = ids.filter((id) => parent.metadata.declarations.has(id.name)); - - if (references.length > 0) { - parent.metadata.contains_group_binding = true; - - each_blocks.push(parent); - ids = ids.filter((id) => !references.includes(id)); - ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]); - } - } - } - - // The identifiers that make up the binding expression form they key for the binding group. - // If the same identifiers in the same order are used in another bind:group, they will be in the same group. - // (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j, - // but this is a limitation of the current static analysis we do; it also never worked in Svelte 4) - const bindings = expression_ids.map((id) => context.state.scope.get(id.name)); - let group_name; - - outer: for (const [[key, b], group] of context.state.analysis.binding_groups) { - if (b.length !== bindings.length || key !== keypath) continue; - for (let i = 0; i < bindings.length; i++) { - if (bindings[i] !== b[i]) continue outer; - } - group_name = group; - } - - if (!group_name) { - group_name = context.state.scope.root.unique('binding_group'); - context.state.analysis.binding_groups.set([keypath, bindings], group_name); - } - - node.metadata = { - binding_group_name: group_name, - parent_each_blocks: each_blocks - }; - } - - if (binding?.kind === 'each' && binding.metadata?.inside_rest) { - w.bind_invalid_each_rest(binding.node, binding.node.name); - } - const parent = context.path.at(-1); if ( @@ -218,5 +122,123 @@ export function BindDirective(node, context) { } } + // When dealing with bind getters/setters skip the specific binding validation + // Group bindings aren't supported for getter/setters so we don't need to handle + // the metadata + if (node.expression.type === 'SequenceExpression') { + if (node.name === 'group') { + e.bind_group_invalid_expression(node); + } + + let i = /** @type {number} */ (node.expression.start); + while (context.state.analysis.source[--i] !== '{') { + if (context.state.analysis.source[i] === '(') { + e.bind_invalid_parens(node, node.name); + } + } + + if (node.expression.expressions.length !== 2) { + e.bind_invalid_expression(node); + } + + return; + } + + validate_no_const_assignment(node, node.expression, context.state.scope, true); + + const assignee = node.expression; + const left = object(assignee); + + if (left === null) { + e.bind_invalid_expression(node); + } + + const binding = context.state.scope.get(left.name); + + if (assignee.type === 'Identifier') { + // reassignment + if ( + node.name !== 'this' && // bind:this also works for regular variables + (!binding || + (binding.kind !== 'state' && + binding.kind !== 'raw_state' && + binding.kind !== 'prop' && + binding.kind !== 'bindable_prop' && + binding.kind !== 'each' && + binding.kind !== 'store_sub' && + !binding.updated)) // TODO wut? + ) { + e.bind_invalid_value(node.expression); + } + + if (context.state.analysis.runes && binding?.kind === 'each') { + e.each_item_invalid_assignment(node); + } + + if (binding?.kind === 'snippet') { + e.snippet_parameter_assignment(node); + } + } + + if (node.name === 'group') { + if (!binding) { + throw new Error('Cannot find declaration for bind:group'); + } + + // Traverse the path upwards and find all EachBlocks who are (indirectly) contributing to bind:group, + // i.e. one of their declarations is referenced in the binding. This allows group bindings to work + // correctly when referencing a variable declared in an EachBlock by using the index of the each block + // entries as keys. + const each_blocks = []; + const [keypath, expression_ids] = extract_all_identifiers_from_expression(node.expression); + let ids = expression_ids; + + let i = context.path.length; + while (i--) { + const parent = context.path[i]; + + if (parent.type === 'EachBlock') { + const references = ids.filter((id) => parent.metadata.declarations.has(id.name)); + + if (references.length > 0) { + parent.metadata.contains_group_binding = true; + + each_blocks.push(parent); + ids = ids.filter((id) => !references.includes(id)); + ids.push(...extract_all_identifiers_from_expression(parent.expression)[1]); + } + } + } + + // The identifiers that make up the binding expression form they key for the binding group. + // If the same identifiers in the same order are used in another bind:group, they will be in the same group. + // (there's an edge case where `bind:group={a[i]}` will be in a different group than `bind:group={a[j]}` even when i == j, + // but this is a limitation of the current static analysis we do; it also never worked in Svelte 4) + const bindings = expression_ids.map((id) => context.state.scope.get(id.name)); + let group_name; + + outer: for (const [[key, b], group] of context.state.analysis.binding_groups) { + if (b.length !== bindings.length || key !== keypath) continue; + for (let i = 0; i < bindings.length; i++) { + if (bindings[i] !== b[i]) continue outer; + } + group_name = group; + } + + if (!group_name) { + group_name = context.state.scope.root.unique('binding_group'); + context.state.analysis.binding_groups.set([keypath, bindings], group_name); + } + + node.metadata = { + binding_group_name: group_name, + parent_each_blocks: each_blocks + }; + } + + if (binding?.kind === 'each' && binding.metadata?.inside_rest) { + w.bind_invalid_each_rest(binding.node, binding.node.name); + } + context.next(); } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js index 79969240c7..f129e059f2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/BindDirective.js @@ -1,4 +1,4 @@ -/** @import { CallExpression, Expression, MemberExpression } from 'estree' */ +/** @import { CallExpression, Expression, MemberExpression, Pattern } from 'estree' */ /** @import { AST, SvelteNode } from '#compiler' */ /** @import { ComponentContext } from '../types' */ import { dev, is_ignored } from '../../../../state.js'; @@ -13,41 +13,50 @@ import { build_bind_this, validate_binding } from './shared/utils.js'; * @param {ComponentContext} context */ export function BindDirective(node, context) { - const expression = node.expression; + const expression = /** @type {Expression} */ (context.visit(node.expression)); const property = binding_properties[node.name]; const parent = /** @type {SvelteNode} */ (context.path.at(-1)); - if ( - dev && - context.state.analysis.runes && - expression.type === 'MemberExpression' && - (node.name !== 'this' || - context.path.some( - ({ type }) => - type === 'IfBlock' || type === 'EachBlock' || type === 'AwaitBlock' || type === 'KeyBlock' - )) && - !is_ignored(node, 'binding_property_non_reactive') - ) { - validate_binding( - context.state, - node, - /**@type {MemberExpression} */ (context.visit(expression)) - ); - } + let get, set; - const get = b.thunk(/** @type {Expression} */ (context.visit(expression))); + if (expression.type === 'SequenceExpression') { + [get, set] = expression.expressions; + } else { + if ( + dev && + context.state.analysis.runes && + expression.type === 'MemberExpression' && + (node.name !== 'this' || + context.path.some( + ({ type }) => + type === 'IfBlock' || + type === 'EachBlock' || + type === 'AwaitBlock' || + type === 'KeyBlock' + )) && + !is_ignored(node, 'binding_property_non_reactive') + ) { + validate_binding(context.state, node, expression); + } - /** @type {Expression | undefined} */ - let set = b.unthunk( - b.arrow( - [b.id('$$value')], - /** @type {Expression} */ (context.visit(b.assignment('=', expression, b.id('$$value')))) - ) - ); + get = b.thunk(expression); - if (get === set) { - set = undefined; + /** @type {Expression | undefined} */ + set = b.unthunk( + b.arrow( + [b.id('$$value')], + /** @type {Expression} */ ( + context.visit( + b.assignment('=', /** @type {Pattern} */ (node.expression), b.id('$$value')) + ) + ) + ) + ); + + if (get === set) { + set = undefined; + } } /** @type {CallExpression} */ @@ -162,7 +171,7 @@ export function BindDirective(node, context) { break; case 'this': - call = build_bind_this(expression, context.state.node, context); + call = build_bind_this(node.expression, context.state.node, context); break; case 'textContent': @@ -213,10 +222,7 @@ export function BindDirective(node, context) { if (value !== undefined) { group_getter = b.thunk( - b.block([ - b.stmt(build_attribute_value(value, context).value), - b.return(/** @type {Expression} */ (context.visit(expression))) - ]) + b.block([b.stmt(build_attribute_value(value, context).value), b.return(expression)]) ); } } 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 3c0be589c3..2c2c287f12 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 @@ -450,6 +450,11 @@ function setup_select_synchronization(value_binding, context) { if (context.state.analysis.runes) return; let bound = value_binding.expression; + + if (bound.type === 'SequenceExpression') { + return; + } + while (bound.type === 'MemberExpression') { bound = /** @type {Identifier | MemberExpression} */ (bound.object); } @@ -484,10 +489,7 @@ function setup_select_synchronization(value_binding, context) { b.call( '$.template_effect', b.thunk( - b.block([ - b.stmt(/** @type {Expression} */ (context.visit(value_binding.expression))), - b.stmt(invalidator) - ]) + b.block([b.stmt(/** @type {Expression} */ (context.visit(bound))), b.stmt(invalidator)]) ) ) ) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js index aa7be93cb5..c94c1e1b0e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/component.js @@ -1,4 +1,4 @@ -/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Property, Statement } from 'estree' */ +/** @import { BlockStatement, Expression, ExpressionStatement, Identifier, MemberExpression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ /** @import { AST, TemplateNode } from '#compiler' */ /** @import { ComponentContext } from '../../types.js' */ import { dev, is_ignored } from '../../../../../state.js'; @@ -44,7 +44,7 @@ export function build_component(node, component_name, context, anchor = context. /** @type {Property[]} */ const custom_css_props = []; - /** @type {Identifier | MemberExpression | null} */ + /** @type {Identifier | MemberExpression | SequenceExpression | null} */ let bind_this = null; /** @type {ExpressionStatement[]} */ @@ -174,60 +174,83 @@ export function build_component(node, component_name, context, anchor = context. } else if (attribute.type === 'BindDirective') { const expression = /** @type {Expression} */ (context.visit(attribute.expression)); - if ( - dev && - expression.type === 'MemberExpression' && - context.state.analysis.runes && - !is_ignored(node, 'binding_property_non_reactive') - ) { - validate_binding(context.state, attribute, expression); + if (dev && attribute.name !== 'this' && attribute.expression.type !== 'SequenceExpression') { + const left = object(attribute.expression); + let binding; + + if (left?.type === 'Identifier') { + binding = context.state.scope.get(left.name); + } + + // Only run ownership addition on $state fields. + // Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`, + // but that feels so much of an edge case that it doesn't warrant a perf hit for the common case. + if (binding?.kind !== 'derived' && binding?.kind !== 'raw_state') { + binding_initializers.push( + b.stmt( + b.call( + b.id('$.add_owner_effect'), + b.thunk(expression), + b.id(component_name), + is_ignored(node, 'ownership_invalid_binding') && b.true + ) + ) + ); + } } - if (attribute.name === 'this') { - bind_this = attribute.expression; + if (expression.type === 'SequenceExpression') { + if (attribute.name === 'this') { + bind_this = attribute.expression; + } else { + const [get, set] = expression.expressions; + const get_id = b.id(context.state.scope.generate('bind_get')); + const set_id = b.id(context.state.scope.generate('bind_set')); + + context.state.init.push(b.var(get_id, get)); + context.state.init.push(b.var(set_id, set)); + + push_prop(b.get(attribute.name, [b.return(b.call(get_id))])); + push_prop(b.set(attribute.name, [b.stmt(b.call(set_id, b.id('$$value')))])); + } } else { - if (dev) { - const left = object(attribute.expression); - let binding; - if (left?.type === 'Identifier') { - binding = context.state.scope.get(left.name); - } - // Only run ownership addition on $state fields. - // Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`, - // but that feels so much of an edge case that it doesn't warrant a perf hit for the common case. - if (binding?.kind !== 'derived' && binding?.kind !== 'raw_state') { - binding_initializers.push( - b.stmt( - b.call( - b.id('$.add_owner_effect'), - b.thunk(expression), - b.id(component_name), - is_ignored(node, 'ownership_invalid_binding') && b.true - ) - ) + if ( + dev && + expression.type === 'MemberExpression' && + context.state.analysis.runes && + !is_ignored(node, 'binding_property_non_reactive') + ) { + validate_binding(context.state, attribute, expression); + } + + if (attribute.name === 'this') { + bind_this = attribute.expression; + } else { + const is_store_sub = + attribute.expression.type === 'Identifier' && + context.state.scope.get(attribute.expression.name)?.kind === 'store_sub'; + + // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them + if (is_store_sub) { + push_prop( + b.get(attribute.name, [b.stmt(b.call('$.mark_store_binding')), b.return(expression)]), + true ); + } else { + push_prop(b.get(attribute.name, [b.return(expression)]), true); } - } - const is_store_sub = - attribute.expression.type === 'Identifier' && - context.state.scope.get(attribute.expression.name)?.kind === 'store_sub'; + const assignment = b.assignment( + '=', + /** @type {Pattern} */ (attribute.expression), + b.id('$$value') + ); - // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them - if (is_store_sub) { push_prop( - b.get(attribute.name, [b.stmt(b.call('$.mark_store_binding')), b.return(expression)]), + b.set(attribute.name, [b.stmt(/** @type {Expression} */ (context.visit(assignment)))]), true ); - } else { - push_prop(b.get(attribute.name, [b.return(expression)]), true); } - - const assignment = b.assignment('=', attribute.expression, b.id('$$value')); - push_prop( - b.set(attribute.name, [b.stmt(/** @type {Expression} */ (context.visit(assignment)))]), - true - ); } } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js index 59beacbb0c..11f76aa025 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js @@ -1,4 +1,4 @@ -/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, Statement, Super } from 'estree' */ +/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, SequenceExpression, Statement, Super } from 'estree' */ /** @import { AST, SvelteNode } from '#compiler' */ /** @import { ComponentClientTransformState } from '../../types' */ import { walk } from 'zimmerframe'; @@ -143,11 +143,16 @@ export function build_update_assignment(state, id, init, value, update) { /** * Serializes `bind:this` for components and elements. - * @param {Identifier | MemberExpression} expression + * @param {Identifier | MemberExpression | SequenceExpression} expression * @param {Expression} value * @param {import('zimmerframe').Context} context */ export function build_bind_this(expression, value, { state, visit }) { + if (expression.type === 'SequenceExpression') { + const [get, set] = /** @type {SequenceExpression} */ (visit(expression)).expressions; + return b.call('$.bind_this', value, set, get); + } + /** @type {Identifier[]} */ const ids = []; @@ -224,6 +229,9 @@ export function build_bind_this(expression, value, { state, visit }) { * @param {MemberExpression} expression */ export function validate_binding(state, binding, expression) { + if (binding.expression.type === 'SequenceExpression') { + return; + } // If we are referencing a $store.foo then we don't need to add validation const left = object(binding.expression); const left_binding = left && state.scope.get(left.name); diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js index 7cabfb06c5..0d04444335 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/component.js @@ -1,4 +1,4 @@ -/** @import { BlockStatement, Expression, Pattern, Property, Statement } from 'estree' */ +/** @import { BlockStatement, Expression, Pattern, Property, SequenceExpression, Statement } from 'estree' */ /** @import { AST, TemplateNode } from '#compiler' */ /** @import { ComponentContext } from '../../types.js' */ import { empty_comment, build_attribute_value } from './utils.js'; @@ -92,24 +92,38 @@ export function build_inline_component(node, expression, context) { const value = build_attribute_value(attribute.value, context, false, true); push_prop(b.prop('init', b.key(attribute.name), value)); } else if (attribute.type === 'BindDirective' && attribute.name !== 'this') { - // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them - push_prop( - b.get(attribute.name, [ - b.return(/** @type {Expression} */ (context.visit(attribute.expression))) - ]), - true - ); - push_prop( - b.set(attribute.name, [ - b.stmt( - /** @type {Expression} */ ( - context.visit(b.assignment('=', attribute.expression, b.id('$$value'))) - ) - ), - b.stmt(b.assignment('=', b.id('$$settled'), b.false)) - ]), - true - ); + if (attribute.expression.type === 'SequenceExpression') { + const [get, set] = /** @type {SequenceExpression} */ (context.visit(attribute.expression)) + .expressions; + const get_id = b.id(context.state.scope.generate('bind_get')); + const set_id = b.id(context.state.scope.generate('bind_set')); + + context.state.init.push(b.var(get_id, get)); + context.state.init.push(b.var(set_id, set)); + + push_prop(b.get(attribute.name, [b.return(b.call(get_id))])); + push_prop(b.set(attribute.name, [b.stmt(b.call(set_id, b.id('$$value')))])); + } else { + // Delay prop pushes so bindings come at the end, to avoid spreads overwriting them + push_prop( + b.get(attribute.name, [ + b.return(/** @type {Expression} */ (context.visit(attribute.expression))) + ]), + true + ); + + push_prop( + b.set(attribute.name, [ + b.stmt( + /** @type {Expression} */ ( + context.visit(b.assignment('=', attribute.expression, b.id('$$value'))) + ) + ), + b.stmt(b.assignment('=', b.id('$$settled'), b.false)) + ]), + true + ); + } } } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index 434447727b..d626bb08db 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -110,14 +110,17 @@ export function build_element_attributes(node, context) { const binding = binding_properties[attribute.name]; if (binding?.omit_in_ssr) continue; + let expression = /** @type {Expression} */ (context.visit(attribute.expression)); + + if (expression.type === 'SequenceExpression') { + expression = b.call(expression.expressions[0]); + } + if (is_content_editable_binding(attribute.name)) { - content = /** @type {Expression} */ (context.visit(attribute.expression)); + content = expression; } else if (attribute.name === 'value' && node.name === 'textarea') { - content = b.call( - '$.escape', - /** @type {Expression} */ (context.visit(attribute.expression)) - ); - } else if (attribute.name === 'group') { + content = b.call('$.escape', expression); + } else if (attribute.name === 'group' && attribute.expression.type !== 'SequenceExpression') { const value_attribute = /** @type {AST.Attribute | undefined} */ ( node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value') ); @@ -130,6 +133,7 @@ export function build_element_attributes(node, context) { is_text_attribute(attr) && attr.value[0].data === 'checkbox' ); + attributes.push( create_attribute('checked', -1, -1, [ { @@ -159,7 +163,7 @@ export function build_element_attributes(node, context) { type: 'ExpressionTag', start: -1, end: -1, - expression: attribute.expression, + expression, metadata: { expression: create_expression_metadata() } diff --git a/packages/svelte/src/compiler/types/legacy-nodes.d.ts b/packages/svelte/src/compiler/types/legacy-nodes.d.ts index 2bd5fbbfa6..0013f5c17a 100644 --- a/packages/svelte/src/compiler/types/legacy-nodes.d.ts +++ b/packages/svelte/src/compiler/types/legacy-nodes.d.ts @@ -6,7 +6,8 @@ import type { Identifier, MemberExpression, ObjectExpression, - Pattern + Pattern, + SequenceExpression } from 'estree'; interface BaseNode { @@ -49,7 +50,7 @@ export interface LegacyBinding extends BaseNode { /** The 'x' in `bind:x` */ name: string; /** The y in `bind:x={y}` */ - expression: Identifier | MemberExpression; + expression: Identifier | MemberExpression | SequenceExpression; } export interface LegacyBody extends BaseElement { diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index a409cf5704..b8724f28dc 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -14,7 +14,8 @@ import type { Pattern, Program, ChainExpression, - SimpleCallExpression + SimpleCallExpression, + SequenceExpression } from 'estree'; import type { Scope } from '../phases/scope'; @@ -187,7 +188,7 @@ export namespace AST { /** The 'x' in `bind:x` */ name: string; /** The y in `bind:x={y}` */ - expression: Identifier | MemberExpression; + expression: Identifier | MemberExpression | SequenceExpression; /** @internal */ metadata: { binding_group_name: Identifier; diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte new file mode 100644 index 0000000000..0026309d44 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/Child.svelte @@ -0,0 +1,11 @@ + + +
div, v => div = v}>123
diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js new file mode 100644 index 0000000000..1d51c8eead --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + async test({ assert, target, logs }) { + assert.htmlEqual(target.innerHTML, `
123
`); + + assert.deepEqual(logs, ['123', '123']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte new file mode 100644 index 0000000000..21646e745a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter-2/main.svelte @@ -0,0 +1,11 @@ + + + child, v => child = v} /> diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte new file mode 100644 index 0000000000..bea5849ec7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/Child.svelte @@ -0,0 +1,12 @@ + + + a, + (v) => { + console.log('b', v); + a = v; + }} +/> diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js new file mode 100644 index 0000000000..158d1e6f63 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js @@ -0,0 +1,20 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; +import { assert_ok } from '../../../suite'; + +export default test({ + async test({ assert, target, logs }) { + const input = target.querySelector('input'); + + assert_ok(input); + + input.value = '2'; + input.dispatchEvent(new window.Event('input')); + + flushSync(); + + assert.htmlEqual(target.innerHTML, ``); + + assert.deepEqual(logs, ['b', '2', 'a', '2']); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte new file mode 100644 index 0000000000..191a423476 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte @@ -0,0 +1,16 @@ + + + + + a, + (v) => { + console.log('a', v); + a = v; + }} +/> + diff --git a/packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json new file mode 100644 index 0000000000..f85363106b --- /dev/null +++ b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/errors.json @@ -0,0 +1,14 @@ +[ + { + "code": "bind_group_invalid_expression", + "message": "`bind:group` can only bind to an Identifier or MemberExpression", + "start": { + "line": 8, + "column": 38 + }, + "end": { + "line": 8, + "column": 84 + } + } +] diff --git a/packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte new file mode 100644 index 0000000000..3f8afe7655 --- /dev/null +++ b/packages/svelte/tests/validator/samples/bind_group_invalid_expression/input.svelte @@ -0,0 +1,12 @@ + + +{#each values as value} + +{/each} + +

{selected.name}

diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 0d761919a8..61a34dcb8e 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -606,7 +606,7 @@ declare module 'svelte/animate' { } declare module 'svelte/compiler' { - import type { Expression, Identifier, ArrayExpression, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, MemberExpression, ObjectExpression, Pattern, Program, ChainExpression, SimpleCallExpression } from 'estree'; + import type { Expression, Identifier, ArrayExpression, ArrowFunctionExpression, VariableDeclaration, VariableDeclarator, MemberExpression, ObjectExpression, Pattern, Program, ChainExpression, SimpleCallExpression, SequenceExpression } from 'estree'; import type { SourceMap } from 'magic-string'; import type { Location } from 'locate-character'; /** @@ -1047,7 +1047,7 @@ declare module 'svelte/compiler' { /** The 'x' in `bind:x` */ name: string; /** The y in `bind:x={y}` */ - expression: Identifier | MemberExpression; + expression: Identifier | MemberExpression | SequenceExpression; } /** A `class:` directive */ From 57f8ca6e3c13d680d642bf7938d2a869e04043dc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 8 Dec 2024 07:31:14 -0500 Subject: [PATCH 006/389] oops --- .changeset/slimy-donkeys-hang.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/slimy-donkeys-hang.md b/.changeset/slimy-donkeys-hang.md index d63141660e..b491d78b4c 100644 --- a/.changeset/slimy-donkeys-hang.md +++ b/.changeset/slimy-donkeys-hang.md @@ -1,5 +1,5 @@ --- -'svelte': patch +'svelte': minor --- feat: add support for bind getters/setters From 301744f1f7f45946f799f82e63f072303e740071 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 8 Dec 2024 07:32:51 -0500 Subject: [PATCH 007/389] Version Packages (#14598) Co-authored-by: github-actions[bot] --- .changeset/rare-cheetahs-laugh.md | 5 ----- .changeset/slimy-donkeys-hang.md | 5 ----- packages/svelte/CHANGELOG.md | 10 ++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 .changeset/rare-cheetahs-laugh.md delete mode 100644 .changeset/slimy-donkeys-hang.md diff --git a/.changeset/rare-cheetahs-laugh.md b/.changeset/rare-cheetahs-laugh.md deleted file mode 100644 index 2637b50b3c..0000000000 --- a/.changeset/rare-cheetahs-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: always run `if` block code the first time diff --git a/.changeset/slimy-donkeys-hang.md b/.changeset/slimy-donkeys-hang.md deleted file mode 100644 index b491d78b4c..0000000000 --- a/.changeset/slimy-donkeys-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': minor ---- - -feat: add support for bind getters/setters diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 4031110fa7..fea90ca0ea 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,15 @@ # svelte +## 5.9.0 + +### Minor Changes + +- feat: add support for bind getters/setters ([#14307](https://github.com/sveltejs/svelte/pull/14307)) + +### Patch Changes + +- fix: always run `if` block code the first time ([#14597](https://github.com/sveltejs/svelte/pull/14597)) + ## 5.8.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index c751a598db..e5afd8e130 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.8.1", + "version": "5.9.0", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 3061318cb0..f5369fe169 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.8.1'; +export const VERSION = '5.9.0'; export const PUBLIC_VERSION = '5'; From c1c59e77a54109e6dd868e8ee7884caf9a275f5a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 8 Dec 2024 07:38:01 -0500 Subject: [PATCH 008/389] docs: where the hell did this come from? (#14613) --- documentation/docs/03-template-syntax/03-each.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/documentation/docs/03-template-syntax/03-each.md b/documentation/docs/03-template-syntax/03-each.md index df0ba4d8f5..70666f6a57 100644 --- a/documentation/docs/03-template-syntax/03-each.md +++ b/documentation/docs/03-template-syntax/03-each.md @@ -23,8 +23,6 @@ Iterating over values can be done with an each block. The values in question can ``` -You can use each blocks to iterate over any array or array-like value — that is, any object with a `length` property. - An each block can also specify an _index_, equivalent to the second argument in an `array.map(...)` callback: ```svelte From c66bf178aae18c2b4d8ac189a48cf10c47e4d417 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Mon, 9 Dec 2024 13:39:31 +0100 Subject: [PATCH 009/389] fix: mark subtree dynamic for bind with sequence expressions (#14626) --- .changeset/green-pandas-study.md | 5 +++++ .../phases/2-analyze/visitors/BindDirective.js | 3 +++ .../samples/bind-getter-setter/_config.js | 14 ++++++++++---- .../samples/bind-getter-setter/main.svelte | 9 +++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 .changeset/green-pandas-study.md diff --git a/.changeset/green-pandas-study.md b/.changeset/green-pandas-study.md new file mode 100644 index 0000000000..869599055c --- /dev/null +++ b/.changeset/green-pandas-study.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: mark subtree dynamic for bind with sequence expressions diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js index b062365380..b4de1925df 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js @@ -11,6 +11,7 @@ import * as w from '../../../warnings.js'; import { binding_properties } from '../../bindings.js'; import fuzzymatch from '../../1-parse/utils/fuzzymatch.js'; import { is_content_editable_binding, is_svg } from '../../../../utils.js'; +import { mark_subtree_dynamic } from './shared/fragment.js'; /** * @param {AST.BindDirective} node @@ -141,6 +142,8 @@ export function BindDirective(node, context) { e.bind_invalid_expression(node); } + mark_subtree_dynamic(context.path); + return; } diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js index 158d1e6f63..dd5c387405 100644 --- a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/_config.js @@ -4,17 +4,23 @@ import { assert_ok } from '../../../suite'; export default test({ async test({ assert, target, logs }) { - const input = target.querySelector('input'); - - assert_ok(input); + const [input, checkbox] = target.querySelectorAll('input'); input.value = '2'; input.dispatchEvent(new window.Event('input')); flushSync(); - assert.htmlEqual(target.innerHTML, ``); + assert.htmlEqual( + target.innerHTML, + `
` + ); assert.deepEqual(logs, ['b', '2', 'a', '2']); + + flushSync(() => { + checkbox.click(); + }); + assert.deepEqual(logs, ['b', '2', 'a', '2', 'check', false]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte index 191a423476..f6d908fba1 100644 --- a/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/bind-getter-setter/main.svelte @@ -2,6 +2,7 @@ import Child from './Child.svelte'; let a = $state(0); + let check = $state(true); @@ -14,3 +15,11 @@ }} /> +
+ check, + (v)=>{ + console.log('check', v); + check = v; + }} /> +
\ No newline at end of file From 0a10c59517e77a0ed0b9fb51fab44a450a3710e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:29:51 -0500 Subject: [PATCH 010/389] Version Packages (#14628) Co-authored-by: github-actions[bot] --- .changeset/green-pandas-study.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/green-pandas-study.md diff --git a/.changeset/green-pandas-study.md b/.changeset/green-pandas-study.md deleted file mode 100644 index 869599055c..0000000000 --- a/.changeset/green-pandas-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: mark subtree dynamic for bind with sequence expressions diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index fea90ca0ea..00ef293475 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.9.1 + +### Patch Changes + +- fix: mark subtree dynamic for bind with sequence expressions ([#14626](https://github.com/sveltejs/svelte/pull/14626)) + ## 5.9.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index e5afd8e130..5d662af00c 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.9.0", + "version": "5.9.1", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index f5369fe169..20ff578fbe 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.9.0'; +export const VERSION = '5.9.1'; export const PUBLIC_VERSION = '5'; From 38171f60ead8d702f50f6b5c23633d2ae4d85be6 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Mon, 9 Dec 2024 16:48:34 +0100 Subject: [PATCH 011/389] fix: allow exports with source from script module even if no bind is present (#14620) * fix: allow exports with source from script module even if no bind is present * chore: move test to validator --- .changeset/four-carrots-burn.md | 5 +++++ packages/svelte/src/compiler/phases/2-analyze/index.js | 2 +- .../export-not-defined-module-with-source/errors.json | 1 + .../export-not-defined-module-with-source/input.svelte | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/four-carrots-burn.md create mode 100644 packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json create mode 100644 packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte diff --git a/.changeset/four-carrots-burn.md b/.changeset/four-carrots-burn.md new file mode 100644 index 0000000000..39cefcc4b7 --- /dev/null +++ b/.changeset/four-carrots-burn.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow exports with source from script module even if no bind is present diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 8f1efd7f63..9e29813ee3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -698,7 +698,7 @@ export function analyze_component(root, source, options) { } for (const node of analysis.module.ast.body) { - if (node.type === 'ExportNamedDeclaration' && node.specifiers !== null) { + if (node.type === 'ExportNamedDeclaration' && node.specifiers !== null && node.source == null) { for (const specifier of node.specifiers) { if (specifier.local.type !== 'Identifier') continue; diff --git a/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/errors.json @@ -0,0 +1 @@ +[] diff --git a/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte new file mode 100644 index 0000000000..df50ebc1fa --- /dev/null +++ b/packages/svelte/tests/validator/samples/export-not-defined-module-with-source/input.svelte @@ -0,0 +1,3 @@ + From 11764632b9d64621bfbf86cd1d3a65adda1dfd09 Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Mon, 9 Dec 2024 17:22:42 +0100 Subject: [PATCH 012/389] fix: deconflict `get_name` for literal class properties (#14607) --- .changeset/stupid-buckets-drum.md | 5 ++++ .../3-transform/client/visitors/ClassBody.js | 24 +++++++++++++++---- .../_config.js | 3 +++ .../main.svelte | 6 +++++ 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 .changeset/stupid-buckets-drum.md create mode 100644 packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte diff --git a/.changeset/stupid-buckets-drum.md b/.changeset/stupid-buckets-drum.md new file mode 100644 index 0000000000..57d6f015f7 --- /dev/null +++ b/.changeset/stupid-buckets-drum.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: deconflict `get_name` for literal class properties 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 5e842a82fe..7b3a9a4d0e 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 @@ -23,6 +23,9 @@ export function ClassBody(node, context) { /** @type {Map} */ const private_state = new Map(); + /** @type {Map<(MethodDefinition|PropertyDefinition)["key"], string>} */ + const definition_names = new Map(); + /** @type {string[]} */ const private_ids = []; @@ -34,9 +37,12 @@ export function ClassBody(node, context) { definition.key.type === 'Literal') ) { const type = definition.key.type; - const name = get_name(definition.key); + const name = get_name(definition.key, public_state); if (!name) continue; + // we store the deconflicted name in the map so that we can access it later + definition_names.set(definition.key, name); + const is_private = type === 'PrivateIdentifier'; if (is_private) private_ids.push(name); @@ -96,7 +102,7 @@ export function ClassBody(node, context) { definition.key.type === 'PrivateIdentifier' || definition.key.type === 'Literal') ) { - const name = get_name(definition.key); + const name = definition_names.get(definition.key); if (!name) continue; const is_private = definition.key.type === 'PrivateIdentifier'; @@ -210,10 +216,20 @@ export function ClassBody(node, context) { /** * @param {Identifier | PrivateIdentifier | Literal} node + * @param {Map} public_state */ -function get_name(node) { +function get_name(node, public_state) { if (node.type === 'Literal') { - return node.value?.toString().replace(regex_invalid_identifier_chars, '_'); + let name = node.value?.toString().replace(regex_invalid_identifier_chars, '_'); + + // the above could generate conflicts because it has to generate a valid identifier + // so stuff like `0` and `1` or `state%` and `state^` will result in the same string + // so we have to de-conflict. We can only check `public_state` because private state + // can't have literal keys + while (name && public_state.has(name)) { + name = '_' + name; + } + return name; } else { return node.name; } diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/_config.js b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte new file mode 100644 index 0000000000..aec1e67cc6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-state-conflicting-get-name/main.svelte @@ -0,0 +1,6 @@ + \ No newline at end of file From c6fca0200981a8be0a73f9602803b37f8ff1c45b Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:39:13 +0100 Subject: [PATCH 013/389] docs: more details for errors/warnings on the site (#14632) * docs: more details for errors/warnings on the site Related to #11305 * Apply suggestions from code review Co-authored-by: Rich Harris * fix in correct place * tab not spaces * tweaks * fix * Apply suggestions from code review * regenerate --------- Co-authored-by: Rich Harris --- .../.generated/client-warnings.md | 84 +++++++++++++ .../.generated/compile-warnings.md | 117 ++++++++++++++++++ .../98-reference/.generated/server-errors.md | 2 + .../98-reference/.generated/shared-errors.md | 42 +++++++ .../.generated/shared-warnings.md | 9 ++ .../messages/client-warnings/warnings.md | 84 +++++++++++++ .../messages/compile-warnings/script.md | 91 ++++++++++++++ .../svelte/messages/compile-warnings/style.md | 14 +++ .../messages/compile-warnings/template.md | 12 ++ .../messages/server-errors/lifecycle.md | 2 + .../svelte/messages/shared-errors/errors.md | 42 +++++++ .../messages/shared-warnings/warnings.md | 9 ++ 12 files changed, 508 insertions(+) diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index ef19a28994..b0490b84ff 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -66,6 +66,31 @@ The easiest way to log a value as it changes over time is to use the [`$inspect` The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value ``` +Certain attributes like `src` on an `` element will not be repaired during hydration, i.e. the server value will be kept. That's because updating these attributes can cause the image to be refetched (or in the case of an ` - - - diff --git a/sites/svelte-5-preview/src/lib/Output/PaneWithPanel.svelte b/sites/svelte-5-preview/src/lib/Output/PaneWithPanel.svelte deleted file mode 100644 index 9018a50bee..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/PaneWithPanel.svelte +++ /dev/null @@ -1,83 +0,0 @@ - - - -
- -
- -
-
- - -
- -
- -
-
-
- - diff --git a/sites/svelte-5-preview/src/lib/Output/ReplProxy.js b/sites/svelte-5-preview/src/lib/Output/ReplProxy.js deleted file mode 100644 index 0e45887bd7..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/ReplProxy.js +++ /dev/null @@ -1,96 +0,0 @@ -let uid = 1; - -export default class ReplProxy { - /** @type {HTMLIFrameElement} */ - iframe; - - /** @type {import("./proxy").Handlers} */ - handlers; - - /** @type {Map void, reject: (value: any) => void }>} */ - pending_cmds = new Map(); - - /** @param {MessageEvent} event */ - handle_event = (event) => { - if (event.source !== this.iframe.contentWindow) return; - - const { action, args } = event.data; - - switch (action) { - case 'cmd_error': - case 'cmd_ok': - return this.handle_command_message(event.data); - case 'fetch_progress': - return this.handlers.on_fetch_progress(args.remaining); - case 'error': - return this.handlers.on_error(event.data); - case 'unhandledrejection': - return this.handlers.on_unhandled_rejection(event.data); - case 'console': - return this.handlers.on_console(event.data); - } - }; - - /** - * @param {HTMLIFrameElement} iframe - * @param {import("./proxy").Handlers} handlers - */ - constructor(iframe, handlers) { - this.iframe = iframe; - this.handlers = handlers; - - window.addEventListener('message', this.handle_event, false); - } - - destroy() { - window.removeEventListener('message', this.handle_event); - } - - /** - * @param {string} action - * @param {any} args - */ - iframe_command(action, args) { - return new Promise((resolve, reject) => { - const cmd_id = uid++; - - this.pending_cmds.set(cmd_id, { resolve, reject }); - - this.iframe.contentWindow?.postMessage({ action, cmd_id, args }, '*'); - }); - } - - /** - * @param {{ action: string; cmd_id: number; message: string; stack: any; args: any; }} cmd_data - */ - handle_command_message(cmd_data) { - let action = cmd_data.action; - let id = cmd_data.cmd_id; - let handler = this.pending_cmds.get(id); - - if (handler) { - this.pending_cmds.delete(id); - if (action === 'cmd_error') { - let { message, stack } = cmd_data; - let e = new Error(message); - e.stack = stack; - handler.reject(e); - } - - if (action === 'cmd_ok') { - handler.resolve(cmd_data.args); - } - } else { - console.error('command not found', id, cmd_data, [...this.pending_cmds.keys()]); - } - } - - /** @param {string} script */ - eval(script) { - return this.iframe_command('eval', { script }); - } - - handle_links() { - return this.iframe_command('catch_clicks', {}); - } -} diff --git a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte b/sites/svelte-5-preview/src/lib/Output/Viewer.svelte deleted file mode 100644 index db506ada3b..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/Viewer.svelte +++ /dev/null @@ -1,337 +0,0 @@ - - -
- -
- - - {#if $bundle?.error} - - {/if} -
- -
- -
- -
- -
-
- -
- {#if error} - - {:else if status || !$bundle} - {status || 'loading Svelte compiler...'} - {/if} -
-
- - diff --git a/sites/svelte-5-preview/src/lib/Output/console/Console.svelte b/sites/svelte-5-preview/src/lib/Output/console/Console.svelte deleted file mode 100644 index e2a880aff8..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/Console.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - -
- {#each logs as log} - - {/each} -
- - diff --git a/sites/svelte-5-preview/src/lib/Output/console/ConsoleLine.svelte b/sites/svelte-5-preview/src/lib/Output/console/ConsoleLine.svelte deleted file mode 100644 index 36635c7130..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/ConsoleLine.svelte +++ /dev/null @@ -1,292 +0,0 @@ - - -{#if log.command === 'table'} - -{/if} - -
- -
- {#if log.count && log.count > 1} - {log.count} - {/if} - - {#if log.stack || log.command === 'group'} - {'\u25B6'} - {/if} - - {#if log.command === 'clear'} - Console was cleared - {:else if log.command === 'unclonable'} - Message could not be cloned. Open devtools to see it - {:else if log.command === 'table'} - - {:else} - - {#each format_args(log.args) as part} - - {#if !part.formatted} - {' '} - {/if}{#if part.type === 'value'} - - {:else} - {part.value} - {/if} - {/each} - - {/if} -
- - {#if log.stack && !log.collapsed} -
- {#each log.stack as line} - {line.label} - {line.location} - {/each} -
- {/if} - - {#each new Array(depth) as _, idx} -
- {/each} -
- -{#if log.command === 'group' && !log.collapsed} - {#each log.logs ?? [] as childLog} - - {/each} -{/if} - - diff --git a/sites/svelte-5-preview/src/lib/Output/console/ConsoleTable.svelte b/sites/svelte-5-preview/src/lib/Output/console/ConsoleTable.svelte deleted file mode 100644 index cba9721807..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/ConsoleTable.svelte +++ /dev/null @@ -1,145 +0,0 @@ - - -
- - - - - - {#each table.columns as column} - - {/each} - - - - - {#each table.rows as row} - - - - {#each row.values as value} - - {/each} - - {/each} - -
(index){column}
- {#if typeof row.key === 'string'} - {row.key} - {:else} - - {/if} - - {#if typeof value === 'string'} - {value} - {:else} - - {/if} -
-
- - diff --git a/sites/svelte-5-preview/src/lib/Output/console/console.d.ts b/sites/svelte-5-preview/src/lib/Output/console/console.d.ts deleted file mode 100644 index 540e0b3b02..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/console/console.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type Log = { - command: 'info' | 'warn' | 'error' | 'table' | 'group' | 'clear' | 'unclonable'; - action?: 'console'; - args?: any[]; - collapsed?: boolean; - expanded?: boolean; - count?: number; - logs?: Log[]; - stack?: Array<{ - label?: string; - location?: string; - }>; - data?: any; - columns?: string[]; -}; diff --git a/sites/svelte-5-preview/src/lib/Output/get-location-from-stack.js b/sites/svelte-5-preview/src/lib/Output/get-location-from-stack.js deleted file mode 100644 index 3ac3e457d6..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/get-location-from-stack.js +++ /dev/null @@ -1,42 +0,0 @@ -import { decode } from '@jridgewell/sourcemap-codec'; - -/** - * @param {string} stack - * @param {import('@jridgewell/sourcemap-codec').SourceMapMappings} map - * @returns - */ -export default function getLocationFromStack(stack, map) { - if (!stack) return; - const last = stack.split('\n')[1]; - const match = /:(\d+):(\d+)\)$/.exec(last); - - if (!match) return null; - - const line = +match[1]; - const column = +match[2]; - - return trace({ line, column }, map); -} - -/** - * - * @param {Omit} loc - * @param {*} map - * @returns - */ -function trace(loc, map) { - const mappings = decode(map.mappings); - const segments = mappings[loc.line - 1]; - - for (let i = 0; i < segments.length; i += 1) { - const segment = segments[i]; - if (segment[0] === loc.column) { - const [, sourceIndex, line, column] = segment; - const source = map.sources[sourceIndex ?? 0].slice(2); - - return { source, line: (line ?? 0) + 1, column }; - } - } - - return null; -} diff --git a/sites/svelte-5-preview/src/lib/Output/proxy.d.ts b/sites/svelte-5-preview/src/lib/Output/proxy.d.ts deleted file mode 100644 index b3f9fa8d1a..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/proxy.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Handlers = Record< - 'on_fetch_progress' | 'on_error' | 'on_unhandled_rejection' | 'on_console', - (data: any) => void ->; diff --git a/sites/svelte-5-preview/src/lib/Output/srcdoc/index.html b/sites/svelte-5-preview/src/lib/Output/srcdoc/index.html deleted file mode 100644 index 202a5f973a..0000000000 --- a/sites/svelte-5-preview/src/lib/Output/srcdoc/index.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - diff --git a/sites/svelte-5-preview/src/lib/Repl.svelte b/sites/svelte-5-preview/src/lib/Repl.svelte deleted file mode 100644 index f43f5f899b..0000000000 --- a/sites/svelte-5-preview/src/lib/Repl.svelte +++ /dev/null @@ -1,438 +0,0 @@ - - - - -
-
- -
- - -
- -
- -
-
-
- - {#if $toggleable} - - {/if} -
- - diff --git a/sites/svelte-5-preview/src/lib/autocomplete.js b/sites/svelte-5-preview/src/lib/autocomplete.js deleted file mode 100644 index b535c7ff04..0000000000 --- a/sites/svelte-5-preview/src/lib/autocomplete.js +++ /dev/null @@ -1,209 +0,0 @@ -import { snippetCompletion } from '@codemirror/autocomplete'; -import { syntaxTree } from '@codemirror/language'; - -/** @typedef {(node: import('@lezer/common').SyntaxNode, context: import('@codemirror/autocomplete').CompletionContext, selected: import('./types').File) => boolean} Test */ - -/** - * Returns `true` if `$bindable()` is valid - * @type {Test} - */ -function is_bindable(node, context) { - // disallow outside `let { x = $bindable }` - if (node.parent?.name !== 'PatternProperty') return false; - if (node.parent.parent?.name !== 'ObjectPattern') return false; - if (node.parent.parent.parent?.name !== 'VariableDeclaration') return false; - - let last = node.parent.parent.parent.lastChild; - if (!last) return true; - - // if the declaration is incomplete, assume the best - if (last.name === 'ObjectPattern' || last.name === 'Equals' || last.name === '⚠') { - return true; - } - - if (last.name === ';') { - last = last.prevSibling; - if (!last || last.name === '⚠') return true; - } - - // if the declaration is complete, only return true if it is a `$props()` declaration - return ( - last.name === 'CallExpression' && - last.firstChild?.name === 'VariableName' && - context.state.sliceDoc(last.firstChild.from, last.firstChild.to) === '$props' - ); -} - -/** - * Returns `true` if `$props()` is valid - * TODO only allow in `.svelte` files, and only at the top level - * @type {Test} - */ -function is_props(node, _, selected) { - if (selected.type !== 'svelte') return false; - - return ( - node.name === 'VariableName' && - node.parent?.name === 'VariableDeclaration' && - node.parent.parent?.name === 'Script' - ); -} - -/** - * Returns `true` is this is a valid place to declare state - * @type {Test} - */ -function is_state(node) { - let parent = node.parent; - - if (node.name === '.' || node.name === 'PropertyName') { - if (parent?.name !== 'MemberExpression') return false; - parent = parent.parent; - } - - if (!parent) return false; - - return parent.name === 'VariableDeclaration' || parent.name === 'PropertyDeclaration'; -} - -/** - * Returns `true` if we're already in a valid call expression, e.g. - * changing an existing `$state()` to `$state.raw()` - * @type {Test} - */ -function is_state_call(node) { - let parent = node.parent; - - if (node.name === '.' || node.name === 'PropertyName') { - if (parent?.name !== 'MemberExpression') return false; - parent = parent.parent; - } - - if (parent?.name !== 'CallExpression') { - return false; - } - - parent = parent.parent; - if (!parent) return false; - - return parent.name === 'VariableDeclaration' || parent.name === 'PropertyDeclaration'; -} - -/** @type {Test} */ -function is_statement(node) { - if (node.name === 'VariableName') { - return node.parent?.name === 'ExpressionStatement'; - } - - if (node.name === '.' || node.name === 'PropertyName') { - return node.parent?.parent?.name === 'ExpressionStatement'; - } - - return false; -} - -/** @type {Array<{ snippet: string, test?: Test }>} */ -const runes = [ - { snippet: '$state(${})', test: is_state }, - { snippet: '$state', test: is_state_call }, - { snippet: '$props()', test: is_props }, - { snippet: '$derived(${});', test: is_state }, - { snippet: '$derived', test: is_state_call }, - { snippet: '$derived.by(() => {\n\t${}\n});', test: is_state }, - { snippet: '$derived.by', test: is_state_call }, - { snippet: '$effect(() => {\n\t${}\n});', test: is_statement }, - { snippet: '$effect.pre(() => {\n\t${}\n});', test: is_statement }, - { snippet: '$state.raw(${});', test: is_state }, - { snippet: '$state.raw', test: is_state_call }, - { snippet: '$bindable()', test: is_bindable }, - { snippet: '$effect.root(() => {\n\t${}\n})' }, - { snippet: '$state.snapshot(${})' }, - { snippet: '$effect.tracking()' }, - { snippet: '$inspect(${});', test: is_statement } -]; - -const options = runes.map(({ snippet, test }, i) => ({ - option: snippetCompletion(snippet, { - type: 'keyword', - boost: runes.length - i, - label: snippet.includes('(') ? snippet.slice(0, snippet.indexOf('(')) : snippet - }), - test -})); - -/** - * @param {import('@codemirror/autocomplete').CompletionContext} context - * @param {import('./types.js').File} selected - * @param {import('./types.js').File[]} files - */ -export function autocomplete(context, selected, files) { - let node = syntaxTree(context.state).resolveInner(context.pos, -1); - - if (node.name === 'String' && node.parent?.name === 'ImportDeclaration') { - const modules = [ - 'svelte', - 'svelte/animate', - 'svelte/easing', - 'svelte/events', - 'svelte/legacy', - 'svelte/motion', - 'svelte/reactivity', - 'svelte/store', - 'svelte/transition' - ]; - - for (const file of files) { - if (file === selected) continue; - modules.push(`./${file.name}.${file.type}`); - } - - return { - from: node.from + 1, - options: modules.map((label) => ({ - label, - type: 'string' - })) - }; - } - - if ( - selected.type !== 'svelte' && - (selected.type !== 'js' || !selected.name.endsWith('.svelte')) - ) { - return false; - } - - if (node.name === 'VariableName' || node.name === 'PropertyName' || node.name === '.') { - // special case — `$inspect(...).with(...)` is the only rune that 'returns' - // an 'object' with a 'method' - if (node.name === 'PropertyName' || node.name === '.') { - if ( - node.parent?.name === 'MemberExpression' && - node.parent.firstChild?.name === 'CallExpression' && - node.parent.firstChild.firstChild?.name === 'VariableName' && - context.state.sliceDoc( - node.parent.firstChild.firstChild.from, - node.parent.firstChild.firstChild.to - ) === '$inspect' - ) { - const open = context.matchBefore(/\.\w*/); - if (!open) return null; - - return { - from: open.from, - options: [snippetCompletion('.with(${})', { type: 'keyword', label: '.with' })] - }; - } - } - - const open = context.matchBefore(/\$[\w\.]*/); - if (!open) return null; - - return { - from: open.from, - options: options - .filter((option) => (option.test ? option.test(node, context, selected) : true)) - .map((option) => option.option) - }; - } -} diff --git a/sites/svelte-5-preview/src/lib/context.js b/sites/svelte-5-preview/src/lib/context.js deleted file mode 100644 index e983e6d147..0000000000 --- a/sites/svelte-5-preview/src/lib/context.js +++ /dev/null @@ -1,13 +0,0 @@ -import { getContext, setContext } from 'svelte'; - -const key = Symbol('repl'); - -/** @returns {import("./types").ReplContext} */ -export function get_repl_context() { - return getContext(key); -} - -/** @param {import("./types").ReplContext} value */ -export function set_repl_context(value) { - setContext(key, value); -} diff --git a/sites/svelte-5-preview/src/lib/index.js b/sites/svelte-5-preview/src/lib/index.js deleted file mode 100644 index 969b641408..0000000000 --- a/sites/svelte-5-preview/src/lib/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Repl.svelte'; diff --git a/sites/svelte-5-preview/src/lib/theme.js b/sites/svelte-5-preview/src/lib/theme.js deleted file mode 100644 index 867e144acc..0000000000 --- a/sites/svelte-5-preview/src/lib/theme.js +++ /dev/null @@ -1,153 +0,0 @@ -import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; -import { EditorView } from '@codemirror/view'; -import { tags as t } from '@lezer/highlight'; - -const ERROR_HUE = 0; -const WARNING_HUE = 40; - -const WARNING_FG = `hsl(${WARNING_HUE} 100% 60%)`; -const WARNING_BG = `hsl(${WARNING_HUE} 100% 40% / 0.5)`; - -const ERROR_FG = `hsl(${ERROR_HUE} 100% 40%)`; -const ERROR_BG = `hsl(${ERROR_HUE} 100% 40% / 0.5)`; - -/** - * @param {string} content - * @param {string} attrs - */ -function svg(content, attrs = `viewBox="0 0 40 40"`) { - return `url('data:image/svg+xml,${encodeURIComponent( - content - )}')`; -} - -/** - * @param {string} color - */ -function underline(color) { - return svg( - ``, - `width="6" height="4"` - ); -} - -const svelteThemeStyles = EditorView.theme( - { - '&': { - color: 'var(--sk-code-base)', - backgroundColor: 'transparent' - }, - - '.cm-content': { - caretColor: 'var(--sk-theme-3)' - }, - - '.cm-cursor, .cm-dropCursor': { borderLeftColor: 'var(--sk-theme-3)' }, - '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': - { backgroundColor: 'var(--sk-selection-color)' }, - - '.cm-panels': { backgroundColor: 'var(--sk-back-2)', color: 'var(--sk-text-2)' }, - '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, - '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, - - '.cm-searchMatch': { - backgroundColor: 'var(--sk-theme-2)' - // outline: '1px solid #457dff', - }, - '.cm-searchMatch.cm-searchMatch-selected': { - backgroundColor: '#6199ff2f' - }, - - '.cm-activeLine': { backgroundColor: '#6699ff0b' }, - '.cm-selectionMatch': { backgroundColor: '#aafe661a' }, - - '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { - backgroundColor: '#bad0f847' - }, - - '.cm-gutters': { - backgroundColor: 'var(--sk-back-3)', - border: 'none' - }, - - '.cm-activeLineGutter': { - backgroundColor: 'var(--sk-back-4)' - }, - - '.cm-foldPlaceholder': { - backgroundColor: 'transparent', - border: 'none', - color: '#ddd' - }, - - // https://github.com/codemirror/lint/blob/271b35f5d31a7e3645eaccbfec608474022098e1/src/lint.ts#L620 - '.cm-lintRange': { - backgroundPosition: 'left bottom', - backgroundRepeat: 'repeat-x', - paddingBottom: '4px' - }, - '.cm-lintRange-error': { - backgroundImage: underline(ERROR_FG) - }, - '.cm-lintRange-warning': { - backgroundImage: underline(WARNING_FG) - }, - '.cm-tooltip .cm-tooltip-arrow:before': { - borderTopColor: 'transparent', - borderBottomColor: 'transparent' - }, - '.cm-tooltip .cm-tooltip-arrow:after': { - borderTopColor: 'var(--sk-back-3)', - borderBottomColor: 'var(--sk-back-3)' - }, - '.cm-tooltip-autocomplete': { - color: 'var(--sk-text-2) !important', - perspective: '1px', - '& > ul > li[aria-selected]': { - backgroundColor: 'var(--sk-back-4)', - color: 'var(--sk-text-1) !important' - } - } - }, - { dark: true } -); - -/// The highlighting style for code in the One Dark theme. -const svelteHighlightStyle = HighlightStyle.define([ - { tag: t.keyword, color: 'var(--sk-code-keyword)' }, - { - tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], - color: 'var(--sk-code-base)' - }, - { tag: [t.function(t.variableName), t.labelName], color: 'var(--sk-code-tags)' }, - { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: 'var(--sk-code-base)' }, - { tag: [t.definition(t.name), t.separator], color: 'var(--sk-code-base)' }, - { - tag: [ - t.typeName, - t.className, - t.number, - t.changed, - t.annotation, - t.modifier, - t.self, - t.namespace - ], - color: 'var(--sk-code-tags)' - }, - { - tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], - color: 'var(--sk-code-base)' - }, - { tag: [t.meta, t.comment], color: 'var(--sk-code-comment)' }, - { tag: t.strong, fontWeight: 'bold' }, - { tag: t.emphasis, fontStyle: 'italic' }, - { tag: t.strikethrough, textDecoration: 'line-through' }, - { tag: t.link, color: 'var(--sk-code-base)', textDecoration: 'underline' }, - { tag: t.heading, fontWeight: 'bold', color: 'var(--sk-text-1)' }, - { tag: [t.atom, t.bool], color: 'var(--sk-code-atom)' }, - { tag: [t.processingInstruction, t.string, t.inserted], color: 'var(--sk-code-string)' }, - { tag: t.invalid, color: '#ff008c' } -]); - -export const svelteTheme = [svelteThemeStyles, syntaxHighlighting(svelteHighlightStyle)]; diff --git a/sites/svelte-5-preview/src/lib/types.d.ts b/sites/svelte-5-preview/src/lib/types.d.ts deleted file mode 100644 index a758846d29..0000000000 --- a/sites/svelte-5-preview/src/lib/types.d.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { EditorState } from '@codemirror/state'; -import { OutputChunk, RollupError } from '@rollup/browser'; -import type { Readable, Writable } from 'svelte/store'; -import type { CompileOptions, CompileError } from 'svelte/compiler'; - -export type Lang = 'js' | 'svelte' | 'json' | 'md' | 'css' | (string & Record); - -type StartOrEnd = { - line: number; - column: number; - character: number; -}; - -export type MessageDetails = { - start: StartOrEnd; - end: StartOrEnd; - filename: string; - message: string; -}; - -export type Warning = MessageDetails; - -export type Bundle = { - uid: number; - client: OutputChunk | null; - error: (RollupError & CompileError) | null; - server: OutputChunk | null; - imports: string[]; - warnings: Warning[]; -}; - -export type File = { - name: string; - source: string; - type: Lang; - modified?: boolean; -}; - -export type ReplState = { - files: File[]; - selected_name: string; - selected: File | null; - bundle: Bundle | null; - bundling: Promise; - bundler: import('./Bundler').default | null; - compile_options: CompileOptions; - cursor_pos: number; - toggleable: boolean; - module_editor: import('./CodeMirror.svelte').default | null; -}; - -export type ReplContext = { - files: Writable; - selected_name: Writable; - selected: Readable; - bundle: Writable; - bundling: Writable; - bundler: Writable; - compile_options: Writable; - cursor_pos: Writable; - toggleable: Writable; - module_editor: Writable; - - EDITOR_STATE_MAP: Map; - - // Methods - rebundle(): Promise; - migrate(): Promise; - handle_select(filename: string): Promise; - handle_change( - event: CustomEvent<{ - value: string; - }> - ): Promise; - go_to_warning_pos(item?: MessageDetails): Promise; - clear_state(): void; -}; diff --git a/sites/svelte-5-preview/src/lib/utils.js b/sites/svelte-5-preview/src/lib/utils.js deleted file mode 100644 index d378e1d517..0000000000 --- a/sites/svelte-5-preview/src/lib/utils.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @param {number} min - * @param {number} max - * @param {number} value - */ -export const clamp = (min, max, value) => Math.max(min, Math.min(max, value)); - -/** - * @param {number} ms - */ -export const sleep = (ms) => new Promise((f) => setTimeout(f, ms)); - -/** @param {import('./types').File} file */ -export function get_full_filename(file) { - return `${file.name}.${file.type}`; -} diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/index.js b/sites/svelte-5-preview/src/lib/workers/bundler/index.js deleted file mode 100644 index 5a289fff7d..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/index.js +++ /dev/null @@ -1,576 +0,0 @@ -/// - -import '../patch_window.js'; -import { sleep } from '$lib/utils.js'; -import { rollup } from '@rollup/browser'; -import { DEV } from 'esm-env'; -import * as resolve from 'resolve.exports'; -import commonjs from './plugins/commonjs.js'; -import glsl from './plugins/glsl.js'; -import json from './plugins/json.js'; -import replace from './plugins/replace.js'; -import loop_protect from './plugins/loop-protect.js'; - -/** @type {string} */ -var pkg_name; - -/** @type {string} */ -let packages_url; - -/** @type {string} */ -let svelte_url; - -/** @type {number} */ -let current_id; - -/** @type {(arg?: never) => void} */ -let fulfil_ready; -const ready = new Promise((f) => { - fulfil_ready = f; -}); - -/** - * @type {{ - * compile: typeof import('svelte/compiler').compile; - * compileModule: typeof import('svelte/compiler').compileModule; - * VERSION: string; - * }} - */ -let svelte; - -self.addEventListener( - 'message', - /** @param {MessageEvent} event */ async (event) => { - switch (event.data.type) { - case 'init': { - ({ packages_url, svelte_url } = event.data); - - const { version } = await fetch(`${svelte_url}/package.json`).then((r) => r.json()); - console.log(`Using Svelte compiler version ${version}`); - - const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - - svelte = globalThis.svelte; - - fulfil_ready(); - break; - } - - case 'bundle': { - await ready; - const { uid, files } = event.data; - - if (files.length === 0) return; - - current_id = uid; - - setTimeout(async () => { - if (current_id !== uid) return; - - const result = await bundle({ uid, files }); - - if (JSON.stringify(result.error) === JSON.stringify(ABORT)) return; - if (result && uid === current_id) postMessage(result); - }); - - break; - } - } - } -); - -/** @type {Record<'client' | 'server', Map }>>} */ -let cached = { - client: new Map(), - server: new Map() -}; - -const ABORT = { aborted: true }; - -/** @type {Map>} */ -const FETCH_CACHE = new Map(); - -/** - * @param {string} url - * @param {number} uid - */ -async function fetch_if_uncached(url, uid) { - if (FETCH_CACHE.has(url)) { - return FETCH_CACHE.get(url); - } - - // TODO: investigate whether this is necessary - await sleep(50); - if (uid !== current_id) throw ABORT; - - const promise = fetch(url) - .then(async (r) => { - if (!r.ok) throw new Error(await r.text()); - - return { - url: r.url, - body: await r.text() - }; - }) - .catch((err) => { - FETCH_CACHE.delete(url); - throw err; - }); - - FETCH_CACHE.set(url, promise); - return promise; -} - -/** - * @param {string} url - * @param {number} uid - */ -async function follow_redirects(url, uid) { - const res = await fetch_if_uncached(url, uid); - return res?.url; -} - -/** - * - * @param {number} major - * @param {number} minor - * @param {number} patch - * @returns {number} - */ -function compare_to_version(major, minor, patch) { - const v = svelte.VERSION.match(/^(\d+)\.(\d+)\.(\d+)/); - - // @ts-ignore - return +v[1] - major || +v[2] - minor || +v[3] - patch; -} - -function is_v4() { - return compare_to_version(4, 0, 0) >= 0; -} - -function is_v5() { - return compare_to_version(5, 0, 0) >= 0; -} - -function is_legacy_package_structure() { - return compare_to_version(3, 4, 4) <= 0; -} - -function has_loopGuardTimeout_feature() { - return compare_to_version(3, 14, 0) >= 0; -} - -/** - * - * @param {Record} pkg - * @param {string} subpath - * @param {number} uid - * @param {string} pkg_url_base - */ -async function resolve_from_pkg(pkg, subpath, uid, pkg_url_base) { - // match legacy Rollup logic — pkg.svelte takes priority over pkg.exports - if (typeof pkg.svelte === 'string' && subpath === '.') { - return pkg.svelte; - } - - // modern - if (pkg.exports) { - try { - const [resolved] = - resolve.exports(pkg, subpath, { - browser: true, - conditions: ['svelte', 'development'] - }) ?? []; - - return resolved; - } catch { - throw `no matched export path was found in "${pkg_name}/package.json"`; - } - } - - // legacy - if (subpath === '.') { - let resolved_id = resolve.legacy(pkg, { - fields: ['browser', 'module', 'main'] - }); - - if (typeof resolved_id === 'object' && !Array.isArray(resolved_id)) { - const subpath = resolved_id['.']; - if (subpath === false) return 'data:text/javascript,export {}'; - - resolved_id = - subpath ?? - resolve.legacy(pkg, { - fields: ['module', 'main'] - }); - } - - if (!resolved_id) { - // last ditch — try to match index.js/index.mjs - for (const index_file of ['index.mjs', 'index.js']) { - try { - const indexUrl = new URL(index_file, `${pkg_url_base}/`).href; - return (await follow_redirects(indexUrl, uid)) ?? ''; - } catch { - // maybe the next option will be successful - } - } - - throw `could not find entry point in "${pkg_name}/package.json"`; - } - - return resolved_id; - } - - if (typeof pkg.browser === 'object') { - // this will either return `pkg.browser[subpath]` or `subpath` - return resolve.legacy(pkg, { - browser: subpath - }); - } - - return subpath; -} - -/** - * @param {number} uid - * @param {'client' | 'server'} mode - * @param {typeof cached['client']} cache - * @param {Map} local_files_lookup - */ -async function get_bundle(uid, mode, cache, local_files_lookup) { - let bundle; - - /** A set of package names (without subpaths) to include in pkg.devDependencies when downloading an app */ - /** @type {Set} */ - const imports = new Set(); - - /** @type {import('$lib/types.js').Warning[]} */ - const warnings = []; - - /** @type {{ message: string }[]} */ - const all_warnings = []; - - /** @type {typeof cache} */ - const new_cache = new Map(); - - /** @type {import('@rollup/browser').Plugin} */ - const repl_plugin = { - name: 'svelte-repl', - async resolveId(importee, importer) { - if (uid !== current_id) throw ABORT; - - if (importee === 'esm-env') return importee; - - const v5 = is_v5(); - const v4 = !v5 && is_v4(); - - if (!v5) { - // importing from Svelte - if (importee === `svelte`) - return v4 ? `${svelte_url}/src/runtime/index.js` : `${svelte_url}/index.mjs`; - - if (importee.startsWith(`svelte/`)) { - const sub_path = importee.slice(7); - if (v4) { - return `${svelte_url}/src/runtime/${sub_path}/index.js`; - } - - return is_legacy_package_structure() - ? `${svelte_url}/${sub_path}.mjs` - : `${svelte_url}/${sub_path}/index.mjs`; - } - } - - // importing from another file in REPL - if (local_files_lookup.has(importee) && (!importer || local_files_lookup.has(importer))) - return importee; - if (local_files_lookup.has(importee + '.js')) return importee + '.js'; - if (local_files_lookup.has(importee + '.json')) return importee + '.json'; - - // remove trailing slash - if (importee.endsWith('/')) importee = importee.slice(0, -1); - - // importing from a URL - if (/^https?:/.test(importee)) return importee; - - if (importee.startsWith('.')) { - if (importer && local_files_lookup.has(importer)) { - // relative import in a REPL file - // should've matched above otherwise importee doesn't exist - console.error(`Cannot find file "${importee}" imported by "${importer}" in the REPL`); - return; - } else { - // relative import in an external file - const url = new URL(importee, importer).href; - self.postMessage({ type: 'status', uid, message: `resolving ${url}` }); - - return await follow_redirects(url, uid); - } - } else { - // fetch from unpkg - self.postMessage({ type: 'status', uid, message: `resolving ${importee}` }); - - const match = /^((?:@[^/]+\/)?[^/]+)(\/.+)?$/.exec(importee); - if (!match) { - return console.error(`Invalid import "${importee}"`); - } - - const pkg_name = match[1]; - const subpath = `.${match[2] ?? ''}`; - - // if this was imported by one of our files, add it to the `imports` set - if (importer && local_files_lookup.has(importer)) { - imports.add(pkg_name); - } - - const fetch_package_info = async () => { - try { - const pkg_url = await follow_redirects( - `${pkg_name === 'svelte' ? '' : packages_url}/${pkg_name}/package.json`, - uid - ); - - if (!pkg_url) throw new Error(); - - const pkg_json = (await fetch_if_uncached(pkg_url, uid))?.body; - const pkg = JSON.parse(pkg_json ?? '""'); - - const pkg_url_base = pkg_url.replace(/\/package\.json$/, ''); - - return { - pkg, - pkg_url_base - }; - } catch (_e) { - throw new Error(`Error fetching "${pkg_name}" from unpkg. Does the package exist?`); - } - }; - - const { pkg, pkg_url_base } = await fetch_package_info(); - - try { - const resolved_id = await resolve_from_pkg(pkg, subpath, uid, pkg_url_base); - return new URL(resolved_id + '', `${pkg_url_base}/`).href; - } catch (reason) { - throw new Error(`Cannot import "${importee}": ${reason}.`); - } - } - }, - async load(resolved) { - if (uid !== current_id) throw ABORT; - - if (resolved === 'esm-env') { - return `export const BROWSER = true; export const DEV = true`; - } - - const cached_file = local_files_lookup.get(resolved); - if (cached_file) return cached_file.source; - - if (!FETCH_CACHE.has(resolved)) { - self.postMessage({ type: 'status', uid, message: `fetching ${resolved}` }); - } - - const res = await fetch_if_uncached(resolved, uid); - return res?.body; - }, - transform(code, id) { - if (uid !== current_id) throw ABORT; - - self.postMessage({ type: 'status', uid, message: `bundling ${id}` }); - - if (!/\.(svelte|js)$/.test(id)) return null; - - const name = id.split('/').pop()?.split('.')[0]; - - const cached_id = cache.get(id); - let result; - - if (cached_id && cached_id.code === code) { - result = cached_id.result; - } else if (id.endsWith('.svelte')) { - result = svelte.compile(code, { - filename: name + '.svelte', - generate: 'client', - dev: true - }); - - if (result.css) { - result.js.code += - '\n\n' + - ` - const $$__style = document.createElement('style'); - $$__style.textContent = ${JSON.stringify(result.css.code)}; - document.head.append($$__style); - `.replace(/\t/g, ''); - } - } else if (id.endsWith('.svelte.js')) { - result = svelte.compileModule(code, { - filename: name + '.js', - generate: 'client', - dev: true - }); - if (!result) { - return null; - } - } else { - return null; - } - - new_cache.set(id, { code, result }); - - // @ts-expect-error - (result.warnings || result.stats?.warnings)?.forEach((warning) => { - // This is required, otherwise postMessage won't work - // @ts-ignore - delete warning.toString; - // TODO remove stats post-launch - // @ts-ignore - warnings.push(warning); - }); - - /** @type {import('@rollup/browser').TransformResult} */ - const transform_result = { - code: result.js.code, - map: result.js.map - }; - - return transform_result; - } - }; - - try { - bundle = await rollup({ - input: './__entry.js', - plugins: [ - repl_plugin, - commonjs, - json, - glsl, - loop_protect, - replace({ - 'process.env.NODE_ENV': JSON.stringify('production') - }) - ], - inlineDynamicImports: true, - onwarn(warning) { - all_warnings.push({ - message: warning.message - }); - } - }); - - return { - bundle, - imports: Array.from(imports), - cache: new_cache, - error: null, - warnings, - all_warnings - }; - } catch (error) { - return { error, imports: null, bundle: null, cache: new_cache, warnings, all_warnings }; - } -} - -/** - * @param {{ uid: number; files: import('$lib/types.js').File[] }} param0 - * @returns - */ -async function bundle({ uid, files }) { - if (!DEV) { - console.clear(); - console.log(`running Svelte compiler version %c${svelte.VERSION}`, 'font-weight: bold'); - } - - /** @type {Map} */ - const lookup = new Map(); - - lookup.set('./__entry.js', { - name: '__entry', - source: ` - export { mount, unmount, untrack } from 'svelte'; - export {default as App} from './App.svelte'; - `, - type: 'js', - modified: false - }); - - files.forEach((file) => { - const path = `./${file.name}.${file.type}`; - lookup.set(path, file); - }); - - /** @type {Awaited>} */ - let client = await get_bundle(uid, 'client', cached.client, lookup); - let error; - - try { - if (client.error) { - throw client.error; - } - - cached.client = client.cache; - - const client_result = ( - await client.bundle?.generate({ - format: 'iife', - exports: 'named' - // sourcemap: 'inline' - }) - )?.output[0]; - - const server = false // TODO how can we do SSR? - ? await get_bundle(uid, 'server', cached.server, lookup) - : null; - - if (server) { - cached.server = server.cache; - if (server.error) { - throw server.error; - } - } - - const server_result = server - ? ( - await server.bundle?.generate({ - format: 'iife', - name: 'SvelteComponent', - exports: 'named' - // sourcemap: 'inline' - }) - )?.output?.[0] - : null; - - return { - uid, - client: client_result, - server: server_result, - imports: client.imports, - warnings: client.warnings, - error: null - }; - } catch (err) { - console.error(err); - - /** @type {Error} */ - // @ts-ignore - const e = error || err; - - // @ts-ignore - delete e.toString; - - return { - uid, - client: null, - server: null, - imports: null, - warnings: client.warnings, - error: Object.assign({}, e, { - message: e.message, - stack: e.stack - }) - }; - } -} diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/commonjs.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/commonjs.js deleted file mode 100644 index 9e0a92dbdd..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/commonjs.js +++ /dev/null @@ -1,58 +0,0 @@ -import { parse } from 'acorn'; -import { walk } from 'zimmerframe'; - -const require = `function require(id) { - if (id in __repl_lookup) return __repl_lookup[id]; - throw new Error(\`Cannot require modules dynamically (\${id})\`); -}`; - -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'commonjs', - - transform: (code, id) => { - if (!/\b(require|module|exports)\b/.test(code)) return; - - try { - const ast = parse(code, { - ecmaVersion: 'latest' - }); - - /** @type {string[]} */ - const requires = []; - - walk(/** @type {import('estree').Node} */ (ast), null, { - CallExpression: (node) => { - if (node.callee.type === 'Identifier' && node.callee.name === 'require') { - if (node.arguments.length !== 1) return; - const arg = node.arguments[0]; - if (arg.type !== 'Literal' || typeof arg.value !== 'string') return; - - requires.push(arg.value); - } - } - }); - - const imports = requires.map((id, i) => `import __repl_${i} from '${id}';`).join('\n'); - const lookup = `const __repl_lookup = { ${requires - .map((id, i) => `'${id}': __repl_${i}`) - .join(', ')} };`; - - const transformed = [ - imports, - lookup, - require, - `const exports = {}; const module = { exports };`, - code, - `export default module.exports;` - ].join('\n\n'); - - return { - code: transformed, - map: null - }; - } catch (err) { - return null; - } - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/glsl.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/glsl.js deleted file mode 100644 index 51e7e062a4..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/glsl.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'glsl', - transform: (code, id) => { - if (!id.endsWith('.glsl')) return; - - return { - code: `export default ${JSON.stringify(code)};`, - map: null - }; - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/json.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/json.js deleted file mode 100644 index 2f79b289e4..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/json.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'json', - transform: (code, id) => { - if (!id.endsWith('.json')) return; - - return { - code: `export default ${code};`, - map: null - }; - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/loop-protect.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/loop-protect.js deleted file mode 100644 index 9cb4a8e25e..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/loop-protect.js +++ /dev/null @@ -1,111 +0,0 @@ -import { parse } from 'acorn'; -import { print } from 'esrap'; -import { walk } from 'zimmerframe'; - -const TIMEOUT = 100; - -const regex = /\b(for|while)\b/; - -/** - * - * @param {string} code - * @returns {import('estree').Statement} - */ -function parse_statement(code) { - return /** @type {import('estree').Statement} */ (parse(code, { ecmaVersion: 'latest' }).body[0]); -} - -const declaration = parse_statement(` - const __start = Date.now(); -`); - -const check = parse_statement(` - if (Date.now() > __start + ${TIMEOUT}) { - throw new Error('Infinite loop detected'); - } -`); - -/** - * - * @param {import('estree').Node[]} path - * @returns {null | import('estree').FunctionExpression | import('estree').FunctionDeclaration | import('estree').ArrowFunctionExpression} - */ -export function get_current_function(path) { - for (let i = path.length - 1; i >= 0; i--) { - const node = path[i]; - if ( - node.type === 'FunctionDeclaration' || - node.type === 'FunctionExpression' || - node.type === 'ArrowFunctionExpression' - ) { - return node; - } - } - return null; -} - -/** - * @template {import('estree').DoWhileStatement | import('estree').ForStatement | import('estree').WhileStatement} Statement - * @param {Statement} node - * @param {import('zimmerframe').Context} context - * @returns {import('estree').Node | void} - */ -function loop_protect(node, context) { - const current_function = get_current_function(context.path); - - if (current_function === null || (!current_function.async && !current_function.generator)) { - const body = /** @type {import('estree').Statement} */ (context.visit(node.body)); - - const statements = body.type === 'BlockStatement' ? [...body.body] : [body]; - - /** @type {import('estree').BlockStatement} */ - const replacement = { - type: 'BlockStatement', - body: [ - declaration, - { - .../** @type {Statement} */ (context.next() ?? node), - body: { - type: 'BlockStatement', - body: [...statements, check] - } - } - ] - }; - - return replacement; - } - - context.next(); -} - -/** @type {import('@rollup/browser').Plugin} */ -export default { - name: 'loop-protect', - transform: (code, id) => { - // only applies to local files, not imports - if (!id.startsWith('./')) return; - - // only applies to JS and Svelte files - if (!id.endsWith('.js') && !id.endsWith('.svelte')) return; - - // fast path - if (!regex.test(code)) return; - - const ast = parse(code, { - ecmaVersion: 'latest', - sourceType: 'module' - }); - - const transformed = walk(/** @type {import('estree').Node} */ (ast), null, { - WhileStatement: loop_protect, - DoWhileStatement: loop_protect, - ForStatement: loop_protect - }); - - // nothing changed - if (ast === transformed) return null; - - return print(transformed); - } -}; diff --git a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/replace.js b/sites/svelte-5-preview/src/lib/workers/bundler/plugins/replace.js deleted file mode 100644 index 6ccdeffed8..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/bundler/plugins/replace.js +++ /dev/null @@ -1,72 +0,0 @@ -/** @param {string} str */ -function escape(str) { - return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); -} - -/** @param {unknown} functionOrValue */ -function ensureFunction(functionOrValue) { - if (typeof functionOrValue === 'function') { - return functionOrValue; - } - return function () { - return functionOrValue; - }; -} - -/** - * @param {string} a - * @param {string} b - */ -function longest(a, b) { - return b.length - a.length; -} - -/** @param {Record} object */ -function mapToFunctions(object) { - return Object.keys(object).reduce( - /** @param {Record} functions */ function (functions, key) { - functions[key] = ensureFunction(object[key]); - return functions; - }, - {} - ); -} - -/** - * @param {Record} options - * @returns {import('@rollup/browser').Plugin} - */ -function replace(options) { - const functionValues = mapToFunctions(options); - const keys = Object.keys(functionValues).sort(longest).map(escape); - - const pattern = new RegExp('\\b(' + keys.join('|') + ')\\b', 'g'); - - return { - name: 'replace', - - transform: function transform(code, id) { - let hasReplacements = false; - let match; - let start; - let end; - let replacement; - - code = code.replace(pattern, (_, key) => { - hasReplacements = true; - return String(functionValues[key](id)); - }); - - if (!hasReplacements) { - return null; - } - - return { - code, - map: null - }; - } - }; -} - -export default replace; diff --git a/sites/svelte-5-preview/src/lib/workers/compiler/index.js b/sites/svelte-5-preview/src/lib/workers/compiler/index.js deleted file mode 100644 index 9247894dd6..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/compiler/index.js +++ /dev/null @@ -1,154 +0,0 @@ -/// -self.window = self; //TODO: still need?: egregious hack to get magic-string to work in a worker - -/** - * @type {{ - * parse: typeof import('svelte/compiler').parse; - * compile: typeof import('svelte/compiler').compile; - * compileModule: typeof import('svelte/compiler').compileModule; - * VERSION: string; - * }} - */ -let svelte; - -/** @type {(arg?: never) => void} */ -let fulfil_ready; -const ready = new Promise((f) => { - fulfil_ready = f; -}); - -self.addEventListener( - 'message', - /** @param {MessageEvent} event */ - async (event) => { - switch (event.data.type) { - case 'init': - const { svelte_url } = event.data; - - const { version } = await fetch(`${svelte_url}/package.json`) - .then((r) => r.json()) - .catch(() => ({ version: 'experimental' })); - - const compiler = await fetch(`${svelte_url}/compiler/index.js`).then((r) => r.text()); - (0, eval)(compiler + '\n//# sourceURL=compiler/index.js@' + version); - - svelte = globalThis.svelte; - - fulfil_ready(); - break; - - case 'compile': - await ready; - postMessage(compile(event.data)); - break; - - case 'migrate': - await ready; - postMessage(migrate(event.data)); - break; - } - } -); - -const common_options = { - dev: false, - css: false -}; - -/** @param {import("../workers").CompileMessageData} param0 */ -function compile({ id, source, options, return_ast }) { - try { - const css = `/* Select a component to see compiled CSS */`; - - if (options.filename.endsWith('.svelte')) { - const compiled = svelte.compile(source, { - ...options, - discloseVersion: false // less visual noise in the output tab - }); - - const { js, css, warnings, metadata } = compiled; - - const ast = return_ast ? svelte.parse(source, { modern: true }) : undefined; - - return { - id, - result: { - js: js.code, - css: css?.code || `/* Add a tag to see compiled CSS */`, - error: null, - warnings: warnings.map((warning) => warning.toJSON()), - metadata, - ast - } - }; - } else if (options.filename.endsWith('.svelte.js')) { - const compiled = svelte.compileModule(source, { - filename: options.filename, - generate: options.generate, - dev: options.dev - }); - - if (compiled) { - return { - id, - result: { - js: compiled.js.code, - css, - error: null, - warnings: compiled.warnings.map((warning) => warning.toJSON()), - metadata: compiled.metadata - } - }; - } - } - - return { - id, - result: { - js: `// Select a component, or a '.svelte.js' module that uses runes, to see compiled output`, - css, - error: null, - warnings: [], - metadata: null - } - }; - } catch (err) { - // @ts-ignore - let message = `/*\nError compiling ${err.filename ?? 'component'}:\n${err.message}\n*/`; - - return { - id, - result: { - js: message, - css: message, - error: { - message: err.message, - position: err.position - }, - warnings: [], - metadata: null - } - }; - } -} - -/** @param {import("../workers").MigrateMessageData} param0 */ -function migrate({ id, source, filename }) { - try { - const result = svelte.migrate(source, { filename }); - - return { - id, - result - }; - } catch (err) { - // @ts-ignore - let message = `/*\nError migrating ${err.filename ?? 'component'}:\n${err.message}\n*/`; - - return { - id, - result: { code: source }, - error: message - }; - } -} diff --git a/sites/svelte-5-preview/src/lib/workers/jsconfig.json b/sites/svelte-5-preview/src/lib/workers/jsconfig.json deleted file mode 100644 index 60351b7548..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/jsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "include": ["./**/*"], - "compilerOptions": { - "paths": { - "svelte": ["../../../static/svelte/main"], - "svelte/*": ["../../../static/svelte/*"] - } - } -} diff --git a/sites/svelte-5-preview/src/lib/workers/patch_window.js b/sites/svelte-5-preview/src/lib/workers/patch_window.js deleted file mode 100644 index ff7057c9c2..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/patch_window.js +++ /dev/null @@ -1 +0,0 @@ -self.window = self; // hack for magic-sring and rollup inline sourcemaps diff --git a/sites/svelte-5-preview/src/lib/workers/workers.d.ts b/sites/svelte-5-preview/src/lib/workers/workers.d.ts deleted file mode 100644 index e66e075c14..0000000000 --- a/sites/svelte-5-preview/src/lib/workers/workers.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { CompileOptions, File } from '../types'; - -export type CompileMessageData = { - id: number; - type: 'compile' | 'init'; - source: string; - options: CompileOptions; - is_entry: boolean; - return_ast: boolean; - svelte_url?: string; - result: { - js: string; - css: string; - ast?: import('svelte/types/compiler/interfaces').Ast; - metadata?: { - runes: boolean; - }; - }; -}; - -export type BundleMessageData = { - uid: number; - type: 'init' | 'bundle' | 'status'; - message: string; - packages_url: string; - svelte_url: string; - files: File[]; -}; - -export type MigrateMessageData = { - id: number; - result: { code: string }; - error?: string; -}; diff --git a/sites/svelte-5-preview/src/routes/+error.svelte b/sites/svelte-5-preview/src/routes/+error.svelte deleted file mode 100644 index 6d6d8a7d7c..0000000000 --- a/sites/svelte-5-preview/src/routes/+error.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - - - {$page.status} - - -
- {#if online} - {#if $page.status === 404} -

Not found!

-

- If you were expecting to find something here, please drop by the - Discord chatroom - and let us know, or raise an issue on - GitHub. Thanks! -

- {:else} -

Yikes!

-

Something went wrong when we tried to render this page.

- {#if $page.error.message} -

{$page.status}: {$page.error.message}

- {:else} -

Encountered a {$page.status} error.

- {/if} -

Please try reloading the page.

-

- If the error persists, please drop by the - Discord chatroom - and let us know, or raise an issue on - GitHub. Thanks! -

- {/if} - {:else} -

It looks like you're offline

-

Reload the page once you've found the internet.

- {/if} -
- - diff --git a/sites/svelte-5-preview/src/routes/+layout.server.js b/sites/svelte-5-preview/src/routes/+layout.server.js deleted file mode 100644 index 640c4c57df..0000000000 --- a/sites/svelte-5-preview/src/routes/+layout.server.js +++ /dev/null @@ -1,12 +0,0 @@ -export const prerender = true; - -/** @type {import('@sveltejs/adapter-vercel').EdgeConfig} */ -export const config = { - runtime: 'edge' -}; - -export const load = async ({ fetch }) => { - const nav_data = await fetch('/nav.json').then((r) => r.json()); - - return { nav_links: nav_data }; -}; diff --git a/sites/svelte-5-preview/src/routes/+layout.svelte b/sites/svelte-5-preview/src/routes/+layout.svelte deleted file mode 100644 index 99b72d0fad..0000000000 --- a/sites/svelte-5-preview/src/routes/+layout.svelte +++ /dev/null @@ -1,101 +0,0 @@ - - - - Svelte 5 preview - - - - - - - - - - - - - - diff --git a/sites/svelte-5-preview/src/routes/+page.svelte b/sites/svelte-5-preview/src/routes/+page.svelte deleted file mode 100644 index b54c36ee5b..0000000000 --- a/sites/svelte-5-preview/src/routes/+page.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - { - if (!setting_hash) { - change_from_hash(); - } - - setting_hash = false; - }} -/> - - diff --git a/sites/svelte-5-preview/src/routes/defaults.js b/sites/svelte-5-preview/src/routes/defaults.js deleted file mode 100644 index f1bdbbbb35..0000000000 --- a/sites/svelte-5-preview/src/routes/defaults.js +++ /dev/null @@ -1,21 +0,0 @@ -export const default_files = () => [ - { - name: 'App', - type: 'svelte', - source: ` - - - - ` - .replace(/^\t{3}/gm, '') - .trim() - } -]; diff --git a/sites/svelte-5-preview/src/routes/docs/+layout.server.js b/sites/svelte-5-preview/src/routes/docs/+layout.server.js deleted file mode 100644 index cbb6433bf9..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/+layout.server.js +++ /dev/null @@ -1,13 +0,0 @@ -export async function load({ url }) { - if (url.pathname === '/docs') { - return { - sections: [] - }; - } - - const { get_docs_data, get_docs_list } = await import('./render.js'); - - return { - sections: get_docs_list(await get_docs_data()) - }; -} diff --git a/sites/svelte-5-preview/src/routes/docs/+layout.svelte b/sites/svelte-5-preview/src/routes/docs/+layout.svelte deleted file mode 100644 index 097fde98ff..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/+layout.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - -
-
- -
- -
- {#if category} -

{category}

- {/if} - {#if title} -

{title}

- {/if} - - -
-
- - diff --git a/sites/svelte-5-preview/src/routes/docs/+page.js b/sites/svelte-5-preview/src/routes/docs/+page.js deleted file mode 100644 index fba7f30e4b..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/+page.js +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from '@sveltejs/kit'; - -export function load() { - redirect(307, '/docs/introduction'); -} diff --git a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.server.js b/sites/svelte-5-preview/src/routes/docs/[slug]/+page.server.js deleted file mode 100644 index 25c78ba281..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.server.js +++ /dev/null @@ -1,19 +0,0 @@ -import { error } from '@sveltejs/kit'; - -export async function entries() { - const { get_docs_data } = await import('../render.js'); - - const data = await get_docs_data(); - return data[0].pages.map((page) => ({ slug: page.slug })); -} - -export async function load({ params }) { - const { get_docs_data, get_parsed_docs } = await import('../render.js'); - - const data = await get_docs_data(); - const processed_page = await get_parsed_docs(data, params.slug); - - if (!processed_page) error(404); - - return { page: processed_page }; -} diff --git a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.svelte b/sites/svelte-5-preview/src/routes/docs/[slug]/+page.svelte deleted file mode 100644 index bb5c166711..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/[slug]/+page.svelte +++ /dev/null @@ -1,76 +0,0 @@ - - - - {data.page?.title} • Docs • Svelte 5 preview - - - - - - -
- - - {@html data.page.content} -
- -
-
- previous - - {#if prev} - {prev.title} - {/if} -
- -
- next - {#if next} - {next.title} - {/if} -
-
- - diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/01-introduction.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/01-introduction.md deleted file mode 100644 index 2e3cb987c0..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/01-introduction.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Introduction ---- - -Welcome to the Svelte 5 preview documentation! This is intended as a resource for people who already have some familiarity with Svelte and want to learn about the new runes API, which you can learn about in the [Introducing runes](https://svelte.dev/blog/runes) blog post. - -You can try runes for yourself in the [playground](/), or learn more about our plans via the [FAQ](/docs/faq). diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md deleted file mode 100644 index 84062a1cfa..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/02-runes.md +++ /dev/null @@ -1,700 +0,0 @@ ---- -title: Runes ---- - -Svelte 5 introduces _runes_, a powerful set of primitives for controlling reactivity inside your Svelte components and — for the first time — inside `.svelte.js` and `.svelte.ts` modules. - -Runes are function-like symbols that provide instructions to the Svelte compiler. You don't need to import them from anywhere — when you use Svelte, they're part of the language. - -When you [opt in to runes mode](#how-to-opt-in), the non-runes features listed in the 'What this replaces' sections are no longer available. - -> Check out the [Introducing runes](https://svelte.dev/blog/runes) blog post before diving into the docs! - -## `$state` - -Reactive state is declared with the `$state` rune: - -```svelte - - - -``` - -You can also use `$state` in class fields (whether public or private): - -```js -// @errors: 7006 2554 -class Todo { - done = $state(false); - text = $state(); - - constructor(text) { - this.text = text; - } -} -``` - -> In this example, the compiler transforms `done` and `text` into `get`/`set` methods on the class prototype referencing private fields - -Only plain objects and arrays [are made deeply reactive](/#H4sIAAAAAAAAE42QwWrDMBBEf2URhUhUNEl7c21DviPOwZY3jVpZEtIqUBz9e-UUt9BTj7M784bdmZ21wciq48xsPyGr2MF7Jhl9-kXEKxrCoqNLQS2TOqqgPbWd7cgggU3TgCFCAw-RekJ-3Et4lvByEq-drbe_dlsPichZcFYZrT6amQto2pXw5FO88FUYtG90gUfYi3zvWrYL75vxL57zfA07_zfr23k1vjtt-aZ0bQTcbrDL5ZifZcAxKeS8lzDc8X0xDhJ2ItdbX1jlOZMb9VnjyCoKCfMpfwG975NFVwEAAA==) by wrapping them with [`Proxies`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy): - -```svelte - - - - - - -

- {numbers.join(' + ') || 0} - = - {numbers.reduce((a, b) => a + b, 0)} -

-``` - -### What this replaces - -In non-runes mode, a `let` declaration is treated as reactive state if it is updated at some point. Unlike `$state(...)`, which works anywhere in your app, `let` only behaves this way at the top level of a component. - -## `$state.raw` - -State declared with `$state.raw` cannot be mutated; it can only be _reassigned_. In other words, rather than assigning to a property of an object, or using an array method like `push`, replace the object or array altogether if you'd like to update it: - -```diff - - -- - -- -+ - -

- {numbers.join(' + ') || 0} - = - {numbers.reduce((a, b) => a + b, 0)} -

-``` - -This can improve performance with large arrays and objects that you weren't planning to mutate anyway, since it avoids the cost of making them reactive. Note that raw state can _contain_ reactive state (for example, a raw array of reactive objects). - -## `$state.snapshot` - -To take a static snapshot of a deeply reactive `$state` proxy, use `$state.snapshot`: - -```svelte - -``` - -This is handy when you want to pass some state to an external library or API that doesn't expect a proxy, such as `structuredClone`. - -## `$derived` - -Derived state is declared with the `$derived` rune: - -```diff - - - - -+

{count} doubled is {doubled}

-``` - -The expression inside `$derived(...)` should be free of side-effects. Svelte will disallow state changes (e.g. `count++`) inside derived expressions. - -As with `$state`, you can mark class fields as `$derived`. - -### What this replaces - -If the value of a reactive variable is being computed it should be replaced with `$derived` whether it previously took the form of `$: double = count * 2` or `$: { double = count * 2; }` There are some important differences to be aware of: - -- With the `$derived` rune, the value of `double` is always current (for example if you update `count` then immediately `console.log(double)`). With `$:` declarations, values are not updated until right before Svelte updates the DOM -- In non-runes mode, Svelte determines the dependencies of `double` by statically analysing the `count * 2` expression. If you refactor it... - ```js - // @errors: 2304 - const doubleCount = () => count * 2; - $: double = doubleCount(); - ``` - ...that dependency information is lost, and `double` will no longer update when `count` changes. With runes, dependencies are instead tracked at runtime. -- In non-runes mode, reactive statements are ordered _topologically_, meaning that in a case like this... - ```js - // @errors: 2304 - $: triple = double + count; - $: double = count * 2; - ``` - ...`double` will be calculated first despite the source order. In runes mode, `triple` cannot reference `double` before it has been declared. - -## `$derived.by` - -Sometimes you need to create complex derivations that don't fit inside a short expression. In these cases, you can use `$derived.by` which accepts a function as its argument. - -```svelte - - - -``` - -In essence, `$derived(expression)` is equivalent to `$derived.by(() => expression)`. - -## `$effect` - -To run _side-effects_ when the component is mounted to the DOM, and when values change, we can use the `$effect` rune ([demo](/#H4sIAAAAAAAAE31T24rbMBD9lUG7kAQ2sbdlX7xOYNk_aB_rQhRpbAsU2UiTW0P-vbrYubSlYGzmzMzROTPymdVKo2PFjzMzfIusYB99z14YnfoQuD1qQh-7bmdFQEonrOppVZmKNBI49QthCc-OOOH0LZ-9jxnR6c7eUpOnuv6KeT5JFdcqbvbcBcgDz1jXKGg6ncFyBedYR6IzLrAZwiN5vtSxaJA-EzadfJEjKw11C6GR22-BLH8B_wxdByWpvUYtqqal2XB6RVkG1CoHB6U1WJzbnYFDiwb3aGEdDa3Bm1oH12sQLTcNPp7r56m_00mHocSG97_zd7ICUXonA5fwKbPbkE2ZtMJGGVkEdctzQi4QzSwr9prnFYNk5hpmqVuqPQjNnfOJoMF22lUsrq_UfIN6lfSVyvQ7grB3X2mjMZYO3XO9w-U5iLx42qg29md3BP_ni5P4gy9ikTBlHxjLzAtPDlyYZmRdjAbGq7HprEQ7p64v4LU_guu0kvAkhBim3nMplWl8FreQD-CW20aZR0wq12t-KqDWeBywhvexKC3memmDwlHAv9q4Vo2ZK8KtK0CgX7u9J8wXbzdKv-nRnfF_2baTqlYoWUF2h5efl9-n0O6koAMAAA==)): - -```svelte - - - -``` - -The function passed to `$effect` will run when the component mounts, and will re-run after any changes to the values it reads that were declared with `$state` or `$derived` (including those passed in with `$props`). Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied. - -Values that are read asynchronously — after an `await` or inside a `setTimeout`, for example — will _not_ be tracked. Here, the canvas will be repainted when `color` changes, but not when `size` changes ([demo](/#H4sIAAAAAAAAE31T24rbMBD9lUG7kCxsbG_LvrhOoPQP2r7VhSjy2BbIspHGuTT436tLnMtSCiaOzpw5M2dGPrNaKrQs_3VmmnfIcvZ1GNgro9PgD3aPitCdbT8a4ZHCCiMH2pS6JIUEVv5BWMOzJU64fM9evswR0ave3EKLp7r-jFm2iIwri-s9tx5ywDPWNQpaLl9gvYFz4JHotfVqmvBITi9mJA3St4gtF5-qWZUuvEQo5Oa7F8tewT2XrIOsqL2eWpRNS7eGSkpToFZaOEilwODKjBoOLWrco4FtsLQF0XLdoE2S5LGmm6X6QSflBxKod8IW6afssB8_uAslndJuJNA9hWKw9VO91pmJ92XunHlu_J1nMDk8_p_8q0hvO9NFtA47qavcW12fIzJBmM26ZG9ZVjKIs7ke05hdyT0Ixa11Ad-P6ZUtWbgNheI7VJvYQiH14Bz5a-SYxvtwIqHonqsR12ff8ORkQ-chP70T-L9eGO4HvYAFwRh9UCxS13h0YP2CgmoyG5h3setNhWZF_ZDD23AE2ytZwZMQ4jLYgVeV1I2LYgfZBey4aaR-xCppB8VPOdQKjxes4UMgxcVcvwHf4dzAv9K4ko1eScLO5iDQXQFzL5gl7zdJt-nZnXYfbddXspZYsZzMiNPv6S8Bl41G7wMAAA==)): - -```ts -// @filename: index.ts -declare let canvas: { - width: number; - height: number; - getContext( - type: '2d', - options?: CanvasRenderingContext2DSettings - ): CanvasRenderingContext2D; -}; -declare let color: string; -declare let size: number; - -// ---cut--- -$effect(() => { - const context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - - // this will re-run whenever `color` changes... - context.fillStyle = color; - - setTimeout(() => { - // ...but not when `size` changes - context.fillRect(0, 0, size, size); - }, 0); -}); -``` - -An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changes _inside_ an object at dev time, you can use [`$inspect`](#$inspect).) - -```svelte - - - - -

{state.value} doubled is {derived.value}

-``` - -An effect only depends on the values that it read the last time it ran. If `a` is true, changes to `b` will [not cause this effect to rerun](/#H4sIAAAAAAAAE3WQ0WrDMAxFf0U1hTow1vcsMfQ7lj3YjlxEXTvEymC4_vfFC6Ewtidxde8RkrJw5DGJ9j2LoO8oWnGZJvEi-GuqIn2iZ1x1istsa6dLdqaJ1RAG9sigoYdjYs0onfYJm7fdMX85q3dE59CylA30CnJtDWxjSNHjq49XeZqXEChcT9usLUAOpIbHA0yzM78oColGhDVofLS3neZSS6mqOz-XD51ZmGOAGKwne-vztk-956CL0kAJsi7decupf4l658EUZX4I8yTWt93jSI5wFC3PC5aP8g0Aje5DcQEAAA==): - -```ts -let a = false; -let b = false; -// ---cut--- -$effect(() => { - console.log('running'); - - if (a || b) { - console.log('inside if block'); - } -}); -``` - -You can return a function from `$effect`, which will run immediately before the effect re-runs, and before it is destroyed ([demo](/#H4sIAAAAAAAAE42SzW6DMBCEX2Vl5RDaVCQ9JoDUY--9lUox9lKsGBvZC1GEePcaKPnpqSe86_m0M2t6ViqNnu0_e2Z4jWzP3pqGbRhdmrHwHWrCUHvbOjF2Ei-caijLTU4aCYRtDUEKK0-ccL2NDstNrbRWHoU10t8Eu-121gTVCssSBa3XEaQZ9GMrpziGj0p5OAccCgSHwmEgJZwrNNihg6MyhK7j-gii4uYb_YyGUZ5guQwzPdL7b_U4ZNSOvp9T2B3m1rB5cLx4zMkhtc7AHz7YVCVwEFzrgosTBMuNs52SKDegaPbvWnMH8AhUXaNUIY6-hHCldQhUIcyLCFlfAuHvkCKaYk8iYevGGgy2wyyJnpy9oLwG0sjdNe2yhGhJN32HsUzi2xOapNpl_bSLIYnDeeoVLZE1YI3QSpzSfo7-8J5PKbwOmdf2jC6JZyD7HxpPaMk93aHhF6utVKVCyfbkWhy-hh9Z3o_2nQIAAA==)). - -```svelte - - -

{count}

- - - -``` - -### When not to use `$effect` - -In general, `$effect` is best considered something of an escape hatch — useful for things like analytics and direct DOM manipulation — rather than a tool you should use frequently. In particular, avoid using it to synchronise state. Instead of this... - -```svelte - -``` - -...do this: - -```svelte - -``` - -> For things that are more complicated than a simple expression like `count * 2`, you can also use [`$derived.by`](#$derived-by). - -You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for "money spent" and "money left" that are connected to each other. If you update one, the other should update accordingly. Don't use effects for this ([demo](/#H4sIAAAAAAAACpVRQWrDMBD8ihA5ONDG7qEXxQ70HXUPir0KgrUsrHWIMf57pXWdlFIKPe6MZmZnNUtjEYJU77N0ugOp5Jv38knS5NMQroAEcQ79ODQJKUMzWE-n2tWEQIJ60igq8VIUxw0LHhxFbBdIE2TF_s4gmG8Ea5mM9A6MgYaybC-qk5gTlDT8fg15Xo3ZbPlTti2w6ZLNQ1bmjw6uRH0G5DqldX6MjWL1qpaDdheopThb16qrxhGqmX0X0elbNbP3InKWfjH5hvKYku7u_wtKC_-aw8Q9Jk0_UgJNCOvvJHC7SGuDRz0pYRBuxxW7aK9EcXiFbr0NX4bl8cO7vrXGQisVDSMsH8sniirsuSsCAAA=)): - -```svelte - - - - - -``` - -Instead, use callbacks where possible ([demo](/#H4sIAAAAAAAACo1SMW6EMBD8imWluFNyQIo0HERKf13KkMKB5WTJGAsvp0OIv8deMEEJRcqdmZ1ZjzzyWiqwPP0YuRYN8JS_GcOfOA7GD_YGCsHNtu270iOZLTtp8LXQBSpAhi0KxXL2nCTngFkDGh32YFEgHJLjyiioNwTtEunoutclylaz3lSOfPceBziy0ZMFBs9HiFB0V8DoJlQP55ldfOdjTvMBRE275hcn33gv2_vWITh4e3GwzuKfNnSmxBcoKiaT2vSuG1diXvBO6CsUnJFrPpLhxFpNonzcvHdijbjnI0VNLCavRR8HlEYfvcb9O9mf_if4QuBOLqnXWD_9SrU4KJg_ggdDm5W0RokhZbWC-1LiVZiUJdELNJvqaN39raatZC2h4il2PUyf0zcIbC-7lgIAAA==)): - -```svelte - - - - - -``` - -If you need to use bindings, for whatever reason (for example when you want some kind of "writable `$derived`"), consider using getters and setters to synchronise state ([demo](/#H4sIAAAAAAAACpVRQW7DIBD8CkI9JFIau4deiB2p7yg9kHhtIWGMYG3Fsvh7ARs3qnrpCWZGM8MuC22lAkfZ50K16IEy-mEMPVGcTQRuAoUQsBtGe49M5e5WGrxyzVEBEhxQKFKTt7K8ZM4Z0Bi4F4cC4VAeo7JpCtooLRFz7AIzCTXC4ZgpjhZwtHpLfl3TLqvoT-vpdt_0ZMy92TllVzx8AFXx83pdKXEDlQappDZjmCUMXXNqhe6AU3KTumGppV5StCe9eNRLivekSNZNKTKbYGza0_9XFPdzTvc_257kvTJyvxodzgrWP4pkXlEjnVFiZqRV8NiW0wnDSHl-hz4RPm0p2cO390MjWwkNZWhD5Zf_BkCCa6AxAgAA)): - -```svelte - - - - - -``` - -If you absolutely have to update `$state` within an effect and run into an infinite loop because you read and write to the same `$state`, use [untrack](functions#untrack). - -### What this replaces - -The portions of `$: {}` that are triggering side-effects can be replaced with `$effect` while being careful to migrate updates of reactive variables to use `$derived`. There are some important differences: - -- Effects only run in the browser, not during server-side rendering -- They run after the DOM has been updated, whereas `$:` statements run immediately _before_ -- You can return a cleanup function that will be called whenever the effect refires - -Additionally, you may prefer to use effects in some places where you previously used `onMount` and `afterUpdate` (the latter of which will be deprecated in Svelte 5). There are some differences between these APIs as `$effect` should not be used to compute reactive values and will be triggered each time a referenced reactive variable changes (unless using `untrack`). - -## `$effect.pre` - -In rare cases, you may need to run code _before_ the DOM updates. For this we can use the `$effect.pre` rune: - -```svelte - - -
- {#each messages as message} -

{message}

- {/each} -
-``` - -Apart from the timing, `$effect.pre` works exactly like [`$effect`](#$effect) — refer to its documentation for more info. - -### What this replaces - -Previously, you would have used `beforeUpdate`, which — like `afterUpdate` — is deprecated in Svelte 5. - -## `$effect.tracking` - -The `$effect.tracking` rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template ([demo](/#H4sIAAAAAAAACn3PQWrDMBAF0KtMRSA2xPFeUQU5R92FUUZBVB4N1rgQjO9eKSlkEcjyfz6PmVX5EDEr_bUqGidUWp2Z1UHJjWvIvxgFS85pmV1tTHZzYLEDDeIS5RTxGNO12QcClyZOhCSQURbW-wPs0Ht0cpR5dD-Brk3bnqDvwY8xYzGK8j9pmhY-Lay1eqUfm3eizEsFZWtPA5n-eSYZtkUQnDiOghrWV2IzPVswH113d6DrbHl6SpfgA16UruX2vf0BWo7W2y8BAAA=)): - -```svelte - - -

in template: {$effect.tracking()}

-``` - -This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. - -## `$effect.root` - -The `$effect.root` rune is an advanced feature that creates a non-tracked scope that doesn't auto-cleanup. This is useful for -nested effects that you want to manually control. This rune also allows for creation of effects outside of the component initialisation phase. - -```svelte - -``` - -## `$props` - -To declare component props, use the `$props` rune: - -```js -let { optionalProp = 42, requiredProp } = $props(); -``` - -You can use familiar destructuring syntax to rename props, in cases where you need to (for example) use a reserved word like `catch` in ``: - -```js -let { catch: theCatch } = $props(); -``` - -To get all properties, use rest syntax: - -```js -let { a, b, c, ...everythingElse } = $props(); -``` - -You can also use an identifier: - -```js -let props = $props(); -``` - -If you're using TypeScript, you can declare the prop types: - - -```ts -interface MyProps { - required: string; - optional?: number; - partOfEverythingElse?: boolean; -}; - -let { required, optional, ...everythingElse }: MyProps = $props(); -``` - -> In an earlier preview, `$props()` took a type argument. This caused bugs, since in a case like this... -> -> ```ts -> // @errors: 2558 -> let { x = 42 } = $props<{ x?: string }>(); -> ``` -> -> ...TypeScript [widens the type](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwBIAHGHIgZwB4AVeAXnilQE8A+ACgEoAueagbgBQgiCAzwA3vAAe9eABYATPAC+c4qQqUp03uQwwsqAOaqOnIfCsB6a-AB6AfiA) of `x` to be `string | number`, instead of erroring. - -If you're using JavaScript, you can declare the prop types using JSDoc: - -```js -/** @type {{ x: string }} */ -let { x } = $props(); - -// or use @typedef if you want to document the properties: - -/** - * @typedef {Object} MyProps - * @property {string} y Some documentation - */ - -/** @type {MyProps} */ -let { y } = $props(); -``` - -By default props are treated as readonly, meaning reassignments will not propagate upwards and mutations will result in a warning at runtime in development mode. You will also get a runtime error when trying to `bind:` to a readonly prop in a parent component. To declare props as bindable, use [`$bindable()`](#$bindable). - -### What this replaces - -`$props` replaces the `export let` and `export { x as y }` syntax for declaring props. It also replaces `$$props` and `$$restProps`, and the little-known `interface $$Props {...}` construct. - -Note that you can still use `export const` and `export function` to expose things to users of your component (if they're using `bind:this`, for example). - -## `$bindable` - -To declare props as bindable, use `$bindable()`. Besides using them as regular props, the parent can (_can_, not _must_) then also `bind:` to them. - -```svelte - -``` - -You can pass an argument to `$bindable()`. This argument is used as a fallback value when the property is `undefined`. - -```svelte - -``` - -Note that the parent is not allowed to pass `undefined` to a property with a fallback if it `bind:`s to that property. - -## `$inspect` - -The `$inspect` rune is roughly equivalent to `console.log`, with the exception that it will re-run whenever its -argument changes. `$inspect` tracks reactive state deeply, meaning that updating something inside an object -or array using [fine-grained reactivity](/docs/fine-grained-reactivity) will cause it to re-fire. ([Demo:](/#H4sIAAAAAAAACkWQ0YqDQAxFfyUMhSotdZ-tCvu431AXtGOqQ2NmmMm0LOK_r7Utfby5JzeXTOpiCIPKT5PidkSVq2_n1F7Jn3uIcEMSXHSw0evHpAjaGydVzbUQCmgbWaCETZBWMPlKj29nxBDaHj_edkAiu12JhdkYDg61JGvE_s2nR8gyuBuiJZuDJTyQ7eE-IEOzog1YD80Lb0APLfdYc5F9qnFxjiKWwbImo6_llKRQVs-2u91c_bD2OCJLkT3JZasw7KLA2XCX31qKWE6vIzNk1fKE0XbmYrBTufiI8-_8D2cUWBA_AQAA)) - -```svelte - - - - -``` - -`$inspect` returns a property `with`, which you can invoke with a callback, which will then be invoked instead of `console.log`. The first argument to the callback is either `"init"` or `"update"`, all following arguments are the values passed to `$inspect`. [Demo:](/#H4sIAAAAAAAACkVQ24qDMBD9lSEUqlTqPlsj7ON-w7pQG8c2VCchmVSK-O-bKMs-DefKYRYx6BG9qL4XQd2EohKf1opC8Nsm4F84MkbsTXAqMbVXTltuWmp5RAZlAjFIOHjuGLOP_BKVqB00eYuKs82Qn2fNjyxLtcWeyUE2sCRry3qATQIpJRyD7WPVMf9TW-7xFu53dBcoSzAOrsqQNyOe2XUKr0Xi5kcMvdDB2wSYO-I9vKazplV1-T-d6ltgNgSG1KjVUy7ZtmdbdjqtzRcphxMS1-XubOITJtPrQWMvKnYB15_1F7KKadA_AQAA) - -```svelte - - - -``` - -A convenient way to find the origin of some change is to pass `console.trace` to `with`: - -```js -// @errors: 2304 -$inspect(stuff).with(console.trace); -``` - -> `$inspect` only works during development. - -## `$host` - -Retrieves the `this` reference of the custom element that contains this component. Example: - -```svelte - - - - - -``` - -> Only available inside custom element components, and only on the client-side - -## How to opt in - -Current Svelte code will continue to work without any adjustments. Components using the Svelte 4 syntax can use components using runes and vice versa. - -The easiest way to opt in to runes mode is to just start using them in your code. Alternatively, you can force the compiler into runes or non-runes mode either on a per-component basis... - - -```svelte - - - -``` - -...or for your entire app: - -```js -/// file: svelte.config.js -export default { - compilerOptions: { - runes: true - } -}; -``` diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md deleted file mode 100644 index b3fe34d21a..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/03-snippets.md +++ /dev/null @@ -1,269 +0,0 @@ ---- -title: Snippets ---- - -Snippets, and _render tags_, are a way to create reusable chunks of markup inside your components. Instead of writing duplicative code like [this](/#H4sIAAAAAAAAE5VUYW-kIBD9K8Tmsm2yXXRzvQ-s3eR-R-0HqqOQKhAZb9sz_vdDkV1t000vRmHewMx7w2AflbIGG7GnPlK8gYhFv42JthG-m9Gwf6BGcLbVXZuPSGrzVho8ZirDGpDIhldgySN5GpEMez9kaNuckY1ANJZRamRuu2ZnhEZt6a84pvs43mzD4pMsUDDi8DMkQFYCGdkvsJwblFq5uCik9bmJ4JZwUkv1eoknWigX2eGNN6aGXa6bjV8ybP-X7sM36T58SVcrIIV2xVIaA41xeD5kKqWXuqpUJEefOqVuOkL9DfBchGrzWfu0vb-RpTd3o-zBR045Ga3HfuE5BmJpKauuhbPtENlUF2sqR9jqpsPSxWsMrlngyj3VJiyYjJXb1-lMa7IWC-iSk2M5Zzh-SJjShe-siq5kpZRPs55BbSGU5YPyte4vVV_VfFXxVb10dSLf17pS2lM5HnpPxw4Zpv6x-F57p0jI3OKlVnhv5V9wPQrNYQQ9D_f6aGHlC89fq1Z3qmDkJCTCweOGF4VUFSPJvD_DhreVdA0eu8ehJJ5x91dBaBkpWm3ureCFPt3uzRv56d4kdp-2euG38XZ6dsnd3ZmPG9yRBCrzRUvi-MccOdwz3qE-fOZ7AwAhlrtTUx3c76vRhSwlFBHDtoPhefgHX3dM0PkEAAA=)... - -```svelte -{#each images as image} - {#if image.href} - -
- {image.caption} -
{image.caption}
-
-
- {:else} -
- {image.caption} -
{image.caption}
-
- {/if} -{/each} -``` - -...you can write [this](/#H4sIAAAAAAAAE5VUYW-bMBD9KxbRlERKY4jWfSA02n5H6QcXDmwVbMs-lnaI_z6D7TTt1moTAnPvzvfenQ_GpBEd2CS_HxPJekjy5IfWyS7BFz0b9id0CM62ajDVjBS2MkLjqZQldoBE9KwFS-7I_YyUOPqlRGuqnKw5orY5pVpUduj3mitUln5LU3pI0_UuBp9FjTwnDr9AHETLMSeHK6xiGoWSLi9yYT034cwSRjohn17zcQPNFTs8s153sK9Uv_Yh0-5_5d7-o9zbD-UqCaRWrllSYZQxLw_HUhb0ta-y4NnJUxfUvc7QuLJSaO0a3oh2MLBZat8u-wsPnXzKQvTtVVF34xK5d69ThFmHEQ4SpzeVRediTG8rjD5vBSeN3E5JyHh6R1DQK9-iml5kjzQUN_lSgVU8DhYLx7wwjSvRkMDvTjiwF4zM1kXZ7DlF1eN3A7IG85e-zRrYEjjm0FkI4Cc7Ripm0pHOChexhcWXzreeZyRMU6Mk3ljxC9w4QH-cQZ_b3T5pjHxk1VNr1CDrnJy5QDh6XLO6FrLNSRb2l9gz0wo3S6m7HErSgLsPGMHkpDZK31jOanXeHPQz-eruLHUP0z6yTbpbrn223V70uMXNSpQSZjpL0y8hcxxpNqA6_ql3BQAxlxvfpQ_uT9GrWjQC6iRHM8D0MP0GQsIi92QEAAA=): - -```diff -+{#snippet figure(image)} -
- {image.caption} -
{image.caption}
-
-+{/snippet} - -{#each images as image} - {#if image.href} - -+ {@render figure(image)} - - {:else} -+ {@render figure(image)} - {/if} -{/each} -``` - -Snippet parameters can be destructured ([demo](/#H4sIAAAAAAAAE5VTYW-bMBD9KyeiKYlEY4jWfSAk2n5H6QcXDmwVbMs2SzuL_z6DTRqp2rQJ2Ycfd_ced2eXtLxHkxRPLhF0wKRIfiiVpIl9V_PB_MTeoj8bOep6RkpTa67spRKV7dECH2iHBs7wNCOVdcFU1ui6gC2zVpmCEMVrMw4HxaSVhnzLMnLMsm26Ol95Y1kBHr9BDHnHbAHHO6ymynIpfF7LuAncwKgBCj0Xrx_5mMb2jh3f6KB6PNRy2AaXKf1fuY__KPfxj3KlQGikL5aQdpUxm-dTJUryUVdRsvwSqEviX2fIbYzgSvmCt7wbNe4ceMUpRIoUFkkpBBkw7ZfMZXC-BLKSDx3Q3p5djJrA-SR-X4K9DdHT6u-jo-flFlKSO3ThIDcSR6LIKUhGWrN1QGhs16LLbXgbjoe5U1PkozCfzu7uy2WtpfuuUTSo1_9ffPZrJKGLoyuwNxjBv0Q4wmdSR2aFi9jS2Pc-FIrlEKeilcI-GP4LfVtxOM1gyO1XSLp6vtD6tdNyFE0BV8YtngKuaNNw0RWQx_jKDlR33M9E5h-PQhZxfxEt6gIaLdWDYbSR191RvcFXv_LMb7p7obssXZ5Dvt_f9HgzdzZKibOZZ9mXmHkdTTpaefqsd4OIay4_hksd_I0fZMNbjk1SWD3i9Dz9BpdEPu8sBAAA)): - -```svelte -{#snippet figure({ src, caption, width, height })} -
- {caption} -
{caption}
-
-{/snippet} -``` - -Like function declarations, snippets can have an arbitrary number of parameters, which can have default values. You cannot use rest parameters however. - -## Snippet scope - -Snippets can be declared anywhere inside your component. They can reference values declared outside themselves, for example in the ` - -{#snippet hello(name)} -

hello {name}! {message}!

-{/snippet} - -{@render hello('alice')} -{@render hello('bob')} -``` - -...and they are 'visible' to everything in the same lexical scope (i.e. siblings, and children of those siblings): - -```svelte -
- {#snippet x()} - {#snippet y()}...{/snippet} - - - {@render y()} - {/snippet} - - - {@render y()} -
- - -{@render x()} -``` - -Snippets can reference themselves and each other ([demo](/#H4sIAAAAAAAAE2WPTQqDMBCFrxLiRqH1Zysi7TlqF1YnENBJSGJLCYGeo5tesUeosfYH3c2bee_jjaWMd6BpfrAU6x5oTvdS0g01V-mFPkNnYNRaDKrxGxto5FKCIaeu1kYwFkauwsoUWtZYPh_3W5FMY4U2mb3egL9kIwY0rbhgiO-sDTgjSEqSTvIDs-jiOP7i_MHuFGAL6p9BtiSbOTl0GtzCuihqE87cqtyam6WRGz_vRcsZh5bmRg3gju4Fptq_kzQBAAA=)): - -```svelte -{#snippet blastoff()} - 🚀 -{/snippet} - -{#snippet countdown(n)} - {#if n > 0} - {n}... - {@render countdown(n - 1)} - {:else} - {@render blastoff()} - {/if} -{/snippet} - -{@render countdown(10)} -``` - -## Passing snippets to components - -Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): - -```svelte - - -{#snippet header()} - fruit - qty - price - total -{/snippet} - -{#snippet row(d)} - {d.name} - {d.qty} - {d.price} - {d.qty * d.price} -{/snippet} - - -``` - -As an authoring convenience, snippets declared directly _inside_ a component implicitly become props _on_ the component ([demo](/#H4sIAAAAAAAAE41Sy27bMBD8lYVcwHYrW4kBXxRFaP-htzgHSqQsojLJkuu2BqF_74qUrfhxCHQRh7MzO9z1SSM74ZL8zSeKHUSSJz-MSdIET2Y4uD-iQ0Fnp4-2HpDC1VYaLHdqh_JgtEX4yapOQGP1AebrLJzWsXD-QjQi1lo5JMZRooNXeBuwHXoYLHOYM2OoiXkKv_GUwzYFY2VNFxvo0xtqxRR9F-7z04X8fE-uW2GtnJQ3E_tpvYV-oL9Ti0U2hVJFjMMZslcfW-5DWj9zShojEFrBuLCLZR_9CmzLQCwy-psw8rxBgvkNhhpZd8F8NppE7Stbq_8u-GTKS8_XQ9Keqnl5BZP1AzTYP2bDV7i7_9hLEeda0iocNJeNFDzJ0R5Fn142JzA-uzsdBfLhldPxPdMhIPS0H1-M1cYtlnejwdBDfBXZjHXTFOg4BhuOtvTfrVDEmAZG2ew5ezYV-Ew2fVzVAivNTyPHzwSr29AlMAe8f6g-zuWDts-GusAmdBSkv3P7qnB4GpMEEHwsRPEPV6yTe5VDJxp8iXClLRmtnGG1VHva3oCPHQd9QJsrbFd1Kzu-2Khvz8uzZsXqX3urj4rnMBNCXNUG83zf6Yp1C2yXKdxA_KJjGOfRfb0Vh7MKDShEuV-M9_4_nq6svF4EAAA=)): - -```svelte - -
- {#snippet header()} - - - - - {/snippet} - - {#snippet row(d)} - - - - - {/snippet} -
fruitqtypricetotal{d.name}{d.qty}{d.price}{d.qty * d.price}
-``` - -Any content inside the component tags that is _not_ a snippet declaration implicitly becomes part of the `children` snippet ([demo](/#H4sIAAAAAAAAE41S247aMBD9lVFYCegGsiDxks1G7T_0bdkHJ3aI1cR27aEtsvzvtZ0LZeGhiiJ5js-cmTMemzS8YybJ320iSM-SPPmmVJImeFEhML9Yh8zHRp51HZDC1JorLI_iiLxXUiN8J1XHoNGyh-U2i9F2SFy-epon1lIY9IwzRwNv8B6wI1oIJXNYEqV8E8sUfuIlh0MKSvPaX-zBpZ-oFRH-m7m7l5m8uyfXLdOaX5X3V_bL9gAu0D98i0V2NSWKwQ4lSN7s0LKLbgtsyxgXmT9NiBe-iaP-DYISSTcj4bcLI7hSDEHL3yu6dkPfBdLS0m1o3nk-LW9gX-gBGss9ZsMXuLu32VjZBdfRaelft5eUN5zRJEd9Zi6dlyEy_ncdOm_IxsGlULe8o5qJNFgE5x_9SWmpzGp9N2-MXQxz4c2cOQ-lZWQyF0Jd2q_-mjI9U1fr4FBPE8iuKTbjjRt2sMBK0svIsQtG6jb2CsQAdQ_1x9f5R9tmIS-yPToK-tNkQRQGL6ObCIIdEpH9wQ3p-Enk0LEGXwe4ktoX2hhFai5Ofi0jPnYc9QF1LrDdRK-rvXjerSfNitQ_TlqeBc1hwRi7yY3F81MnK9KtsF2n8Amis44ilA7VtwfWTyr-kaKV-_X4cH8BTOhfRzcEAAA=)): - -```diff - -- {#snippet header()} -- -- -- -- -- {/snippet} -+ -+ -+ -+ - - -
fruitqtypricetotalfruitqtypricetotal
-``` - -```diff - - - -- {#if header} -+ {#if children} - -- {@render header()} -+ {@render children()} - - {/if} - - -
-``` - -> Note that you cannot have a prop called `children` if you also have content inside the component — for this reason, you should avoid having props with that name - -## Typing snippets - -Snippets implement the `Snippet` interface imported from `'svelte'`: - -```diff -- -``` - -With this change, red squigglies will appear if you try and use the component without providing a `data` prop and a `row` snippet. Notice that the type argument provided to `Snippet` is a tuple, since snippets can have multiple parameters. - -We can tighten things up further by declaring a generic, so that `data` and `row` refer to the same type: - -```diff -- -``` - -## Creating snippets programmatically - -In advanced scenarios, you may need to create a snippet programmatically. For this, you can use [`createRawSnippet`](/docs/imports#svelte-createrawsnippet) - -## Snippets and slots - -In Svelte 4, content can be passed to components using [slots](https://svelte.dev/docs/special-elements#slot). Snippets are more powerful and flexible, and as such slots are deprecated in Svelte 5. - -They continue to work, however, and you can mix and match snippets and slots in your components. - -When using custom elements, you should still use `` like before. In a future version, when Svelte removes its internal version of slots, it will leave those slots as-is, i.e. output a regular DOM tag instead of transforming it. diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md deleted file mode 100644 index 5124ae291d..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/04-event-handlers.md +++ /dev/null @@ -1,205 +0,0 @@ ---- -title: Event handlers ---- - -Event handlers have been given a facelift in Svelte 5. Whereas in Svelte 4 we use the `on:` directive to attach an event listener to an element, in Svelte 5 they are properties like any other: - -```diff - - -- -``` - -Since they're just properties, you can use the normal shorthand syntax... - -```svelte - - - -``` - -...though when using a named event handler function it's usually better to use a more descriptive name. - -Traditional `on:` event handlers will continue to work, but are deprecated in Svelte 5. - -## Component events - -In Svelte 4, components could emit events by creating a dispatcher with [`createEventDispatcher`](https://svelte.dev/docs/svelte#createeventdispatcher). - -This function is deprecated in Svelte 5. Instead, components should accept _callback props_ - which means you then pass functions as properties to these components ([demo](/#H4sIAAAAAAAACo1US27bMBC9yoBtELu2ZDmAG0CRhPYG3VddyPIwIUKRgjiOkwrcd9VFL5BV75cjFKQo2e5_IQnzeW-GM3zqGRcSDUs_9kxVDbKUvW9btmT01DrDPKAkZEtm9L6rnSczdSdaKkpVkmha3RF82Dct8E43cBmvnBEPsMsbl-QeiQRGfEbI4bWhinC23sxvxsh23xk6hnglDfqoKonvVU1CK-jQIM3m0HtOCmzrzVCDRg4P9j5bqmx1bFZlrjPfteKyIsz7WasP2M0hL85YFzn4QGAWHGbeX8D1Zj41S90-1LHuvcM_kp4QJPNhDNFpCUew8i32rwQfCnjObLsn0gq0qqWo7_Pez8AWCg-wraTUWmWrIcevIzNtpaCWlTF5ybZaNyUrXp6_fc9WLlKUqk9RGrS_SR7oSgaGniTmJTN1JTGFPomTNbzxbduSFcORXp6_fvEkE_FKcOun7PE-zRcIM2i1EW6NKXDxiLswWomcUkiCRbo9Ggexo7sU1klyETx3KG7v6MzFtaLIdea9D4eRCB8pqqS4VSnUqGhapRQKo4nnZmxNuJQIH1CRSUFpNV0g94nDbMajUFep8TB-SJDEV-YcoXUzpldKNNWQ7d1JvDHAdXeout0Z6t09PvGuatDAKT65gB7CMpL4LdjBfbU5819vxoAbz0lkcA9aCJthS9boneACdyx119guJ_E7jfyv-p10ewhqWkJQAFin5LbTrZkdJe5v-1HiXvzn6vz5rs-8hAJ7EJUtgn1y7f8ADN1MwGD_G-gBUWSLaModfnA-kELvvxb-Bl8sbLGY4L_O-5P9ATwVcA54BQAA)): - -```svelte - - - { - size += power; - if (size > 75) burst = true; - }} - deflate={(power) => { - if (size > 0) size -= power; - }} -/> - -{#if burst} - - 💥 -{:else} - - 🎈 - -{/if} -``` - -```svelte - - - - - -Pump power: {power} - -``` - -## Bubbling events - -Instead of doing ` -``` - -Note that this also means you can 'spread' event handlers onto the element along with other props: - -```svelte - - - -``` - -## Event modifiers - -In Svelte 4, you can add event modifiers to handlers: - -```svelte - -``` - -Modifiers are specific to `on:` and as such do not work with modern event handlers. Adding things like `event.preventDefault()` inside the handler itself is preferable, since all the logic lives in one place rather than being split between handler and modifiers. - -Since event handlers are just functions, you can create your own wrappers as necessary: - -```svelte - - - -``` - -There are three modifiers — `capture`, `passive` and `nonpassive` — that can't be expressed as wrapper functions, since they need to be applied when the event handler is bound rather than when it runs. - -For `capture`, we add the modifier to the event name: - -```svelte - -``` - -Changing the [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#using_passive_listeners) option of an event handler, meanwhile, is not something to be done lightly. If you have a use case for it — and you probably don't! - then you will need to use an action to apply the event handler yourself. - -## Multiple event handlers - -In Svelte 4, this is possible: - -```svelte - -``` - -This is something of an anti-pattern, since it impedes readability (if there are many attributes, it becomes harder to spot that there are two handlers unless they are right next to each other) and implies that the two handlers are independent, when in fact something like `event.stopImmediatePropagation()` inside `one` would prevent `two` from being called. - -Duplicate attributes/properties on elements — which now includes event handlers — are not allowed. Instead, do this: - -```svelte - -``` - -When spreading props, local event handlers must go _after_ the spread, or they risk being overwritten: - -```svelte - -``` - -## Why the change? - -By deprecating `createEventDispatcher` and the `on:` directive in favour of callback props and normal element properties, we: - -- reduce Svelte's learning curve -- remove boilerplate, particularly around `createEventDispatcher` -- remove the overhead of creating `CustomEvent` objects for events that may not even have listeners -- add the ability to spread event handlers -- add the ability to know which event handlers were provided to a component -- add the ability to express whether a given event handler is required or optional -- increase type safety (previously, it was effectively impossible for Svelte to guarantee that a component didn't emit a particular event) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md deleted file mode 100644 index 7cbec56e17..0000000000 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Imports ---- - -As well as runes, Svelte 5 introduces a handful of new things you can import, alongside existing ones like `getContext`, `setContext` and `tick`. - -## `svelte` - -### `flushSync` - -Forces any pending effects (including DOM updates) to be applied immediately, rather than in the future. This is mainly useful in a testing context — you'll rarely need it in application code. - -```svelte - - -{count} - -``` - -### `mount` - -Instantiates a component and mounts it to the given target: - -```js -// @errors: 2322 -import { mount } from 'svelte'; -import App from './App.svelte'; - -const app = mount(App, { - target: document.querySelector('#app'), - props: { some: 'property' } -}); -``` - -Note that unlike calling `new App(...)` in Svelte 4, things like effects (including `onMount` callbacks, and action functions) will not run during `mount`. If you need to force pending effects to run (in the context of a test, for example) you can do so with `flushSync()`. - -### `hydrate` - -Like `mount`, but will reuse up any HTML rendered by Svelte's SSR output (from the [`render`](#svelte-server-render) function) inside the target and make it interactive: - -```js -// @errors: 2322 -import { hydrate } from 'svelte'; -import App from './App.svelte'; - -const app = hydrate(App, { - target: document.querySelector('#app'), - props: { some: 'property' } -}); -``` - -As with `mount`, effects will not run during `hydrate` — use `flushSync()` immediately afterwards if you need them to. - -### `unmount` - -Unmounts a component created with [`mount`](#svelte-mount) or [`hydrate`](#svelte-hydrate): - -```js -// @errors: 1109 -import { mount, unmount } from 'svelte'; -import App from './App.svelte'; - -const app = mount(App, {...}); - -// later -unmount(app); -``` - -### `untrack` - -To prevent something from being treated as an `$effect`/`$derived` dependency, use `untrack`: - -```svelte - -``` - -### `createRawSnippet` - -An advanced API designed for people building frameworks that integrate with Svelte, `createRawSnippet` allows you to create [snippets](/docs/snippets) programmatically for use with `{@render ...}` tags: - -```js -import { createRawSnippet } from 'svelte'; - -const greet = createRawSnippet((name) => { - return { - render: () => ` -

Hello ${name()}!

- `, - setup: (node) => { - $effect(() => { - node.textContent = `Hello ${name()}!`; - }); - } - }; -}); -``` - -The `render` function is called during server-side rendering, or during `mount` (but not during `hydrate`, because it already ran on the server), and must return HTML representing a single element. - -The `setup` function is called during `mount` or `hydrate` with that same element as its sole argument. It is responsible for ensuring that the DOM is updated when the arguments change their value — in this example, when `name` changes: - -```svelte -{@render greet(name)} -``` - -If `setup` returns a function, it will be called when the snippet is unmounted. If the snippet is fully static, you can omit the `setup` function altogether. - -## `svelte/reactivity` - -Svelte provides reactive `SvelteMap`, `SvelteSet`, `SvelteDate` and `SvelteURL` classes. These can be imported from `svelte/reactivity` and used just like their native counterparts. [Demo:](https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAE32QwUrEMBBAf2XMpQrb9t7tFrx7UjxZYWM6NYFkEpJJ16X03yWK9OQeZ3iPecwqZmMxie5tFSQdik48hiAOgq-hDGlByygOIvkcVdn0SUUTeBhpZOOCjwwrvPxgr89PsMEcvYPqV2wjSsVmMXytjiMVR3lKDDlaOAHhZVfvK80cUte2-CVdsNgo79ogWVcPx5H6dj9M_V1dg9KSPjEBe2CNCZumgboeRuoNhczwYWjqFmkzntYcbROiZ6-83f5HtE9c3nADKUF_yEi9jnvQxVgLOUySEc464nwGSRMsRiEsGJO8mVeEbRAH4fxkZoOT6Dhm3N63b9_bGfOlAQAA) - -```svelte - - - - - - - -
- - - -``` - -## `svelte/events` - -Where possible, event handlers added with [attributes like `onclick`](/docs/event-handlers) use a technique called _event delegation_. It works by creating a single handler for each event type on the root DOM element, rather than creating a handler for each element, resulting in better performance and memory usage. - -Delegated event handlers run after other event handlers. In other words, a handler added programmatically with [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) will run _before_ a handler added declaratively with `onclick`, regardless of their relative position in the DOM ([demo](/#H4sIAAAAAAAAE41Sy2rDMBD8lUUXJxDiu-sYeugt_YK6h8RaN6LyykgrQzH6965shxJooQc_RhrNzA6aVW8sBlW9zYouA6pKPY-jOij-GjMIE1pGwcFF3-WVOnTejNy01LIZRucZZnD06iIxJOi9G6BYjxVPmZQfiwzaTBkL2ti73R5ODcwLiftIHRtHcLuQtuhlc9tpuSyBbyZAuLloNfhIELBzpO8E-Q_O4tG6j13hIqO_y0BvPOpiv0bhtJ1Y3pLoeNH6ZULiswmMJLZFZ033WRzuAvstdMseOXqCh9SriMfBTfgPnZxg-aYM6_KnS6pFCK6GdJVHPc0C01JyfY0slUnHi-JpfgjwSzUycdgmfOjFEP3RS1qdhJ8dYMDFt1yNmxxU0jRyCwanTW9Qq4p9xPSevgHI3m43QAIAAA==)). It also means that calling `event.stopPropagation()` inside a declarative handler _won't_ prevent the programmatic handler (created inside an action, for example) from running. - -To preserve the relative order, use `on` rather than `addEventListener` ([demo](/#H4sIAAAAAAAAE3VRy26DMBD8lZUvECkqdwpI_YB-QdJDgpfGqlkjex2pQv73rnmoStQeMB52dnZmmdVgLAZVn2ZFlxFVrd6mSR0Vf08ZhDtaRsHBRd_nL03ovZm4O9OZzTg5zzCDo3cXiSHB4N0IxdpWvD6RnuoV3pE4rLT8WGTQ5p6xoE20LA_QdjAvJB4i9WxE6nYhbdFLcaucuaqAbyZAuLloNfhIELB3pHeC3IOz-GLdZ1m4yOh3GRiMR10cViucto7l9MjRk9gvxdsRit6a_qs47q1rT8qvpvpdDjXChqshXWdT7SwwLVtrrpElnAguSu38EPCPEOItbF4eEhiifxKkdZLw8wQYcZlbrYO7bFTcdPJbR6fNYFCrmn3E9JF-AJZOg9MRAgAA)): - -```js -// @filename: index.ts -const element: Element = null as any; -// ---cut--- -import { on } from 'svelte/events'; - -const off = on(element, 'click', () => { - console.log('element was clicked'); -}); - -// later, if we need to remove the event listener: -off(); -``` - -`on` also accepts an optional fourth argument which matches the options argument for `addEventListener`. - -## `svelte/server` - -### `render` - -Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app: - -```js -// @errors: 2724 2305 2307 -import { render } from 'svelte/server'; -import App from './App.svelte'; - -const result = render(App, { - props: { some: 'property' } -}); -``` - -If the `css` compiler option was set to `'injected'`, ` diff --git a/sites/svelte-5-preview/src/routes/status/data.json/+server.js b/sites/svelte-5-preview/src/routes/status/data.json/+server.js deleted file mode 100644 index cfa65b0065..0000000000 --- a/sites/svelte-5-preview/src/routes/status/data.json/+server.js +++ /dev/null @@ -1,6 +0,0 @@ -import { json } from '@sveltejs/kit'; -import results from '../results.json'; - -export function GET() { - return json(results); -} diff --git a/sites/svelte-5-preview/src/routes/svelte/[...path]/+server.js b/sites/svelte-5-preview/src/routes/svelte/[...path]/+server.js deleted file mode 100644 index 4e22542435..0000000000 --- a/sites/svelte-5-preview/src/routes/svelte/[...path]/+server.js +++ /dev/null @@ -1,37 +0,0 @@ -import compiler_js from '../../../../../../packages/svelte/compiler/index.js?url'; -import package_json from '../../../../../../packages/svelte/package.json?url'; -import { read } from '$app/server'; - -const files = import.meta.glob('../../../../../../packages/svelte/src/**/*.js', { - eager: true, - query: '?url', - import: 'default' -}); - -const prefix = '../../../../../../packages/svelte/'; - -export const prerender = true; - -export function entries() { - const entries = Object.keys(files).map((path) => ({ path: path.replace(prefix, '') })); - entries.push({ path: 'compiler/index.js' }, { path: 'package.json' }); - return entries; -} - -// service worker requests files under this path to load the compiler and runtime -export async function GET({ params }) { - let file = ''; - - if (params.path === 'compiler/index.js') { - file = compiler_js; - } else if (params.path === 'package.json') { - file = package_json; - } else { - file = /** @type {string} */ (files[prefix + params.path]); - - // remove query string added by Vite when changing source code locally - file = file.split('?')[0]; - } - - return read(file); -} diff --git a/sites/svelte-5-preview/static/favicon.png b/sites/svelte-5-preview/static/favicon.png deleted file mode 100644 index 825b9e65af7c104cfb07089bb28659393b4f2097..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH Date: Thu, 12 Dec 2024 00:22:30 +0100 Subject: [PATCH 033/389] fix: correctly handle ssr for `reactivity/window` (#14681) --- .changeset/khaki-guests-switch.md | 5 +++++ packages/svelte/src/reactivity/window/index.js | 8 +++++--- .../samples/reactivity-window/_expected.html | 1 + .../samples/reactivity-window/main.svelte | 14 ++++++++++++++ packages/svelte/types/index.d.ts | 2 +- 5 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .changeset/khaki-guests-switch.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte diff --git a/.changeset/khaki-guests-switch.md b/.changeset/khaki-guests-switch.md new file mode 100644 index 0000000000..f32e71084b --- /dev/null +++ b/.changeset/khaki-guests-switch.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: correctly handle ssr for `reactivity/window` diff --git a/packages/svelte/src/reactivity/window/index.js b/packages/svelte/src/reactivity/window/index.js index 16e8b7b87b..8c50a5c440 100644 --- a/packages/svelte/src/reactivity/window/index.js +++ b/packages/svelte/src/reactivity/window/index.js @@ -124,7 +124,7 @@ export const online = new ReactiveValue( * `devicePixelRatio.current` is a reactive view of `window.devicePixelRatio`. On the server it is `undefined`. * Note that behaviour differs between browsers — on Chrome it will respond to the current zoom level, * on Firefox and Safari it won't. - * @type {{ get current(): number }} + * @type {{ get current(): number | undefined }} * @since 5.11.0 */ export const devicePixelRatio = /* @__PURE__ */ new (class DevicePixelRatio { @@ -144,11 +144,13 @@ export const devicePixelRatio = /* @__PURE__ */ new (class DevicePixelRatio { } constructor() { - this.#update(); + if (BROWSER) { + this.#update(); + } } get current() { get(this.#dpr); - return window.devicePixelRatio; + return BROWSER ? window.devicePixelRatio : undefined; } })(); diff --git a/packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html new file mode 100644 index 0000000000..ee65cb76c7 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/_expected.html @@ -0,0 +1 @@ +

devicePixelRatio:

innerHeight:

innerWidth:

online:

outerHeight:

outerWidth:

screenLeft:

screenTop:

scrollX:

scrollY:

\ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte new file mode 100644 index 0000000000..e84e41bf63 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/reactivity-window/main.svelte @@ -0,0 +1,14 @@ + + +

devicePixelRatio: {devicePixelRatio.current}

+

innerHeight: {innerHeight.current}

+

innerWidth: {innerWidth.current}

+

online: {online.current}

+

outerHeight: {outerHeight.current}

+

outerWidth: {outerWidth.current}

+

screenLeft: {screenLeft.current}

+

screenTop: {screenTop.current}

+

scrollX: {scrollX.current}

+

scrollY: {scrollY.current}

\ No newline at end of file diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 206f9931f5..435476d703 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2015,7 +2015,7 @@ declare module 'svelte/reactivity/window' { * @since 5.11.0 */ export const devicePixelRatio: { - get current(): number; + get current(): number | undefined; }; class ReactiveValue { From 7aa80fc2a7ae1e622ec2c2b49d4654fa8aef257a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 18:29:35 -0500 Subject: [PATCH 034/389] Version Packages (#14682) Co-authored-by: github-actions[bot] --- .changeset/khaki-guests-switch.md | 5 ----- packages/svelte/CHANGELOG.md | 6 ++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .changeset/khaki-guests-switch.md diff --git a/.changeset/khaki-guests-switch.md b/.changeset/khaki-guests-switch.md deleted file mode 100644 index f32e71084b..0000000000 --- a/.changeset/khaki-guests-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: correctly handle ssr for `reactivity/window` diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index f4b7c18d7d..978e841bf8 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,11 @@ # svelte +## 5.11.2 + +### Patch Changes + +- fix: correctly handle ssr for `reactivity/window` ([#14681](https://github.com/sveltejs/svelte/pull/14681)) + ## 5.11.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f461a4b4c3..e95341a0bd 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.11.1", + "version": "5.11.2", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 1cedefa314..e264eace2c 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.11.1'; +export const VERSION = '5.11.2'; export const PUBLIC_VERSION = '5'; From 8ba1b9ddd0bc65b9a790030aa8b7c73ae2990543 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 12 Dec 2024 10:42:10 +0000 Subject: [PATCH 035/389] fix: avoid mutation validation for invalidate_inner_signals (#14688) * fix: avoid mutation validation for invalidate_inner_signals * add test * Update packages/svelte/src/internal/client/runtime.js --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/strong-pandas-provide.md | 5 +++++ .../svelte/src/internal/client/runtime.js | 7 ++++--- .../binding-interop-derived/Comp.svelte | 12 ++++++++++++ .../binding-interop-derived/_config.js | 5 +++++ .../binding-interop-derived/main.svelte | 19 +++++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 .changeset/strong-pandas-provide.md create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte diff --git a/.changeset/strong-pandas-provide.md b/.changeset/strong-pandas-provide.md new file mode 100644 index 0000000000..0fe7e70c6d --- /dev/null +++ b/.changeset/strong-pandas-provide.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: avoid mutation validation for invalidate_inner_signals diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 4928419d16..5d53ca3360 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -29,7 +29,7 @@ import { } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; -import { mutate, set, source } from './reactivity/sources.js'; +import { internal_set, set, source } from './reactivity/sources.js'; import { destroy_derived, execute_derived, update_derived } from './reactivity/deriveds.js'; import * as e from './errors.js'; import { lifecycle_outside_component } from '../shared/errors.js'; @@ -960,11 +960,12 @@ export function invalidate_inner_signals(fn) { if ((signal.f & LEGACY_DERIVED_PROP) !== 0) { for (const dep of /** @type {Derived} */ (signal).deps || []) { if ((dep.f & DERIVED) === 0) { - mutate(dep, null /* doesnt matter */); + // Use internal_set instead of set here and below to avoid mutation validation + internal_set(dep, dep.v); } } } else { - mutate(signal, null /* doesnt matter */); + internal_set(signal, signal.v); } } } diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte new file mode 100644 index 0000000000..c309299748 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/Comp.svelte @@ -0,0 +1,12 @@ + + +{@render children({ props: snippetProps })} diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js new file mode 100644 index 0000000000..e52264c793 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/_config.js @@ -0,0 +1,5 @@ +import { test } from '../../test'; + +export default test({ + html: '' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte new file mode 100644 index 0000000000..5900ddc846 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/binding-interop-derived/main.svelte @@ -0,0 +1,19 @@ + + + + + + {#snippet children({ props })} + + {/snippet} + From ef8bd6adeb238f2d8ccc8c04547e9e16cb932c25 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 12 Dec 2024 12:16:51 -0500 Subject: [PATCH 036/389] fix: better handle hydration of script/style elements (alternative) (#14683) * alternative approach to #14624 * changeset * fix * lint --- .changeset/rotten-yaks-nail.md | 5 +++++ .../src/internal/client/dom/blocks/svelte-element.js | 6 ++++++ packages/svelte/src/internal/server/index.js | 9 +++------ packages/svelte/src/utils.js | 8 ++++++++ .../svelte/tests/hydration/samples/script/_config.js | 11 +++++++++++ .../tests/hydration/samples/script/_expected.html | 1 + .../svelte/tests/hydration/samples/script/main.svelte | 1 + 7 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 .changeset/rotten-yaks-nail.md create mode 100644 packages/svelte/tests/hydration/samples/script/_config.js create mode 100644 packages/svelte/tests/hydration/samples/script/_expected.html create mode 100644 packages/svelte/tests/hydration/samples/script/main.svelte diff --git a/.changeset/rotten-yaks-nail.md b/.changeset/rotten-yaks-nail.md new file mode 100644 index 0000000000..bbe9b777ae --- /dev/null +++ b/.changeset/rotten-yaks-nail.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: better handle hydration of script/style elements diff --git a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js index 823b9a4362..35d2f223ae 100644 --- a/packages/svelte/src/internal/client/dom/blocks/svelte-element.js +++ b/packages/svelte/src/internal/client/dom/blocks/svelte-element.js @@ -21,6 +21,7 @@ import { component_context, active_effect } from '../../runtime.js'; import { DEV } from 'esm-env'; import { EFFECT_TRANSPARENT } from '../../constants.js'; import { assign_nodes } from '../template.js'; +import { is_raw_text_element } from '../../../../utils.js'; /** * @param {Comment | Element} node @@ -116,6 +117,11 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio assign_nodes(element, element); if (render_fn) { + if (hydrating && is_raw_text_element(next_tag)) { + // prevent hydration glitches + element.append(document.createComment('')); + } + // If hydrating, use the existing ssr comment as the anchor so that the // inner open and close methods can pick up the existing nodes correctly var child_anchor = /** @type {TemplateNode} */ ( diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index b944c602b8..b8371b7e00 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -16,7 +16,7 @@ import { DEV } from 'esm-env'; import { current_component, pop, push } from './context.js'; import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { validate_store } from '../shared/validate.js'; -import { is_boolean_attribute, is_void } from '../../utils.js'; +import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; import { reset_elements } from './dev.js'; // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 @@ -24,9 +24,6 @@ import { reset_elements } from './dev.js'; const INVALID_ATTR_NAME_CHAR_REGEX = /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; -/** List of elements that require raw contents and should not have SSR comments put in them */ -const RAW_TEXT_ELEMENTS = ['textarea', 'script', 'style', 'title']; - /** * @param {Payload} to_copy * @returns {Payload} @@ -64,13 +61,13 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop) payload.out += ''; if (tag) { - payload.out += `<${tag} `; + payload.out += `<${tag}`; attributes_fn(); payload.out += `>`; if (!is_void(tag)) { children_fn(); - if (!RAW_TEXT_ELEMENTS.includes(tag)) { + if (!is_raw_text_element(tag)) { payload.out += EMPTY_COMMENT; } payload.out += ``; diff --git a/packages/svelte/src/utils.js b/packages/svelte/src/utils.js index 75171c1786..9324408007 100644 --- a/packages/svelte/src/utils.js +++ b/packages/svelte/src/utils.js @@ -441,3 +441,11 @@ const RUNES = /** @type {const} */ ([ export function is_rune(name) { return RUNES.includes(/** @type {RUNES[number]} */ (name)); } + +/** List of elements that require raw contents and should not have SSR comments put in them */ +const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style', 'title']); + +/** @param {string} name */ +export function is_raw_text_element(name) { + return RAW_TEXT_ELEMENTS.includes(/** @type {RAW_TEXT_ELEMENTS[number]} */ (name)); +} diff --git a/packages/svelte/tests/hydration/samples/script/_config.js b/packages/svelte/tests/hydration/samples/script/_config.js new file mode 100644 index 0000000000..4723e4e454 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/script/_config.js @@ -0,0 +1,11 @@ +import { test } from '../../test'; + +export default test({ + snapshot(target) { + const script = target.querySelector('script'); + + return { + script + }; + } +}); diff --git a/packages/svelte/tests/hydration/samples/script/_expected.html b/packages/svelte/tests/hydration/samples/script/_expected.html new file mode 100644 index 0000000000..b3a4d92219 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/script/_expected.html @@ -0,0 +1 @@ + diff --git a/packages/svelte/tests/hydration/samples/script/main.svelte b/packages/svelte/tests/hydration/samples/script/main.svelte new file mode 100644 index 0000000000..3904d47f73 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/script/main.svelte @@ -0,0 +1 @@ +{"{}"} From 432db95358b3a8ad5a81e7958109b024ff2f4a8e Mon Sep 17 00:00:00 2001 From: James Glenn <47917431+JR-G@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:19:13 +0000 Subject: [PATCH 037/389] docs: Update the linked playgrounds in the snippet docs (#14676) * Update the linked playgrounds in the snippet docs * Apply suggestions from code review --------- Co-authored-by: Rich Harris --- documentation/docs/03-template-syntax/06-snippet.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/03-template-syntax/06-snippet.md b/documentation/docs/03-template-syntax/06-snippet.md index f8148f3dc3..c9951d3f34 100644 --- a/documentation/docs/03-template-syntax/06-snippet.md +++ b/documentation/docs/03-template-syntax/06-snippet.md @@ -112,7 +112,7 @@ Snippets can reference themselves and each other ([demo](/playground/untitled#H4 ## Passing snippets to components -Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE41SwY6bMBD9lRGplKQlYRMpF5ZF7T_0ttmDwSZYJbZrT9pGlv-9g4Fkk-xhxYV5vHlvhjc-aWQnXJK_-kSxo0jy5IcxSZrg2fSF-yM6FFQ7fbJ1jxSuttJguVd7lEejLcJPVnUCGquPMF9nsVoPjfNnohGx1sohMU4SHbzAa4_t0UNvmcOcGUNDzFP4jeccdikYK2v6sIWQ3lErpui5cDdPF_LmkVy3wlp5Vd5e2U_rHYSe_kYjFtl1KeVnTkljBEIrGBd2sYy8AtsyLlBk9DYhJHtTR_UbBDWybkR8NkqHWyOr_y74ZMNLz9f9AoG6ePkOJLMHLBp-xISvcPf11r0YUuMM2Ysfkgngh5XphUYKkJWU_FFz2UjBkxztSYT0cihR4LOn0tGaPrql439N-7Uh0Dl8MVYbt1jeJ1Fg7xDb_Uw2Y18YQqZ_S2U5FH1pS__dCkWMa3C0uR0pfQRTg89kE4bLLLDS_Dxy_Eywuo1TAnPAw4fqY1rvtH3W9w35ZZMgvU3jq8LhedwkguCHRhT_cMU6eVA5dKLB5wGutCWjlTOslupAxxrxceKoD2hzhe2qbmXHF1v1bbOcNCtW_zpYfVI8h5kQ4qY3mueHTlesW2C7TOEO4hcdwzgf3Nc7cZxUKKC4yuNhvIX_MlV_Xk0EAAA=)): +Within the template, snippets are values just like any other. As such, they can be passed to components as props ([demo](/playground/untitled#H4sIAAAAAAAAE3VS247aMBD9lZGpBGwDASRegonaPvQL2qdlH5zYEKvBNvbQLbL875VzAcKyj3PmzJnLGU8UOwqSkd8KJdaCk4TsZS0cyV49wYuJuQiQpGd-N2bu_ooaI1YwJ57hpVYoFDqSEepKKw3mO7VDeTTaIvxiRS1gb_URxvO0ibrS8WanIrHUyiHs7Vmigy28RmyHHmKvDMbMmFq4cQInvGSwTsBYWYoMVhCSB2rBFFPsyl0uruTlR3JZCWvlTXl1Yy_mawiR_rbZKZrellJ-5JQ0RiBUgnFhJ9OGR7HKmwVoilXeIye8DOJGfYCgRlZ3iE876TBsZPX7hPdteO75PC4QaIo8vwNPePmANQ2fMeEFHrLD7rR1jTNkW986E8C3KwfwVr8HSHOSEBT_kGRozyIkn_zQveXDL3rIfPJHtUDwzShJd_Qk3gQCbOGLsdq4yfTRJopRuin3I7nv6kL7ARRjmLdBDG3uv1mhuLA3V2mKtqNEf_oCn8p9aN-WYqH5peP4kWBl1UwJzAEPT9U7K--0fRrrWnPTXpCm1_EVdXjpNmlA8G1hPPyM1fKgMqjFHjctXGjLhZ05w0qpDhksGrybuNEHtJnCalZWsuaTlfq6nPaaBSv_HKw-K57BjzOiVj9ZKQYKzQjZodYFqydYTRN4gPhVzTDO2xnma3HsVWjaLjT8nbfwHy7Q5f2dBAAA)): ```svelte + +
+

Input/Textarea value

+ +
+ + + + + + + + +
+ + +
+ + + + + + + + +
+ + +
+ + + + + + + + +
+ +

Input checked

+ +
+ + + + +
+ + +
+ + + + +
+ + +
+ + + + +
+ + +
+ + +
+ + + +

Select (single)

+ + + + + + + + + + + + +

Select (multiple)

+ + + + + + + + +

Static values

+
+ + + +
+ + +
+ +

+ Bound values: + {value1} {value3} {value6} {value8} + {value9} {value12} {value14} {value16} + {value17} {value20} {value22} {value24} + {checked2} {checked4} + {checked6} {checked8} + {checked10} {checked12} + {checked14} + {selected1} + {selected2} + {selected3} + {selected4} + {selected5} + {selected6} +

diff --git a/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js b/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js index 5ef72aaa8e..35ab6e8ece 100644 --- a/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/form-default-value/_config.js @@ -68,7 +68,6 @@ export default test({ assert.htmlEqual(test1_span.innerHTML, 'foo foo foo foo'); after_reset.push(() => { - console.log('-------------'); check_inputs(inputs, 'value', 'x'); assert.htmlEqual(test1_span.innerHTML, 'x x x x'); }); @@ -88,7 +87,6 @@ export default test({ assert.htmlEqual(test2_span.innerHTML, 'foo foo foo foo'); after_reset.push(() => { - console.log('-------------'); check_inputs(inputs, 'value', 'x'); assert.htmlEqual(test2_span.innerHTML, 'x x x x'); }); From 65db40986035e0d20b8e1da8e4006e16d2c12971 Mon Sep 17 00:00:00 2001 From: waedi Date: Thu, 12 Dec 2024 20:22:06 +0100 Subject: [PATCH 039/389] docs: typo in ## script_context_deprecated (#14694) * Fix typo in ## script_context_deprecated Changed +++context+++ to +++module+++ * regenerate --------- Co-authored-by: Rich Harris --- documentation/docs/98-reference/.generated/compile-warnings.md | 2 +- packages/svelte/messages/compile-warnings/template.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 88b1f278a6..f6f2585df2 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -775,7 +775,7 @@ Reassignments of module-level declarations will not cause reactive statements to ``` ```svelte - ``` diff --git a/packages/svelte/messages/compile-warnings/template.md b/packages/svelte/messages/compile-warnings/template.md index b15b01241b..33e635bdb2 100644 --- a/packages/svelte/messages/compile-warnings/template.md +++ b/packages/svelte/messages/compile-warnings/template.md @@ -57,7 +57,7 @@ This code will work when the component is rendered on the client (which is why t > `context="module"` is deprecated, use the `module` attribute instead ```svelte - ``` From 88c2d6ea36f1b9bd6d1f52788c4e5a258c30868b Mon Sep 17 00:00:00 2001 From: Yang Pan <77009679+panyang05@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:43:01 -0500 Subject: [PATCH 040/389] fix: Allow unquoted slash in attributes (#14615) * test: add sample for unquoted attributes * fix: handle unquoted slash in attributes * docs: allow unquoted slash in attributes * test: add additional sample for unquoted href attributes * fix: improve handling of self-closing tags with unquoted attributes * Update .changeset/long-boxes-flow.md --------- Co-authored-by: Rich Harris --- .changeset/long-boxes-flow.md | 5 ++ .../compiler/phases/1-parse/state/element.js | 20 ++++- .../samples/attribute-unquoted/input.svelte | 4 +- .../samples/attribute-unquoted/output.json | 80 ++++++++++++++++++- 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 .changeset/long-boxes-flow.md diff --git a/.changeset/long-boxes-flow.md b/.changeset/long-boxes-flow.md new file mode 100644 index 0000000000..d249354b67 --- /dev/null +++ b/.changeset/long-boxes-flow.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: allow unquoted slash in attributes diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index 45350bb1ae..cd5cdd3e6e 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -504,8 +504,24 @@ function read_attribute(parser) { let value = true; if (parser.eat('=')) { parser.allow_whitespace(); - value = read_attribute_value(parser); - end = parser.index; + + if (parser.template[parser.index] === '/' && parser.template[parser.index + 1] === '>') { + const char_start = parser.index; + parser.index++; // consume '/' + value = [ + { + start: char_start, + end: char_start + 1, + type: 'Text', + raw: '/', + data: '/' + } + ]; + end = parser.index; + } else { + value = read_attribute_value(parser); + end = parser.index; + } } else if (parser.match_regex(regex_starts_with_quote_characters)) { e.expected_token(parser.index, '='); } diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/input.svelte b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/input.svelte index 4bab0df72f..527d6eebf1 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/input.svelte +++ b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/input.svelte @@ -1 +1,3 @@ -
\ No newline at end of file +
+home +home \ No newline at end of file diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json index 5df4d66ab6..ab2912a2c0 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json @@ -2,7 +2,7 @@ "html": { "type": "Fragment", "start": 0, - "end": 21, + "end": 62, "children": [ { "type": "Element", @@ -27,6 +27,84 @@ } ], "children": [] + }, + { + "type": "Text", + "start": 21, + "end": 22, + "raw": "\n", + "data": "\n" + }, + { + "type": "Element", + "start": 22, + "end": 40, + "name": "a", + "attributes": [ + { + "type": "Attribute", + "start": 25, + "end": 31, + "name": "href", + "value": [ + { + "start": 30, + "end": 31, + "type": "Text", + "raw": "/", + "data": "/" + } + ] + } + ], + "children": [ + { + "type": "Text", + "start": 32, + "end": 36, + "raw": "home", + "data": "home" + } + ] + }, + { + "type": "Text", + "start": 40, + "end": 41, + "raw": "\n", + "data": "\n" + }, + { + "type": "Element", + "start": 41, + "end": 62, + "name": "a", + "attributes": [ + { + "type": "Attribute", + "start": 44, + "end": 53, + "name": "href", + "value": [ + { + "start": 49, + "end": 53, + "type": "Text", + "raw": "/foo", + "data": "/foo" + } + ] + } + ], + "children": [ + { + "type": "Text", + "start": 54, + "end": 58, + "raw": "home", + "data": "home" + } + ] } ] } From 780041a51e1425167c30875dd54d906028128eff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:55:51 -0500 Subject: [PATCH 041/389] Version Packages (#14689) Co-authored-by: github-actions[bot] --- .changeset/long-boxes-flow.md | 5 ----- .changeset/rotten-yaks-nail.md | 5 ----- .changeset/silent-tips-cover.md | 5 ----- .changeset/strong-pandas-provide.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/long-boxes-flow.md delete mode 100644 .changeset/rotten-yaks-nail.md delete mode 100644 .changeset/silent-tips-cover.md delete mode 100644 .changeset/strong-pandas-provide.md diff --git a/.changeset/long-boxes-flow.md b/.changeset/long-boxes-flow.md deleted file mode 100644 index d249354b67..0000000000 --- a/.changeset/long-boxes-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: allow unquoted slash in attributes diff --git a/.changeset/rotten-yaks-nail.md b/.changeset/rotten-yaks-nail.md deleted file mode 100644 index bbe9b777ae..0000000000 --- a/.changeset/rotten-yaks-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: better handle hydration of script/style elements diff --git a/.changeset/silent-tips-cover.md b/.changeset/silent-tips-cover.md deleted file mode 100644 index 1f51572cda..0000000000 --- a/.changeset/silent-tips-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: make `defaultValue` work with spread diff --git a/.changeset/strong-pandas-provide.md b/.changeset/strong-pandas-provide.md deleted file mode 100644 index 0fe7e70c6d..0000000000 --- a/.changeset/strong-pandas-provide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'svelte': patch ---- - -fix: avoid mutation validation for invalidate_inner_signals diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 978e841bf8..6f3380ff5a 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.11.3 + +### Patch Changes + +- fix: allow unquoted slash in attributes ([#14615](https://github.com/sveltejs/svelte/pull/14615)) + +- fix: better handle hydration of script/style elements ([#14683](https://github.com/sveltejs/svelte/pull/14683)) + +- fix: make `defaultValue` work with spread ([#14640](https://github.com/sveltejs/svelte/pull/14640)) + +- fix: avoid mutation validation for invalidate_inner_signals ([#14688](https://github.com/sveltejs/svelte/pull/14688)) + ## 5.11.2 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index e95341a0bd..abcda8613d 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.11.2", + "version": "5.11.3", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index e264eace2c..39d89acd53 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.11.2'; +export const VERSION = '5.11.3'; export const PUBLIC_VERSION = '5'; From 2e0dcd78722d457f4c9e4b6db4ef4cb3ab26c037 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 12 Dec 2024 20:09:29 +0000 Subject: [PATCH 042/389] fix: ensure if block paths retain correct template namespacing (#14685) * fix: ensure if block paths retain correct template namespacing * add tests * address feedback * address feedback * simplify --------- Co-authored-by: Rich Harris --- .changeset/giant-moons-accept.md | 5 +++++ .../src/compiler/phases/3-transform/utils.js | 19 ++++++++++++++++++- .../svg-namespace-if-block/Child.svelte | 8 ++++++++ .../samples/svg-namespace-if-block/_config.js | 14 ++++++++++++++ .../svg-namespace-if-block/main.svelte | 7 +++++++ 5 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 .changeset/giant-moons-accept.md create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte diff --git a/.changeset/giant-moons-accept.md b/.changeset/giant-moons-accept.md new file mode 100644 index 0000000000..7940371d6f --- /dev/null +++ b/.changeset/giant-moons-accept.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure if block paths retain correct template namespacing diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 14fd3aa2e8..ffd07dd26a 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -347,7 +347,24 @@ export function infer_namespace(namespace, parent, nodes) { } } - return namespace; + /** @type {Namespace | null} */ + let new_namespace = null; + + // Check the elements within the fragment and look for consistent namespaces. + // If we have no namespaces or they are mixed, then fallback to existing namespace + for (const node of nodes) { + if (node.type !== 'RegularElement') continue; + + if (node.metadata.mathml) { + new_namespace = new_namespace === null || new_namespace === 'mathml' ? 'mathml' : 'html'; + } else if (node.metadata.svg) { + new_namespace = new_namespace === null || new_namespace === 'svg' ? 'svg' : 'html'; + } else { + return 'html'; + } + } + + return new_namespace ?? namespace; } /** diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte new file mode 100644 index 0000000000..53e6203dde --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/Child.svelte @@ -0,0 +1,8 @@ + +{#if true} + + + +{:else} +
lol
+{/if} diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js new file mode 100644 index 0000000000..22a2469bfb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/_config.js @@ -0,0 +1,14 @@ +import { test, ok } from '../../test'; + +export default test({ + html: ``, + test({ assert, target }) { + const g = target.querySelector('g'); + const rect = target.querySelector('rect'); + ok(g); + ok(rect); + + assert.equal(g.namespaceURI, 'http://www.w3.org/2000/svg'); + assert.equal(rect.namespaceURI, 'http://www.w3.org/2000/svg'); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte new file mode 100644 index 0000000000..8f6154462f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-if-block/main.svelte @@ -0,0 +1,7 @@ + + + + + From 61a0da8a5fdf5ac86431ceadfae0f54d38dc9a66 Mon Sep 17 00:00:00 2001 From: Mateusz Kadlubowski Date: Fri, 13 Dec 2024 04:15:40 +0800 Subject: [PATCH 043/389] feat: Expose more AST types from `"svelte/compiler"` (#14601) * add missing `SvelteBoundary` in `ElementLike` * make union of AST types public and exportable with `AST` namespace * apply AST types change to codebase * changeset * manually generate types * Add `AttributeLike` type * export namespace `Css` inside `AST` * manually generate types again * exported `Css` -> `CSS` * `Css` -> `AST.CSS` * fix Prettier issue * Apply suggestions from code review --------- Co-authored-by: Rich Harris --- .changeset/short-papayas-relate.md | 5 + packages/svelte/src/compiler/legacy.js | 6 +- packages/svelte/src/compiler/migrate/index.js | 10 +- .../src/compiler/phases/1-parse/index.js | 4 +- .../compiler/phases/1-parse/read/script.js | 4 +- .../src/compiler/phases/1-parse/read/style.js | 40 +++---- .../compiler/phases/1-parse/state/element.js | 18 +-- .../phases/2-analyze/css/css-analyze.js | 18 +-- .../phases/2-analyze/css/css-prune.js | 58 +++++----- .../compiler/phases/2-analyze/css/css-warn.js | 8 +- .../compiler/phases/2-analyze/css/utils.js | 14 +-- .../src/compiler/phases/2-analyze/index.js | 8 +- .../src/compiler/phases/2-analyze/types.d.ts | 8 +- .../visitors/AssignmentExpression.js | 1 - .../phases/2-analyze/visitors/Attribute.js | 4 +- .../2-analyze/visitors/CallExpression.js | 4 +- .../2-analyze/visitors/LabeledStatement.js | 4 +- .../phases/2-analyze/visitors/SnippetBlock.js | 4 +- .../phases/2-analyze/visitors/shared/a11y.js | 6 +- .../2-analyze/visitors/shared/attribute.js | 4 +- .../2-analyze/visitors/shared/fragment.js | 4 +- .../3-transform/client/transform-client.js | 10 +- .../phases/3-transform/client/types.d.ts | 10 +- .../phases/3-transform/client/utils.js | 4 +- .../client/visitors/BindDirective.js | 6 +- .../client/visitors/shared/component.js | 4 +- .../client/visitors/shared/events.js | 4 +- .../client/visitors/shared/fragment.js | 6 +- .../client/visitors/shared/utils.js | 6 +- .../compiler/phases/3-transform/css/index.js | 24 ++-- .../3-transform/server/transform-server.js | 10 +- .../phases/3-transform/server/types.d.ts | 10 +- .../server/visitors/AssignmentExpression.js | 4 +- .../server/visitors/shared/component.js | 4 +- .../server/visitors/shared/element.js | 4 +- .../server/visitors/shared/utils.js | 4 +- .../compiler/phases/3-transform/types.d.ts | 4 +- .../src/compiler/phases/3-transform/utils.js | 20 ++-- packages/svelte/src/compiler/phases/css.js | 4 +- packages/svelte/src/compiler/phases/nodes.js | 4 +- packages/svelte/src/compiler/phases/scope.js | 26 ++--- .../svelte/src/compiler/phases/types.d.ts | 8 +- packages/svelte/src/compiler/state.js | 8 +- packages/svelte/src/compiler/types/css.d.ts | 2 +- packages/svelte/src/compiler/types/index.d.ts | 6 +- .../src/compiler/types/legacy-nodes.d.ts | 4 +- .../svelte/src/compiler/types/template.d.ts | 101 ++++++++-------- packages/svelte/src/compiler/utils/ast.js | 4 +- packages/svelte/src/compiler/utils/slot.js | 4 +- packages/svelte/types/index.d.ts | 109 +++++++++++------- 50 files changed, 343 insertions(+), 303 deletions(-) create mode 100644 .changeset/short-papayas-relate.md diff --git a/.changeset/short-papayas-relate.md b/.changeset/short-papayas-relate.md new file mode 100644 index 0000000000..430c507a0c --- /dev/null +++ b/.changeset/short-papayas-relate.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +feat: expose more AST types from `"svelte/compiler"` diff --git a/packages/svelte/src/compiler/legacy.js b/packages/svelte/src/compiler/legacy.js index 2d90988936..e3f88c8f1d 100644 --- a/packages/svelte/src/compiler/legacy.js +++ b/packages/svelte/src/compiler/legacy.js @@ -1,5 +1,5 @@ /** @import { Expression } from 'estree' */ -/** @import { AST, SvelteNode, TemplateNode } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import * as Legacy from './types/legacy-nodes.js' */ import { walk } from 'zimmerframe'; import { @@ -11,7 +11,7 @@ import { extract_svelte_ignore } from './utils/extract_svelte_ignore.js'; /** * Some of the legacy Svelte AST nodes remove whitespace from the start and end of their children. - * @param {TemplateNode[]} nodes + * @param {AST.TemplateNode[]} nodes */ function remove_surrounding_whitespace_nodes(nodes) { const first = nodes.at(0); @@ -40,7 +40,7 @@ function remove_surrounding_whitespace_nodes(nodes) { * @returns {Legacy.LegacyRoot} */ export function convert(source, ast) { - const root = /** @type {SvelteNode | Legacy.LegacySvelteNode} */ (ast); + const root = /** @type {AST.SvelteNode | Legacy.LegacySvelteNode} */ (ast); return /** @type {Legacy.LegacyRoot} */ ( walk(root, null, { diff --git a/packages/svelte/src/compiler/migrate/index.js b/packages/svelte/src/compiler/migrate/index.js index 88f9bbf0ee..1bb7a69a20 100644 --- a/packages/svelte/src/compiler/migrate/index.js +++ b/packages/svelte/src/compiler/migrate/index.js @@ -2,7 +2,7 @@ /** @import { Visitors } from 'zimmerframe' */ /** @import { ComponentAnalysis } from '../phases/types.js' */ /** @import { Scope, ScopeRoot } from '../phases/scope.js' */ -/** @import { AST, Binding, SvelteNode, ValidatedCompileOptions } from '#compiler' */ +/** @import { AST, Binding, ValidatedCompileOptions } from '#compiler' */ import MagicString from 'magic-string'; import { walk } from 'zimmerframe'; import { parse } from '../phases/1-parse/index.js'; @@ -479,7 +479,7 @@ export function migrate(source, { filename, use_ts } = {}) { * }} State */ -/** @type {Visitors} */ +/** @type {Visitors} */ const instance_script = { _(node, { state, next }) { // @ts-expect-error @@ -1050,7 +1050,7 @@ function trim_block(state, start, end) { } } -/** @type {Visitors} */ +/** @type {Visitors} */ const template = { Identifier(node, { state, path }) { handle_identifier(node, state, path); @@ -1410,7 +1410,7 @@ const template = { /** * @param {AST.RegularElement | AST.SvelteElement | AST.SvelteComponent | AST.Component | AST.SlotElement | AST.SvelteFragment} node - * @param {SvelteNode[]} path + * @param {AST.SvelteNode[]} path * @param {State} state */ function migrate_slot_usage(node, path, state) { @@ -1580,7 +1580,7 @@ function migrate_slot_usage(node, path, state) { /** * @param {VariableDeclarator} declarator * @param {State} state - * @param {SvelteNode[]} path + * @param {AST.SvelteNode[]} path */ function extract_type_and_comment(declarator, state, path) { const str = state.str; diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 7639c2f0ed..c3a8a098d3 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -1,4 +1,4 @@ -/** @import { AST, TemplateNode } from '#compiler' */ +/** @import { AST } from '#compiler' */ // @ts-expect-error acorn type definitions are borked in the release we use import { isIdentifierStart, isIdentifierChar } from 'acorn'; import fragment from './state/fragment.js'; @@ -28,7 +28,7 @@ export class Parser { /** Whether we're parsing in TypeScript mode */ ts = false; - /** @type {TemplateNode[]} */ + /** @type {AST.TemplateNode[]} */ stack = []; /** @type {AST.Fragment[]} */ diff --git a/packages/svelte/src/compiler/phases/1-parse/read/script.js b/packages/svelte/src/compiler/phases/1-parse/read/script.js index 87367aff08..9d9ed3a1ef 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/script.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/script.js @@ -1,5 +1,5 @@ /** @import { Program } from 'estree' */ -/** @import { AST, Directive } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import { Parser } from '../index.js' */ import * as acorn from '../acorn.js'; import { regex_not_newline_characters } from '../../patterns.js'; @@ -16,7 +16,7 @@ const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module']; /** * @param {Parser} parser * @param {number} start - * @param {Array} attributes + * @param {Array} attributes * @returns {AST.Script} */ export function read_script(parser, start, attributes) { diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index aa835a1d96..29e8a0e541 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -1,4 +1,4 @@ -/** @import { AST, Css, Directive } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import { Parser } from '../index.js' */ import * as e from '../../../errors.js'; @@ -18,8 +18,8 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/; /** * @param {Parser} parser * @param {number} start - * @param {Array} attributes - * @returns {Css.StyleSheet} + * @param {Array} attributes + * @returns {AST.CSS.StyleSheet} */ export default function read_style(parser, start, attributes) { const content_start = parser.index; @@ -49,7 +49,7 @@ export default function read_style(parser, start, attributes) { * @returns {any[]} */ function read_body(parser, close) { - /** @type {Array} */ + /** @type {Array} */ const children = []; while (parser.index < parser.template.length) { @@ -71,7 +71,7 @@ function read_body(parser, close) { /** * @param {Parser} parser - * @returns {Css.Atrule} + * @returns {AST.CSS.Atrule} */ function read_at_rule(parser) { const start = parser.index; @@ -81,7 +81,7 @@ function read_at_rule(parser) { const prelude = read_value(parser); - /** @type {Css.Block | null} */ + /** @type {AST.CSS.Block | null} */ let block = null; if (parser.match('{')) { @@ -104,7 +104,7 @@ function read_at_rule(parser) { /** * @param {Parser} parser - * @returns {Css.Rule} + * @returns {AST.CSS.Rule} */ function read_rule(parser) { const start = parser.index; @@ -126,10 +126,10 @@ function read_rule(parser) { /** * @param {Parser} parser * @param {boolean} [inside_pseudo_class] - * @returns {Css.SelectorList} + * @returns {AST.CSS.SelectorList} */ function read_selector_list(parser, inside_pseudo_class = false) { - /** @type {Css.ComplexSelector[]} */ + /** @type {AST.CSS.ComplexSelector[]} */ const children = []; allow_comment_or_whitespace(parser); @@ -162,18 +162,18 @@ function read_selector_list(parser, inside_pseudo_class = false) { /** * @param {Parser} parser * @param {boolean} [inside_pseudo_class] - * @returns {Css.ComplexSelector} + * @returns {AST.CSS.ComplexSelector} */ function read_selector(parser, inside_pseudo_class = false) { const list_start = parser.index; - /** @type {Css.RelativeSelector[]} */ + /** @type {AST.CSS.RelativeSelector[]} */ const children = []; /** - * @param {Css.Combinator | null} combinator + * @param {AST.CSS.Combinator | null} combinator * @param {number} start - * @returns {Css.RelativeSelector} + * @returns {AST.CSS.RelativeSelector} */ function create_selector(combinator, start) { return { @@ -190,7 +190,7 @@ function read_selector(parser, inside_pseudo_class = false) { }; } - /** @type {Css.RelativeSelector} */ + /** @type {AST.CSS.RelativeSelector} */ let relative_selector = create_selector(null, parser.index); while (parser.index < parser.template.length) { @@ -247,7 +247,7 @@ function read_selector(parser, inside_pseudo_class = false) { } else if (parser.eat(':')) { const name = read_identifier(parser); - /** @type {null | Css.SelectorList} */ + /** @type {null | AST.CSS.SelectorList} */ let args = null; if (parser.eat('(')) { @@ -372,7 +372,7 @@ function read_selector(parser, inside_pseudo_class = false) { /** * @param {Parser} parser - * @returns {Css.Combinator | null} + * @returns {AST.CSS.Combinator | null} */ function read_combinator(parser) { const start = parser.index; @@ -407,14 +407,14 @@ function read_combinator(parser) { /** * @param {Parser} parser - * @returns {Css.Block} + * @returns {AST.CSS.Block} */ function read_block(parser) { const start = parser.index; parser.eat('{', true); - /** @type {Array} */ + /** @type {Array} */ const children = []; while (parser.index < parser.template.length) { @@ -441,7 +441,7 @@ function read_block(parser) { * Reads a declaration, rule or at-rule * * @param {Parser} parser - * @returns {Css.Declaration | Css.Rule | Css.Atrule} + * @returns {AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule} */ function read_block_item(parser) { if (parser.match('@')) { @@ -460,7 +460,7 @@ function read_block_item(parser) { /** * @param {Parser} parser - * @returns {Css.Declaration} + * @returns {AST.CSS.Declaration} */ function read_declaration(parser) { const start = parser.index; diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index cd5cdd3e6e..2b6a88f7bd 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -1,5 +1,5 @@ /** @import { Expression } from 'estree' */ -/** @import { AST, Directive, ElementLike, TemplateNode } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import { Parser } from '../index.js' */ import { is_void } from '../../../../utils.js'; import read_expression from '../read/expression.js'; @@ -28,7 +28,7 @@ export const regex_valid_component_name = // (must start with uppercase letter if no dots, can contain dots) /^(?:\p{Lu}[$\u200c\u200d\p{ID_Continue}.]*|\p{ID_Start}[$\u200c\u200d\p{ID_Continue}]*(?:\.[$\u200c\u200d\p{ID_Continue}]+)+)$/u; -/** @type {Map} */ +/** @type {Map} */ const root_only_meta_tags = new Map([ ['svelte:head', 'SvelteHead'], ['svelte:options', 'SvelteOptions'], @@ -37,7 +37,7 @@ const root_only_meta_tags = new Map([ ['svelte:body', 'SvelteBody'] ]); -/** @type {Map} */ +/** @type {Map} */ const meta_tags = new Map([ ...root_only_meta_tags, ['svelte:element', 'SvelteElement'], @@ -137,7 +137,7 @@ export default function element(parser) { ? 'SlotElement' : 'RegularElement'; - /** @type {ElementLike} */ + /** @type {AST.ElementLike} */ const element = type === 'RegularElement' ? { @@ -155,7 +155,7 @@ export default function element(parser) { path: [] } } - : /** @type {ElementLike} */ ({ + : /** @type {AST.ElementLike} */ ({ type, start, end: -1, @@ -358,7 +358,7 @@ export default function element(parser) { } } -/** @param {TemplateNode[]} stack */ +/** @param {AST.TemplateNode[]} stack */ function parent_is_head(stack) { let i = stack.length; while (i--) { @@ -369,7 +369,7 @@ function parent_is_head(stack) { return false; } -/** @param {TemplateNode[]} stack */ +/** @param {AST.TemplateNode[]} stack */ function parent_is_shadowroot_template(stack) { // https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root let i = stack.length; @@ -433,7 +433,7 @@ function read_static_attribute(parser) { /** * @param {Parser} parser - * @returns {AST.Attribute | AST.SpreadAttribute | Directive | null} + * @returns {AST.Attribute | AST.SpreadAttribute | AST.Directive | null} */ function read_attribute(parser) { const start = parser.index; @@ -564,7 +564,7 @@ function read_attribute(parser) { } } - /** @type {Directive} */ + /** @type {AST.Directive} */ const directive = { start, end, diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js index 1dd2d9ae7c..b8c88a1023 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-analyze.js @@ -1,5 +1,5 @@ /** @import { ComponentAnalysis } from '../../types.js' */ -/** @import { Css } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import { Visitors } from 'zimmerframe' */ import { walk } from 'zimmerframe'; import * as e from '../../../errors.js'; @@ -8,17 +8,17 @@ import { is_global, is_unscoped_pseudo_class } from './utils.js'; /** * @typedef {Visitors< - * Css.Node, + * AST.CSS.Node, * { * keyframes: string[]; - * rule: Css.Rule | null; + * rule: AST.CSS.Rule | null; * } * >} CssVisitors */ /** * True if is `:global` - * @param {Css.SimpleSelector} simple_selector + * @param {AST.CSS.SimpleSelector} simple_selector */ function is_global_block_selector(simple_selector) { return ( @@ -112,7 +112,7 @@ const css_visitors = { } }, RelativeSelector(node, context) { - const parent = /** @type {Css.ComplexSelector} */ (context.path.at(-1)); + const parent = /** @type {AST.CSS.ComplexSelector} */ (context.path.at(-1)); if ( node.combinator != null && @@ -149,7 +149,7 @@ const css_visitors = { if (node.metadata.is_global_like || node.metadata.is_global) { // So that nested selectors like `:root:not(.x)` are not marked as unused for (const child of node.selectors) { - walk(/** @type {Css.Node} */ (child), null, { + walk(/** @type {AST.CSS.Node} */ (child), null, { ComplexSelector(node, context) { node.metadata.used = true; context.next(); @@ -177,7 +177,7 @@ const css_visitors = { if (idx !== -1) { is_global_block = true; for (let i = idx + 1; i < child.selectors.length; i++) { - walk(/** @type {Css.Node} */ (child.selectors[i]), null, { + walk(/** @type {AST.CSS.Node} */ (child.selectors[i]), null, { ComplexSelector(node) { node.metadata.used = true; } @@ -240,7 +240,7 @@ const css_visitors = { }); }, NestingSelector(node, context) { - const rule = /** @type {Css.Rule} */ (context.state.rule); + const rule = /** @type {AST.CSS.Rule} */ (context.state.rule); const parent_rule = rule.metadata.parent_rule; if (!parent_rule) { @@ -271,7 +271,7 @@ const css_visitors = { }; /** - * @param {Css.StyleSheet} stylesheet + * @param {AST.CSS.StyleSheet} stylesheet * @param {ComponentAnalysis} analysis */ export function analyze_css(stylesheet, analysis) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index d017b215f2..35bc675166 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -14,7 +14,7 @@ const whitelist_attribute_selector = new Map([ ['dialog', ['open']] ]); -/** @type {Compiler.Css.Combinator} */ +/** @type {Compiler.AST.CSS.Combinator} */ const descendant_combinator = { type: 'Combinator', name: ' ', @@ -22,7 +22,7 @@ const descendant_combinator = { end: -1 }; -/** @type {Compiler.Css.RelativeSelector} */ +/** @type {Compiler.AST.CSS.RelativeSelector} */ const nesting_selector = { type: 'RelativeSelector', start: -1, @@ -51,11 +51,11 @@ const seen = new Set(); /** * - * @param {Compiler.Css.StyleSheet} stylesheet + * @param {Compiler.AST.CSS.StyleSheet} stylesheet * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element */ export function prune(stylesheet, element) { - walk(/** @type {Compiler.Css.Node} */ (stylesheet), null, { + walk(/** @type {Compiler.AST.CSS.Node} */ (stylesheet), null, { Rule(node, context) { if (node.metadata.is_global_block) { context.visit(node.prelude); @@ -69,7 +69,11 @@ export function prune(stylesheet, element) { seen.clear(); if ( - apply_selector(selectors, /** @type {Compiler.Css.Rule} */ (node.metadata.rule), element) + apply_selector( + selectors, + /** @type {Compiler.AST.CSS.Rule} */ (node.metadata.rule), + element + ) ) { node.metadata.used = true; } @@ -86,7 +90,7 @@ export function prune(stylesheet, element) { * Also searches them for any existing `&` selectors and adds one if none are found. * This ensures we traverse up to the parent rule when the inner selectors match and we're * trying to see if the parent rule also matches. - * @param {Compiler.Css.ComplexSelector} node + * @param {Compiler.AST.CSS.ComplexSelector} node */ function get_relative_selectors(node) { const selectors = truncate(node); @@ -124,7 +128,7 @@ function get_relative_selectors(node) { /** * Discard trailing `:global(...)` selectors, these are unused for scoping purposes - * @param {Compiler.Css.ComplexSelector} node + * @param {Compiler.AST.CSS.ComplexSelector} node */ function truncate(node) { const i = node.children.findLastIndex(({ metadata, selectors }) => { @@ -152,8 +156,8 @@ function truncate(node) { } /** - * @param {Compiler.Css.RelativeSelector[]} relative_selectors - * @param {Compiler.Css.Rule} rule + * @param {Compiler.AST.CSS.RelativeSelector[]} relative_selectors + * @param {Compiler.AST.CSS.Rule} rule * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element * @returns {boolean} */ @@ -178,9 +182,9 @@ function apply_selector(relative_selectors, rule, element) { } /** - * @param {Compiler.Css.RelativeSelector} relative_selector - * @param {Compiler.Css.RelativeSelector[]} parent_selectors - * @param {Compiler.Css.Rule} rule + * @param {Compiler.AST.CSS.RelativeSelector} relative_selector + * @param {Compiler.AST.CSS.RelativeSelector[]} parent_selectors + * @param {Compiler.AST.CSS.Rule} rule * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node * @returns {boolean} */ @@ -263,8 +267,8 @@ function apply_combinator(relative_selector, parent_selectors, rule, node) { * it's a `:global(...)` or unscopeable selector, or * is an `:is(...)` or `:where(...)` selector that contains * a global selector - * @param {Compiler.Css.RelativeSelector} selector - * @param {Compiler.Css.Rule} rule + * @param {Compiler.AST.CSS.RelativeSelector} selector + * @param {Compiler.AST.CSS.Rule} rule */ function is_global(selector, rule) { if (selector.metadata.is_global || selector.metadata.is_global_like) { @@ -272,7 +276,7 @@ function is_global(selector, rule) { } for (const s of selector.selectors) { - /** @type {Compiler.Css.SelectorList | null} */ + /** @type {Compiler.AST.CSS.SelectorList | null} */ let selector_list = null; let owner = rule; @@ -283,7 +287,7 @@ function is_global(selector, rule) { } if (s.type === 'NestingSelector') { - owner = /** @type {Compiler.Css.Rule} */ (rule.metadata.parent_rule); + owner = /** @type {Compiler.AST.CSS.Rule} */ (rule.metadata.parent_rule); selector_list = owner.prelude; } @@ -306,8 +310,8 @@ const regex_backslash_and_following_character = /\\(.)/g; /** * Ensure that `element` satisfies each simple selector in `relative_selector` * - * @param {Compiler.Css.RelativeSelector} relative_selector - * @param {Compiler.Css.Rule} rule + * @param {Compiler.AST.CSS.RelativeSelector} relative_selector + * @param {Compiler.AST.CSS.Rule} rule * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement} element * @returns {boolean} */ @@ -352,7 +356,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element) const seen = new Set(); /** - * @param {Compiler.SvelteNode} node + * @param {Compiler.AST.SvelteNode} node * @param {{ is_child: boolean }} state */ function walk_children(node, state) { @@ -389,7 +393,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element) // upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the // selector in a way that is similar to ancestor matching. In a sense, we're treating `.x:has(.y)` as `.x .y`. for (const has_selector of has_selectors) { - const complex_selectors = /** @type {Compiler.Css.SelectorList} */ (has_selector.args) + const complex_selectors = /** @type {Compiler.AST.CSS.SelectorList} */ (has_selector.args) .children; let matched = false; @@ -578,7 +582,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element) case 'NestingSelector': { let matched = false; - const parent = /** @type {Compiler.Css.Rule} */ (rule.metadata.parent_rule); + const parent = /** @type {Compiler.AST.CSS.Rule} */ (rule.metadata.parent_rule); for (const complex_selector of parent.prelude.children) { if ( @@ -611,9 +615,9 @@ function get_following_sibling_elements(element, include_self) { const path = element.metadata.path; let i = path.length; - /** @type {Compiler.SvelteNode} */ + /** @type {Compiler.AST.SvelteNode} */ let start = element; - let nodes = /** @type {Compiler.SvelteNode[]} */ ( + let nodes = /** @type {Compiler.AST.SvelteNode[]} */ ( /** @type {Compiler.AST.Fragment} */ (path[0]).nodes ); @@ -639,7 +643,7 @@ function get_following_sibling_elements(element, include_self) { const seen = new Set(); - /** @param {Compiler.SvelteNode} node */ + /** @param {Compiler.AST.SvelteNode} node */ function get_siblings(node) { walk(node, null, { RegularElement(node) { @@ -836,7 +840,7 @@ function get_possible_element_siblings(node, adjacent_only, seen = new Set()) { const result = new Map(); const path = node.metadata.path; - /** @type {Compiler.SvelteNode} */ + /** @type {Compiler.AST.SvelteNode} */ let current = node; let i = path.length; @@ -1008,7 +1012,7 @@ function higher_existence(exist1, exist2) { } /** - * @param {Compiler.SvelteNode[]} children + * @param {Compiler.AST.SvelteNode[]} children * @param {boolean} adjacent_only */ function loop_child(children, adjacent_only) { @@ -1038,7 +1042,7 @@ function loop_child(children, adjacent_only) { } /** - * @param {Compiler.SvelteNode} node + * @param {Compiler.AST.SvelteNode} node * @returns {node is Compiler.AST.IfBlock | Compiler.AST.EachBlock | Compiler.AST.AwaitBlock | Compiler.AST.KeyBlock | Compiler.AST.SlotElement} */ function is_block(node) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js index eab67327e2..238c83f00e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-warn.js @@ -1,17 +1,17 @@ /** @import { Visitors } from 'zimmerframe' */ -/** @import { Css } from '#compiler' */ +/** @import { AST } from '#compiler' */ import { walk } from 'zimmerframe'; import * as w from '../../../warnings.js'; import { is_keyframes_node } from '../../css.js'; /** - * @param {Css.StyleSheet} stylesheet + * @param {AST.CSS.StyleSheet} stylesheet */ export function warn_unused(stylesheet) { walk(stylesheet, { stylesheet }, visitors); } -/** @type {Visitors} */ +/** @type {Visitors} */ const visitors = { Atrule(node, context) { if (!is_keyframes_node(node)) { @@ -28,7 +28,7 @@ const visitors = { !node.metadata.used && // prevent double-marking of `.unused:is(.unused)` (context.path.at(-2)?.type !== 'PseudoClassSelector' || - /** @type {Css.ComplexSelector} */ (context.path.at(-4))?.metadata.used) + /** @type {AST.CSS.ComplexSelector} */ (context.path.at(-4))?.metadata.used) ) { const content = context.state.stylesheet.content; const text = content.styles.substring(node.start - content.start, node.end - content.start); diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/utils.js b/packages/svelte/src/compiler/phases/2-analyze/css/utils.js index 07171a23bb..d3fd71ec39 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/utils.js @@ -1,4 +1,4 @@ -/** @import { AST, Css } from '#compiler' */ +/** @import { AST } from '#compiler' */ /** @import { Node } from 'estree' */ const UNKNOWN = {}; @@ -36,7 +36,7 @@ export function get_possible_values(chunk) { /** * Returns all parent rules; root is last - * @param {Css.Rule | null} rule + * @param {AST.CSS.Rule | null} rule */ export function get_parent_rules(rule) { const rules = []; @@ -51,8 +51,8 @@ export function get_parent_rules(rule) { /** * True if is `:global(...)` or `:global` and no pseudo class that is scoped. - * @param {Css.RelativeSelector} relative_selector - * @returns {relative_selector is Css.RelativeSelector & { selectors: [Css.PseudoClassSelector, ...Array] }} + * @param {AST.CSS.RelativeSelector} relative_selector + * @returns {relative_selector is AST.CSS.RelativeSelector & { selectors: [AST.CSS.PseudoClassSelector, ...Array] }} */ export function is_global(relative_selector) { const first = relative_selector.selectors[0]; @@ -72,7 +72,7 @@ export function is_global(relative_selector) { /** * `true` if is a pseudo class that cannot be or is not scoped - * @param {Css.SimpleSelector} selector + * @param {AST.CSS.SimpleSelector} selector */ export function is_unscoped_pseudo_class(selector) { return ( @@ -96,8 +96,8 @@ export function is_unscoped_pseudo_class(selector) { /** * True if is `:global(...)` or `:global`, irrespective of whether or not there are any pseudo classes that are scoped. * Difference to `is_global`: `:global(x):has(y)` is `true` for `is_outer_global` but `false` for `is_global`. - * @param {Css.RelativeSelector} relative_selector - * @returns {relative_selector is Css.RelativeSelector & { selectors: [Css.PseudoClassSelector, ...Array] }} + * @param {AST.CSS.RelativeSelector} relative_selector + * @returns {relative_selector is AST.CSS.RelativeSelector & { selectors: [AST.CSS.PseudoClassSelector, ...Array] }} */ export function is_outer_global(relative_selector) { const first = relative_selector.selectors[0]; diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 9e29813ee3..042e88fa2f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -1,5 +1,5 @@ /** @import { Expression, Node, Program } from 'estree' */ -/** @import { Binding, AST, SvelteNode, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */ +/** @import { Binding, AST, ValidatedCompileOptions, ValidatedModuleCompileOptions } from '#compiler' */ /** @import { AnalysisState, Visitors } from './types' */ /** @import { Analysis, ComponentAnalysis, Js, ReactiveStatement, Template } from '../types' */ import { walk } from 'zimmerframe'; @@ -525,7 +525,7 @@ export function analyze_component(root, source, options) { // more legacy nonsense: if an `each` binding is reassigned/mutated, // treat the expression as being mutated as well - walk(/** @type {SvelteNode} */ (template.ast), null, { + walk(/** @type {AST.SvelteNode} */ (template.ast), null, { EachBlock(node) { const scope = /** @type {Scope} */ (template.scopes.get(node)); @@ -608,7 +608,7 @@ export function analyze_component(root, source, options) { reactive_statements: new Map() }; - walk(/** @type {SvelteNode} */ (ast), state, visitors); + walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); } // warn on any nonstate declarations that are a) reassigned and b) referenced in the template @@ -677,7 +677,7 @@ export function analyze_component(root, source, options) { function_depth: scope.function_depth }; - walk(/** @type {SvelteNode} */ (ast), state, visitors); + walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); } for (const [name, binding] of instance.scope.declarations) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index dedbe95ace..b4ca4dc262 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -1,11 +1,11 @@ import type { Scope } from '../scope.js'; import type { ComponentAnalysis, ReactiveStatement } from '../types.js'; -import type { ExpressionMetadata, AST, ValidatedCompileOptions, SvelteNode } from '#compiler'; +import type { AST, ExpressionMetadata, ValidatedCompileOptions } from '#compiler'; import type { LabeledStatement } from 'estree'; export interface AnalysisState { scope: Scope; - scopes: Map; + scopes: Map; analysis: ComponentAnalysis; options: ValidatedCompileOptions; ast_type: 'instance' | 'template' | 'module'; @@ -31,11 +31,11 @@ export interface AnalysisState { } export type Context = import('zimmerframe').Context< - SvelteNode, + AST.SvelteNode, State >; export type Visitors = import('zimmerframe').Visitors< - SvelteNode, + AST.SvelteNode, State >; diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js index 54e5b46486..a64c89cd88 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AssignmentExpression.js @@ -1,5 +1,4 @@ /** @import { AssignmentExpression } from 'estree' */ -/** @import { SvelteNode } from '#compiler' */ /** @import { Context } from '../types' */ import { extract_identifiers, object } from '../../../utils/ast.js'; import { validate_assignment } from './shared/utils.js'; 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 6c050d966a..6eb9faca6d 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Attribute.js @@ -1,5 +1,5 @@ /** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression } from 'estree' */ -/** @import { AST, DelegatedEvent, SvelteNode } from '#compiler' */ +/** @import { AST, DelegatedEvent } from '#compiler' */ /** @import { Context } from '../types' */ import { cannot_be_set_statically, is_capture_event, is_delegated } from '../../../../utils.js'; import { @@ -16,7 +16,7 @@ import { mark_subtree_dynamic } from './shared/fragment.js'; export function Attribute(node, context) { context.next(); - const parent = /** @type {SvelteNode} */ (context.path.at(-1)); + const parent = /** @type {AST.SvelteNode} */ (context.path.at(-1)); if (parent.type === 'RegularElement') { // special case