From 5d3385c56f9c95ad45dce1fbb4ed4e1a77ae5411 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 5 Mar 2024 01:05:18 +0100 Subject: [PATCH 1/6] fix: don't collapse whitespace within text nodes (#10691) fixes #9892 --- .changeset/happy-beds-scream.md | 5 +++ .../src/compiler/phases/3-transform/utils.js | 38 ++++++++++++------- .../svelte/src/compiler/phases/patterns.js | 4 +- .../runtime-legacy/samples/pre-tag/_config.js | 5 ++- .../svelte/tests/runtime-legacy/shared.ts | 16 +++++--- .../samples/snippet-whitespace/_config.js | 8 +++- .../samples/snippet-whitespace/main.svelte | 10 +++++ .../samples/snippet-whitespace/pre.svelte | 5 +++ 8 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 .changeset/happy-beds-scream.md create mode 100644 packages/svelte/tests/runtime-runes/samples/snippet-whitespace/pre.svelte diff --git a/.changeset/happy-beds-scream.md b/.changeset/happy-beds-scream.md new file mode 100644 index 0000000000..2a82aff9ba --- /dev/null +++ b/.changeset/happy-beds-scream.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: don't collapse whitespace within text nodes diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index ceb8838c90..97fc45d7a8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -162,24 +162,36 @@ export function clean_nodes( /** @type {import('#compiler').SvelteNode[]} */ const trimmed = []; - /** @type {import('#compiler').Text | null} */ - let last_text = null; + // Replace any whitespace between a text and non-text node with a single spaceand keep whitespace + // as-is within text nodes, or between text nodes and expression tags (because in the end they count + // as one text). This way whitespace is mostly preserved when using CSS with `white-space: pre-line` + // and default slot content going into a pre tag (which we can't see). + for (let i = 0; i < regular.length; i++) { + const prev = regular[i - 1]; + const node = regular[i]; + const next = regular[i + 1]; - // Replace any inbetween whitespace with a single space - for (const node of regular) { if (node.type === 'Text') { - node.data = node.data.replace(regex_whitespaces_strict, ' '); - node.raw = node.raw.replace(regex_whitespaces_strict, ' '); - if ( - (last_text === null && !can_remove_entirely) || - node.data !== ' ' || - node.data.charCodeAt(0) === 160 // non-breaking space - ) { + if (prev?.type !== 'ExpressionTag') { + const prev_is_text_ending_with_whitespace = + prev?.type === 'Text' && regex_ends_with_whitespaces.test(prev.data); + node.data = node.data.replace( + regex_starts_with_whitespaces, + prev_is_text_ending_with_whitespace ? '' : ' ' + ); + node.raw = node.raw.replace( + regex_starts_with_whitespaces, + prev_is_text_ending_with_whitespace ? '' : ' ' + ); + } + if (next?.type !== 'ExpressionTag') { + node.data = node.data.replace(regex_ends_with_whitespaces, ' '); + node.raw = node.raw.replace(regex_ends_with_whitespaces, ' '); + } + if (node.data && (node.data !== ' ' || !can_remove_entirely)) { trimmed.push(node); } - last_text = node; } else { - last_text = null; trimmed.push(node); } } diff --git a/packages/svelte/src/compiler/phases/patterns.js b/packages/svelte/src/compiler/phases/patterns.js index 74e715da1b..4f737adb3c 100644 --- a/packages/svelte/src/compiler/phases/patterns.js +++ b/packages/svelte/src/compiler/phases/patterns.js @@ -2,9 +2,9 @@ export const regex_whitespace = /\s/; export const regex_whitespaces = /\s+/; export const regex_starts_with_newline = /^\r?\n/; export const regex_starts_with_whitespace = /^\s/; -export const regex_starts_with_whitespaces = /^[ \t\r\n]*/; +export const regex_starts_with_whitespaces = /^[ \t\r\n]+/; export const regex_ends_with_whitespace = /\s$/; -export const regex_ends_with_whitespaces = /[ \t\r\n]*$/; +export const regex_ends_with_whitespaces = /[ \t\r\n]+$/; /** Not \S because that also removes explicit whitespace defined through things like ` ` */ export const regex_not_whitespace = /[^ \t\r\n]/; /** Not \s+ because that also includes explicit whitespace defined through things like ` ` */ diff --git a/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js b/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js index b8119f3f2c..d5a18488e2 100644 --- a/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js +++ b/packages/svelte/tests/runtime-legacy/samples/pre-tag/_config.js @@ -20,7 +20,10 @@ function get_html(ssr) { E F -
A B C D E F
    A
+
A + B C + D E + F
    A
     B
     
       C
diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts
index 02168c79f9..e8074b9e5b 100644
--- a/packages/svelte/tests/runtime-legacy/shared.ts
+++ b/packages/svelte/tests/runtime-legacy/shared.ts
@@ -66,7 +66,7 @@ export interface RuntimeTest = RecordTesting
+123          ;
+    456
`, + ssrHtml: `A B C D
Testing
+123          ;
+    456
` }); diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte index a85a932602..35bbf4b310 100644 --- a/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/main.svelte @@ -1,5 +1,15 @@ + A {#snippet snip()}C{/snippet} B {@render snip()} D + +
+    Testing
+123          ;
+    456
+
\ No newline at end of file diff --git a/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/pre.svelte b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/pre.svelte new file mode 100644 index 0000000000..821b578fa9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/snippet-whitespace/pre.svelte @@ -0,0 +1,5 @@ + + +
{@render children()}
From 767b5a8824998a57813a7f135b7442d829e93806 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 5 Mar 2024 01:07:44 +0100 Subject: [PATCH 2/6] chore: remove obsolete test (#10679) Checking for expected or unexpected props in 2024 is the job TypeScript, and runtime validation has resulted in false positives previously - therefore leave this out in Svelte 5. closes #10672 --- .../no-missing-prop-warnings/_config.js | 23 ------------------- .../no-missing-prop-warnings/main.svelte | 9 -------- 2 files changed, 32 deletions(-) delete mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/_config.js delete mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/main.svelte diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/_config.js deleted file mode 100644 index 45dc13fea0..0000000000 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/_config.js +++ /dev/null @@ -1,23 +0,0 @@ -import { test } from '../../assert'; -const tick = () => Promise.resolve(); - -export default test({ - dev: true, - skip: true, // TODO: needs dev time warning - async test({ assert, target }) { - /** @type {string[]} */ - const warnings = []; - const warn = console.warn; - - console.warn = (warning) => { - warnings.push(warning); - }; - - target.innerHTML = ''; - await tick(); - - assert.deepEqual(warnings, [" was created without expected prop 'bar'"]); - - console.warn = warn; - } -}); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/main.svelte deleted file mode 100644 index 31076dc357..0000000000 --- a/packages/svelte/tests/runtime-browser/custom-elements-samples/no-missing-prop-warnings/main.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - - -

foo: {foo}

-

bar: {bar}

From 6fb64c99147d69dde4908d6e58f6e3d98108ffc8 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 5 Mar 2024 02:28:25 +0200 Subject: [PATCH 3/6] fix: improve namespace inference when having `{@render}` and `{@html}` tags (#10631) * fix: treat snippets like normal components when inferring namespace * n * simplify * better desc * slight adjustment * feedback Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * feedback Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * skip html tag * test * changeset name * cleanup --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- .changeset/giant-planets-shake.md | 5 ++ .../src/compiler/phases/3-transform/utils.js | 85 +++++++++---------- .../svg-namespace-infer/Wrapper.svelte | 14 ++- .../samples/svg-namespace-infer/_config.js | 5 +- 4 files changed, 60 insertions(+), 49 deletions(-) create mode 100644 .changeset/giant-planets-shake.md diff --git a/.changeset/giant-planets-shake.md b/.changeset/giant-planets-shake.md new file mode 100644 index 0000000000..1197567abd --- /dev/null +++ b/.changeset/giant-planets-shake.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve namespace inference when having `{@render}` and `{@html}` tags diff --git a/packages/svelte/src/compiler/phases/3-transform/utils.js b/packages/svelte/src/compiler/phases/3-transform/utils.js index 97fc45d7a8..3429748e29 100644 --- a/packages/svelte/src/compiler/phases/3-transform/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/utils.js @@ -5,6 +5,7 @@ import { regex_whitespaces_strict } from '../patterns.js'; import * as b from '../../utils/builders.js'; +import { walk } from 'zimmerframe'; /** * @param {string} s @@ -249,51 +250,49 @@ export function infer_namespace(namespace, parent, nodes, path) { * @param {import('#compiler').Namespace | 'keep' | 'maybe_html'} namespace */ function check_nodes_for_namespace(nodes, namespace) { + /** + * @param {import('#compiler').SvelteElement | import('#compiler').RegularElement} node} + * @param {{stop: () => void}} context + */ + const RegularElement = (node, { stop }) => { + if (!node.metadata.svg) { + namespace = 'html'; + stop(); + } else if (namespace === 'keep') { + namespace = 'svg'; + } + }; + for (const node of nodes) { - if (node.type === 'RegularElement' || node.type === 'SvelteElement') { - if (!node.metadata.svg) { - namespace = 'html'; - break; - } else if (namespace === 'keep') { - namespace = 'svg'; - } - } else if ( - (node.type === 'Text' && node.data.trim() !== '') || - node.type === 'HtmlTag' || - node.type === 'RenderTag' - ) { - namespace = 'maybe_html'; - } else if (node.type === 'EachBlock') { - namespace = check_nodes_for_namespace(node.body.nodes, namespace); - if (namespace === 'html') break; - if (node.fallback) { - namespace = check_nodes_for_namespace(node.fallback.nodes, namespace); - if (namespace === 'html') break; - } - } else if (node.type === 'IfBlock') { - namespace = check_nodes_for_namespace(node.consequent.nodes, namespace); - if (namespace === 'html') break; - if (node.alternate) { - namespace = check_nodes_for_namespace(node.alternate.nodes, namespace); - if (namespace === 'html') break; - } - } else if (node.type === 'AwaitBlock') { - if (node.pending) { - namespace = check_nodes_for_namespace(node.pending.nodes, namespace); - if (namespace === 'html') break; + walk( + node, + {}, + { + _(node, { next }) { + if ( + node.type === 'EachBlock' || + node.type === 'IfBlock' || + node.type === 'AwaitBlock' || + node.type === 'Fragment' || + node.type === 'KeyBlock' || + node.type === 'RegularElement' || + node.type === 'SvelteElement' || + node.type === 'Text' + ) { + next(); + } + }, + SvelteElement: RegularElement, + RegularElement, + Text(node) { + if (node.data.trim() !== '') { + namespace = 'maybe_html'; + } + } } - if (node.then) { - namespace = check_nodes_for_namespace(node.then.nodes, namespace); - if (namespace === 'html') break; - } - if (node.catch) { - namespace = check_nodes_for_namespace(node.catch.nodes, namespace); - if (namespace === 'html') break; - } - } else if (node.type === 'KeyBlock') { - namespace = check_nodes_for_namespace(node.fragment.nodes, namespace); - if (namespace === 'html') break; - } + ); + + if (namespace === 'html') return namespace; } return namespace; diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte index 41f3f38fa8..5231a0c44a 100644 --- a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/Wrapper.svelte @@ -2,12 +2,18 @@ {#if true} true -{:else} - false {/if} -{#each Array(3).fill(0) as item, idx} +{#each Array(2).fill(0) as item, idx} {idx} {/each} - +{@html 'html'} + +{@render test("snippet")} + +{#snippet test(text)} +{text} +{/snippet} + + diff --git a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js index 46118026af..9ea299b21a 100644 --- a/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/svg-namespace-infer/_config.js @@ -7,7 +7,8 @@ export default test({ true 0 1 - 2 + html + snippet `, test({ assert, target }) { @@ -18,7 +19,7 @@ export default test({ const text_elements = target.querySelectorAll('text'); - assert.equal(text_elements.length, 5); + assert.equal(text_elements.length, 6); for (const { namespaceURI } of text_elements) assert.equal(namespaceURI, 'http://www.w3.org/2000/svg'); From d577740c82d5bf05d00e13969d1742f7960ea8ac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:31:08 -0700 Subject: [PATCH 4/6] Version Packages (next) (#10693) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 2 ++ packages/svelte/CHANGELOG.md | 8 ++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 06f31fc7d6..5d4b668ce9 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -97,6 +97,7 @@ "gentle-sheep-hug", "gentle-spies-happen", "giant-moons-own", + "giant-planets-shake", "giant-roses-press", "good-buses-reply", "good-cars-visit", @@ -108,6 +109,7 @@ "green-eggs-approve", "green-hounds-play", "green-tigers-judge", + "happy-beds-scream", "happy-suits-film", "healthy-planes-vanish", "heavy-comics-move", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 5a92088173..0fce88cce7 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,13 @@ # svelte +## 5.0.0-next.71 + +### Patch Changes + +- fix: improve namespace inference when having `{@render}` and `{@html}` tags ([#10631](https://github.com/sveltejs/svelte/pull/10631)) + +- fix: don't collapse whitespace within text nodes ([#10691](https://github.com/sveltejs/svelte/pull/10691)) + ## 5.0.0-next.70 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 9ebe29acd9..e8d9e43c1c 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.0.0-next.70", + "version": "5.0.0-next.71", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index 8b92139b74..86694f4697 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.70'; +export const VERSION = '5.0.0-next.71'; export const PUBLIC_VERSION = '5'; From 2d15c9de3f5ea7f99964342afad7bdbb6c8f0ea9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Mar 2024 17:53:47 -0700 Subject: [PATCH 5/6] create #client types alias (#10695) Co-authored-by: Rich Harris --- packages/svelte/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 869225f89f..0764cde0cb 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -24,7 +24,8 @@ "svelte/motion": ["./src/motion/public.d.ts"], "svelte/server": ["./src/server/index.js"], "svelte/store": ["./src/store/public.d.ts"], - "#compiler": ["./src/compiler/types/index.d.ts"] + "#compiler": ["./src/compiler/types/index.d.ts"], + "#client": ["./src/internal/client/types.d.ts"] } }, "include": [ From aa29a853dd1bb3bad623b7427588c82fc380c5f6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Mar 2024 18:31:35 -0700 Subject: [PATCH 6/6] chore: move reactivity code around (#10696) * move some code * split computations.js into deriveds.js and effects.js * move reactivity types into separate .d.ts file * move some signal code --------- Co-authored-by: Rich Harris --- .../src/internal/client/custom-element.js | 2 +- .../src/internal/client/dom/blocks/await.js | 2 +- .../src/internal/client/dom/blocks/each.js | 12 +- .../src/internal/client/dom/blocks/if.js | 2 +- .../src/internal/client/dom/blocks/key.js | 2 +- packages/svelte/src/internal/client/proxy.js | 8 +- .../internal/client/reactivity/deriveds.js | 36 ++++ .../{computations.js => effects.js} | 34 +--- .../src/internal/client/reactivity/props.js | 21 +++ .../src/internal/client/reactivity/sources.js | 128 +++++++++++++- .../src/internal/client/reactivity/store.js | 6 +- .../src/internal/client/reactivity/types.d.ts | 74 +++++++++ packages/svelte/src/internal/client/render.js | 7 +- .../svelte/src/internal/client/runtime.js | 157 +++--------------- .../svelte/src/internal/client/transitions.js | 2 +- .../svelte/src/internal/client/types.d.ts | 82 +-------- packages/svelte/src/internal/index.js | 9 +- packages/svelte/src/reactivity/index.js | 4 +- packages/svelte/tests/signals/test.ts | 60 ++++--- 19 files changed, 332 insertions(+), 316 deletions(-) create mode 100644 packages/svelte/src/internal/client/reactivity/deriveds.js rename packages/svelte/src/internal/client/reactivity/{computations.js => effects.js} (86%) create mode 100644 packages/svelte/src/internal/client/reactivity/props.js create mode 100644 packages/svelte/src/internal/client/reactivity/types.d.ts diff --git a/packages/svelte/src/internal/client/custom-element.js b/packages/svelte/src/internal/client/custom-element.js index 92d1a46573..0bd64cdf50 100644 --- a/packages/svelte/src/internal/client/custom-element.js +++ b/packages/svelte/src/internal/client/custom-element.js @@ -1,6 +1,6 @@ import { createClassComponent } from '../../legacy/legacy-client.js'; import { destroy_signal } from './runtime.js'; -import { render_effect } from './reactivity/computations.js'; +import { render_effect } from './reactivity/effects.js'; import { open, close } from './render.js'; import { define_property } from './utils.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/await.js b/packages/svelte/src/internal/client/dom/blocks/await.js index ede2a759e9..4f24a4d4ae 100644 --- a/packages/svelte/src/internal/client/dom/blocks/await.js +++ b/packages/svelte/src/internal/client/dom/blocks/await.js @@ -8,7 +8,7 @@ import { flushSync, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/computations.js'; +import { render_effect } from '../../reactivity/effects.js'; import { trigger_transitions } from '../../transitions.js'; import { AWAIT_BLOCK, UNINITIALIZED } from '../../constants.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/each.js b/packages/svelte/src/internal/client/dom/blocks/each.js index 8c40d6c774..59bf93319c 100644 --- a/packages/svelte/src/internal/client/dom/blocks/each.js +++ b/packages/svelte/src/internal/client/dom/blocks/each.js @@ -16,15 +16,9 @@ import { } from '../../hydration.js'; import { clear_text_content, empty, map_get, map_set } from '../../operations.js'; import { insert, remove } from '../../reconciler.js'; -import { - current_block, - destroy_signal, - execute_effect, - push_destroy_fn, - set_signal_value -} from '../../runtime.js'; -import { render_effect } from '../../reactivity/computations.js'; -import { source, mutable_source } from '../../reactivity/sources.js'; +import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; +import { render_effect } from '../../reactivity/effects.js'; +import { source, mutable_source, set_signal_value } from '../../reactivity/sources.js'; import { trigger_transitions } from '../../transitions.js'; import { is_array } from '../../utils.js'; import { EACH_BLOCK, EACH_ITEM_BLOCK } from '../../constants.js'; diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js index 1aa742fd43..6a2372596b 100644 --- a/packages/svelte/src/internal/client/dom/blocks/if.js +++ b/packages/svelte/src/internal/client/dom/blocks/if.js @@ -7,7 +7,7 @@ import { } from '../../hydration.js'; import { remove } from '../../reconciler.js'; import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/computations.js'; +import { render_effect } from '../../reactivity/effects.js'; import { trigger_transitions } from '../../transitions.js'; /** @returns {import('../../types.js').IfBlock} */ diff --git a/packages/svelte/src/internal/client/dom/blocks/key.js b/packages/svelte/src/internal/client/dom/blocks/key.js index 9499590817..d4cfd175b7 100644 --- a/packages/svelte/src/internal/client/dom/blocks/key.js +++ b/packages/svelte/src/internal/client/dom/blocks/key.js @@ -2,7 +2,7 @@ import { UNINITIALIZED, KEY_BLOCK } from '../../constants.js'; import { hydrate_block_anchor } from '../../hydration.js'; import { remove } from '../../reconciler.js'; import { current_block, destroy_signal, execute_effect, push_destroy_fn } from '../../runtime.js'; -import { render_effect } from '../../reactivity/computations.js'; +import { render_effect } from '../../reactivity/effects.js'; import { trigger_transitions } from '../../transitions.js'; import { safe_not_equal } from '../../reactivity/equality.js'; diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js index 290dc187ff..8627fc1f4a 100644 --- a/packages/svelte/src/internal/client/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -1,14 +1,12 @@ import { DEV } from 'esm-env'; import { get, - set, updating_derived, batch_inspect, current_component_context, - untrack, - set_signal_value + untrack } from './runtime.js'; -import { effect_active } from './reactivity/computations.js'; +import { effect_active } from './reactivity/effects.js'; import { array_prototype, define_property, @@ -20,7 +18,7 @@ import { object_prototype } from './utils.js'; import { add_owner, check_ownership, strip_owner } from './dev/ownership.js'; -import { mutable_source, source } from './reactivity/sources.js'; +import { mutable_source, source, set, set_signal_value } from './reactivity/sources.js'; import { STATE_SYMBOL, UNINITIALIZED } from './constants.js'; /** diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js new file mode 100644 index 0000000000..c710d34afc --- /dev/null +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -0,0 +1,36 @@ +import { CLEAN, DERIVED, UNINITIALIZED, UNOWNED } from '../constants.js'; +import { current_block, current_consumer, current_effect } from '../runtime.js'; +import { create_computation_signal, push_reference } from './effects.js'; +import { default_equals, safe_equal } from './equality.js'; + +/** + * @template V + * @param {() => V} fn + * @returns {import('../types.js').ComputationSignal} + */ +/*#__NO_SIDE_EFFECTS__*/ +export function derived(fn) { + const is_unowned = current_effect === null; + const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; + const signal = /** @type {import('../types.js').ComputationSignal} */ ( + create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block) + ); + signal.i = fn; + signal.e = default_equals; + if (current_consumer !== null) { + push_reference(current_consumer, signal); + } + return signal; +} + +/** + * @template V + * @param {() => V} fn + * @returns {import('../types.js').ComputationSignal} + */ +/*#__NO_SIDE_EFFECTS__*/ +export function derived_safe_equal(fn) { + const signal = derived(fn); + signal.e = safe_equal; + return signal; +} diff --git a/packages/svelte/src/internal/client/reactivity/computations.js b/packages/svelte/src/internal/client/reactivity/effects.js similarity index 86% rename from packages/svelte/src/internal/client/reactivity/computations.js rename to packages/svelte/src/internal/client/reactivity/effects.js index 13052e410a..8d1ae7f81e 100644 --- a/packages/svelte/src/internal/client/reactivity/computations.js +++ b/packages/svelte/src/internal/client/reactivity/effects.js @@ -27,7 +27,7 @@ import { * @param {V} value * @param {import('../types.js').Block | null} block */ -function create_computation_signal(flags, value, block) { +export function create_computation_signal(flags, value, block) { /** @type {import('../types.js').ComputationSignal} */ const signal = { b: block, @@ -224,35 +224,3 @@ export function render_effect(fn, block = current_block, managed = false, sync = } return internal_create_effect(flags, /** @type {any} */ (fn), sync, block, true); } - -/** - * @template V - * @param {() => V} fn - * @returns {import('../types.js').ComputationSignal} - */ -/*#__NO_SIDE_EFFECTS__*/ -export function derived(fn) { - const is_unowned = current_effect === null; - const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; - const signal = /** @type {import('../types.js').ComputationSignal} */ ( - create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block) - ); - signal.i = fn; - signal.e = default_equals; - if (current_consumer !== null) { - push_reference(current_consumer, signal); - } - return signal; -} - -/** - * @template V - * @param {() => V} fn - * @returns {import('../types.js').ComputationSignal} - */ -/*#__NO_SIDE_EFFECTS__*/ -export function derived_safe_equal(fn) { - const signal = derived(fn); - signal.e = safe_equal; - return signal; -} diff --git a/packages/svelte/src/internal/client/reactivity/props.js b/packages/svelte/src/internal/client/reactivity/props.js new file mode 100644 index 0000000000..99e5048627 --- /dev/null +++ b/packages/svelte/src/internal/client/reactivity/props.js @@ -0,0 +1,21 @@ +/** + * @param {((value?: number) => number)} fn + * @param {1 | -1} [d] + * @returns {number} + */ +export function update_prop(fn, d = 1) { + const value = fn(); + fn(value + d); + return value; +} + +/** + * @param {((value?: number) => number)} fn + * @param {1 | -1} [d] + * @returns {number} + */ +export function update_pre_prop(fn, d = 1) { + const value = fn() + d; + fn(value); + return value; +} diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index d0b6de7cba..050b7faac6 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -1,7 +1,25 @@ import { DEV } from 'esm-env'; -import { current_component_context } from '../runtime.js'; +import { + current_component_context, + current_consumer, + current_dependencies, + current_effect, + current_untracked_writes, + current_untracking, + flushSync, + get, + ignore_mutation_validation, + is_batching_effect, + is_runes, + mark_signal_consumers, + schedule_effect, + set_current_untracked_writes, + set_last_inspected_signal, + set_signal_status, + untrack +} from '../runtime.js'; import { default_equals, safe_equal } from './equality.js'; -import { CLEAN, SOURCE } from '../constants.js'; +import { CLEAN, DERIVED, DIRTY, MANAGED, SOURCE } from '../constants.js'; /** * @template V @@ -68,3 +86,109 @@ function create_source_signal(flags, value) { w: 0 }; } + +/** + * @template V + * @param {import('./types.js').Signal} signal + * @param {V} value + * @returns {V} + */ +export function set(signal, value) { + set_signal_value(signal, value); + return value; +} + +/** + * @template V + * @param {import('./types.js').Signal} signal + * @param {V} value + * @returns {void} + */ +export function set_sync(signal, value) { + flushSync(() => set(signal, value)); +} + +/** + * @template V + * @param {import('./types.js').Signal} source + * @param {V} value + */ +export function mutate(source, value) { + set_signal_value( + source, + untrack(() => get(source)) + ); + return value; +} + +/** + * @template V + * @param {import('./types.js').Signal} signal + * @param {V} value + * @returns {void} + */ +export function set_signal_value(signal, value) { + if ( + !current_untracking && + !ignore_mutation_validation && + current_consumer !== null && + is_runes(null) && + (current_consumer.f & DERIVED) !== 0 + ) { + throw new Error( + 'ERR_SVELTE_UNSAFE_MUTATION' + + (DEV + ? ": Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " + + 'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' + + 'to be reactive do not use the "$state" rune for that declaration.' + : '') + ); + } + if ( + (signal.f & SOURCE) !== 0 && + !(/** @type {import('#client').EqualsFunctions} */ (signal.e)(value, signal.v)) + ) { + signal.v = value; + // Increment write version so that unowned signals can properly track dirtyness + signal.w++; + // If the current signal is running for the first time, it won't have any + // consumers as we only allocate and assign the consumers after the signal + // has fully executed. So in the case of ensuring it registers the consumer + // properly for itself, we need to ensure the current effect actually gets + // scheduled. i.e: + // + // $effect(() => x++) + // + // We additionally want to skip this logic for when ignore_mutation_validation is + // true, as stores write to source signal on initialization. + if ( + is_runes(null) && + !ignore_mutation_validation && + current_effect !== null && + current_effect.c === null && + (current_effect.f & CLEAN) !== 0 && + (current_effect.f & MANAGED) === 0 + ) { + if (current_dependencies !== null && current_dependencies.includes(signal)) { + set_signal_status(current_effect, DIRTY); + schedule_effect(current_effect, false); + } else { + if (current_untracked_writes === null) { + set_current_untracked_writes([signal]); + } else { + current_untracked_writes.push(signal); + } + } + } + mark_signal_consumers(signal, DIRTY, true); + + // @ts-expect-error + if (DEV && signal.inspect) { + if (is_batching_effect) { + set_last_inspected_signal(/** @type {import('./types.js').SignalDebug} */ (signal)); + } else { + for (const fn of /** @type {import('./types.js').SignalDebug} */ (signal).inspect) fn(); + } + } + } +} diff --git a/packages/svelte/src/internal/client/reactivity/store.js b/packages/svelte/src/internal/client/reactivity/store.js index 3680b95052..7226fb879f 100644 --- a/packages/svelte/src/internal/client/reactivity/store.js +++ b/packages/svelte/src/internal/client/reactivity/store.js @@ -1,9 +1,9 @@ import { subscribe_to_store } from '../../../store/utils.js'; import { noop } from '../../common.js'; import { UNINITIALIZED } from '../constants.js'; -import { get, set, set_ignore_mutation_validation, untrack } from '../runtime.js'; -import { user_effect } from './computations.js'; -import { mutable_source } from './sources.js'; +import { get, set_ignore_mutation_validation, untrack } from '../runtime.js'; +import { user_effect } from './effects.js'; +import { mutable_source, set } from './sources.js'; /** * Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy diff --git a/packages/svelte/src/internal/client/reactivity/types.d.ts b/packages/svelte/src/internal/client/reactivity/types.d.ts new file mode 100644 index 0000000000..86c810ff90 --- /dev/null +++ b/packages/svelte/src/internal/client/reactivity/types.d.ts @@ -0,0 +1,74 @@ +import type { Block, ComponentContext, EqualsFunctions } from '#client'; +import type { DERIVED, EFFECT, PRE_EFFECT, RENDER_EFFECT, SOURCE } from '../constants'; + +export type SignalFlags = + | typeof SOURCE + | typeof DERIVED + | typeof EFFECT + | typeof PRE_EFFECT + | typeof RENDER_EFFECT; +export type EffectType = typeof EFFECT | typeof PRE_EFFECT | typeof RENDER_EFFECT; + +// We keep two shapes rather than a single monomorphic shape to improve the memory usage. +// Source signals don't need the same shape as they simply don't do as much as computations +// (effects and derived signals). Thus we can improve the memory profile at the slight cost +// of some runtime performance. + +export type SourceSignal = { + /** consumers: Signals that read from the current signal */ + c: null | ComputationSignal[]; + /** equals: For value equality */ + e: null | EqualsFunctions; + /** flags: The types that the signal represent, as a bitwise value */ + f: SignalFlags; + /** value: The latest value for this signal */ + v: V; + // write version + w: number; +}; + +export type SourceSignalDebug = { + /** This is DEV only */ + inspect: Set; +}; + +export type ComputationSignal = { + /** block: The block associated with this effect/computed */ + b: null | Block; + /** consumers: Signals that read from the current signal */ + c: null | ComputationSignal[]; + /** context: The associated component if this signal is an effect/computed */ + x: null | ComponentContext; + /** dependencies: Signals that this signal reads from */ + d: null | Signal[]; + /** destroy: Thing(s) that need destroying */ + y: null | (() => void) | Array<() => void>; + /** equals: For value equality */ + e: null | EqualsFunctions; + /** The types that the signal represent, as a bitwise value */ + f: SignalFlags; + /** init: The function that we invoke for effects and computeds */ + i: + | null + | (() => V) + | (() => void | (() => void)) + | ((b: Block, s: Signal) => void | (() => void)); + /** references: Anything that a signal owns */ + r: null | ComputationSignal[]; + /** value: The latest value for this signal, doubles as the teardown for effects */ + v: V; + /** level: the depth from the root signal, used for ordering render/pre-effects topologically **/ + l: number; + /** write version: used for unowned signals to track if their depdendencies are dirty or not **/ + w: number; +}; + +export type Signal = SourceSignal | ComputationSignal; + +export type SignalDebug = SourceSignalDebug & Signal; + +export type EffectSignal = ComputationSignal void)>; + +export type MaybeSignal = T | Signal; + +export type UnwrappedSignal = T extends Signal ? U : T; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 836be712b3..8942e8ad87 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -45,19 +45,18 @@ import { pop, current_component_context, get, - set, is_signals_recorded, inspect_fn, deep_read_state } from './runtime.js'; +import { derived } from './reactivity/deriveds.js'; import { render_effect, effect, managed_effect, - derived, pre_effect, user_effect -} from './reactivity/computations.js'; +} from './reactivity/effects.js'; import { current_hydration_fragment, get_hydration_fragment, @@ -76,7 +75,7 @@ import { } from './utils.js'; import { run } from '../common.js'; import { bind_transition, trigger_transitions } from './transitions.js'; -import { mutable_source, source } from './reactivity/sources.js'; +import { mutable_source, source, set } from './reactivity/sources.js'; import { safe_equal, safe_not_equal } from './reactivity/equality.js'; /** @type {Set} */ diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 6190d16987..ed8d76f6f8 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -10,7 +10,7 @@ import { object_prototype } from './utils.js'; import { unstate } from './proxy.js'; -import { pre_effect } from './reactivity/computations.js'; +import { pre_effect } from './reactivity/effects.js'; import { EACH_BLOCK, IF_BLOCK, @@ -31,6 +31,7 @@ import { } from './constants.js'; import { flush_tasks } from './dom/task.js'; import { add_owner } from './dev/ownership.js'; +import { mutate, set_signal_value } from './reactivity/sources.js'; const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT; @@ -64,20 +65,32 @@ export let current_consumer = null; export let current_effect = null; /** @type {null | import('./types.js').Signal[]} */ -let current_dependencies = null; +export let current_dependencies = null; let current_dependencies_index = 0; /** * Tracks writes that the effect it's executed in doesn't listen to yet, * so that the dependency can be added to the effect later on if it then reads it * @type {null | import('./types.js').Signal[]} */ -let current_untracked_writes = null; +export let current_untracked_writes = null; + +/** @param {null | import('./types.js').Signal[]} value */ +export function set_current_untracked_writes(value) { + current_untracked_writes = value; +} + /** @type {null | import('./types.js').SignalDebug} */ -let last_inspected_signal = null; +export let last_inspected_signal = null; + +/** @param {null | import('./types.js').SignalDebug} signal */ +export function set_last_inspected_signal(signal) { + last_inspected_signal = signal; +} + /** If `true`, `get`ting the signal should not register it as a dependency */ export let current_untracking = false; /** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */ -let ignore_mutation_validation = false; +export let ignore_mutation_validation = false; /** @param {boolean} value */ export function set_ignore_mutation_validation(value) { ignore_mutation_validation = value; @@ -110,7 +123,7 @@ export let updating_derived = false; * @param {null | import('./types.js').ComponentContext} context * @returns {boolean} */ -function is_runes(context) { +export function is_runes(context) { const component_context = context || current_component_context; return component_context !== null && component_context.r; } @@ -767,27 +780,6 @@ export function get(signal) { return signal.v; } -/** - * @template V - * @param {import('./types.js').Signal} signal - * @param {V} value - * @returns {V} - */ -export function set(signal, value) { - set_signal_value(signal, value); - return value; -} - -/** - * @template V - * @param {import('./types.js').Signal} signal - * @param {V} value - * @returns {void} - */ -export function set_sync(signal, value) { - flushSync(() => set(signal, value)); -} - /** * Invokes a function and captures all signals that are read during the invocation, * then invalidates them. @@ -816,19 +808,6 @@ export function invalidate_inner_signals(fn) { } } -/** - * @template V - * @param {import('./types.js').Signal} source - * @param {V} value - */ -export function mutate(source, value) { - set_signal_value( - source, - untrack(() => get(source)) - ); - return value; -} - /** * @param {import('./types.js').ComputationSignal} signal * @param {boolean} inert @@ -900,7 +879,7 @@ export function mark_subtree_inert(signal, inert, visited_blocks = new Set()) { * @param {boolean} force_schedule * @returns {void} */ -function mark_signal_consumers(signal, to_status, force_schedule) { +export function mark_signal_consumers(signal, to_status, force_schedule) { const runes = is_runes(null); const consumers = signal.c; if (consumers !== null) { @@ -932,78 +911,6 @@ function mark_signal_consumers(signal, to_status, force_schedule) { } } -/** - * @template V - * @param {import('./types.js').Signal} signal - * @param {V} value - * @returns {void} - */ -export function set_signal_value(signal, value) { - if ( - !current_untracking && - !ignore_mutation_validation && - current_consumer !== null && - is_runes(null) && - (current_consumer.f & DERIVED) !== 0 - ) { - throw new Error( - 'ERR_SVELTE_UNSAFE_MUTATION' + - (DEV - ? ": Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " + - 'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' + - 'to be reactive do not use the "$state" rune for that declaration.' - : '') - ); - } - if ( - (signal.f & SOURCE) !== 0 && - !(/** @type {import('./types.js').EqualsFunctions} */ (signal.e)(value, signal.v)) - ) { - signal.v = value; - // Increment write version so that unowned signals can properly track dirtyness - signal.w++; - // If the current signal is running for the first time, it won't have any - // consumers as we only allocate and assign the consumers after the signal - // has fully executed. So in the case of ensuring it registers the consumer - // properly for itself, we need to ensure the current effect actually gets - // scheduled. i.e: - // - // $effect(() => x++) - // - // We additionally want to skip this logic for when ignore_mutation_validation is - // true, as stores write to source signal on initialization. - if ( - is_runes(null) && - !ignore_mutation_validation && - current_effect !== null && - current_effect.c === null && - (current_effect.f & CLEAN) !== 0 && - (current_effect.f & MANAGED) === 0 - ) { - if (current_dependencies !== null && current_dependencies.includes(signal)) { - set_signal_status(current_effect, DIRTY); - schedule_effect(current_effect, false); - } else { - if (current_untracked_writes === null) { - current_untracked_writes = [signal]; - } else { - current_untracked_writes.push(signal); - } - } - } - mark_signal_consumers(signal, DIRTY, true); - - // @ts-expect-error - if (DEV && signal.inspect) { - if (is_batching_effect) { - last_inspected_signal = /** @type {import('./types.js').SignalDebug} */ (signal); - } else { - for (const fn of /** @type {import('./types.js').SignalDebug} */ (signal).inspect) fn(); - } - } - } -} - /** * @template V * @param {import('./types.js').ComputationSignal} signal @@ -1071,7 +978,7 @@ const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN); * @param {number} status * @returns {void} */ -function set_signal_status(signal, status) { +export function set_signal_status(signal, status) { signal.f = (signal.f & STATUS_MASK) | status; } @@ -1208,17 +1115,6 @@ export function update(signal, d = 1) { return value; } -/** - * @param {((value?: number) => number)} fn - * @param {1 | -1} [d] - * @returns {number} - */ -export function update_prop(fn, d = 1) { - const value = fn(); - fn(value + d); - return value; -} - /** * @param {import('./types.js').Signal} signal * @param {1 | -1} [d] @@ -1230,17 +1126,6 @@ export function update_pre(signal, d = 1) { return value; } -/** - * @param {((value?: number) => number)} fn - * @param {1 | -1} [d] - * @returns {number} - */ -export function update_pre_prop(fn, d = 1) { - const value = fn() + d; - fn(value); - return value; -} - /** * @param {Record} obj * @param {string[]} keys diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 59eb3ad60d..deb3a27598 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -12,7 +12,7 @@ import { import { destroy_each_item_block, get_first_element } from './dom/blocks/each.js'; import { schedule_raf_task } from './dom/task.js'; import { append_child, empty } from './operations.js'; -import { effect, managed_effect, managed_pre_effect } from './reactivity/computations.js'; +import { effect, managed_effect, managed_pre_effect } from './reactivity/effects.js'; import { current_block, current_effect, diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 028f81af43..853b960717 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -1,9 +1,4 @@ import { - DERIVED, - EFFECT, - RENDER_EFFECT, - SOURCE, - PRE_EFFECT, ROOT_BLOCK, EACH_BLOCK, EACH_ITEM_BLOCK, @@ -16,16 +11,7 @@ import { SNIPPET_BLOCK, STATE_SYMBOL } from './constants.js'; - -// Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file - -export type SignalFlags = - | typeof SOURCE - | typeof DERIVED - | typeof EFFECT - | typeof PRE_EFFECT - | typeof RENDER_EFFECT; -export type EffectType = typeof EFFECT | typeof PRE_EFFECT | typeof RENDER_EFFECT; +import type { ComputationSignal, EffectSignal, Signal, SourceSignal } from './reactivity/types.js'; type EventCallback = (event: Event) => boolean; export type EventCallbackMap = Record; @@ -67,70 +53,6 @@ export type ComponentContext = { }; }; -// We keep two shapes rather than a single monomorphic shape to improve the memory usage. -// Source signals don't need the same shape as they simply don't do as much as computations -// (effects and derived signals). Thus we can improve the memory profile at the slight cost -// of some runtime performance. - -export type SourceSignal = { - /** consumers: Signals that read from the current signal */ - c: null | ComputationSignal[]; - /** equals: For value equality */ - e: null | EqualsFunctions; - /** flags: The types that the signal represent, as a bitwise value */ - f: SignalFlags; - /** value: The latest value for this signal */ - v: V; - // write version - w: number; -}; - -export type SourceSignalDebug = { - /** This is DEV only */ - inspect: Set; -}; - -export type ComputationSignal = { - /** block: The block associated with this effect/computed */ - b: null | Block; - /** consumers: Signals that read from the current signal */ - c: null | ComputationSignal[]; - /** context: The associated component if this signal is an effect/computed */ - x: null | ComponentContext; - /** dependencies: Signals that this signal reads from */ - d: null | Signal[]; - /** destroy: Thing(s) that need destroying */ - y: null | (() => void) | Array<() => void>; - /** equals: For value equality */ - e: null | EqualsFunctions; - /** The types that the signal represent, as a bitwise value */ - f: SignalFlags; - /** init: The function that we invoke for effects and computeds */ - i: - | null - | (() => V) - | (() => void | (() => void)) - | ((b: Block, s: Signal) => void | (() => void)); - /** references: Anything that a signal owns */ - r: null | ComputationSignal[]; - /** value: The latest value for this signal, doubles as the teardown for effects */ - v: V; - /** level: the depth from the root signal, used for ordering render/pre-effects topologically **/ - l: number; - /** write version: used for unowned signals to track if their depdendencies are dirty or not **/ - w: number; -}; - -export type Signal = SourceSignal | ComputationSignal; - -export type SignalDebug = SourceSignalDebug & Signal; - -export type EffectSignal = ComputationSignal void)>; - -export type MaybeSignal = T | Signal; - -export type UnwrappedSignal = T extends Signal ? U : T; - export type EqualsFunctions = (a: T, v: T) => boolean; export type BlockType = @@ -421,3 +343,5 @@ export interface ProxyMetadata> { export type ProxyStateObject> = T & { [STATE_SYMBOL]: ProxyMetadata; }; + +export * from './reactivity/types'; diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index cb57244def..1503477eb5 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -1,16 +1,11 @@ export { get, - set, - set_sync, invalidate_inner_signals, flushSync, tick, untrack, update, - update_prop, update_pre, - update_pre_prop, - mutate, value_or_fallback, exclude_from_object, pop, @@ -30,9 +25,11 @@ export { await_block as await } from './client/dom/blocks/await.js'; export { if_block as if } from './client/dom/blocks/if.js'; export { key_block as key } from './client/dom/blocks/key.js'; export * from './client/dom/blocks/each.js'; -export * from './client/reactivity/computations.js'; +export * from './client/reactivity/deriveds.js'; +export * from './client/reactivity/effects.js'; export * from './client/reactivity/sources.js'; export * from './client/reactivity/equality.js'; +export * from './client/reactivity/props.js'; export * from './client/reactivity/store.js'; export * from './client/render.js'; export * from './client/validate.js'; diff --git a/packages/svelte/src/reactivity/index.js b/packages/svelte/src/reactivity/index.js index b68cef7be3..f802121a2e 100644 --- a/packages/svelte/src/reactivity/index.js +++ b/packages/svelte/src/reactivity/index.js @@ -1,5 +1,5 @@ -import { source } from '../internal/client/reactivity/sources.js'; -import { get, set } from '../internal/client/runtime.js'; +import { source, set } from '../internal/client/reactivity/sources.js'; +import { get } from '../internal/client/runtime.js'; /** @type {Array} */ const read = [ diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 0df1218f57..a0c8454600 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1,12 +1,8 @@ import { describe, assert, it } from 'vitest'; import * as $ from '../../src/internal/client/runtime'; -import { - derived, - effect, - render_effect, - user_effect -} from '../../src/internal/client/reactivity/computations'; -import { source } from '../../src/internal/client/reactivity/sources'; +import { derived } from '../../src/internal/client/reactivity/deriveds'; +import { effect, render_effect, user_effect } from '../../src/internal/client/reactivity/effects'; +import { source, set } from '../../src/internal/client/reactivity/sources'; import type { ComputationSignal } from '../../src/internal/client/types'; import { proxy } from '../../src/internal/client/proxy'; @@ -51,8 +47,8 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); - $.flushSync(() => $.set(count, 2)); + $.flushSync(() => set(count, 1)); + $.flushSync(() => set(count, 2)); assert.deepEqual(log, ['0:0', '1:2', '2:4']); }; @@ -72,8 +68,8 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); - $.flushSync(() => $.set(count, 2)); + $.flushSync(() => set(count, 1)); + $.flushSync(() => set(count, 2)); assert.deepEqual(log, ['A:0:0', 'B:0', 'A:1:2', 'B:2', 'A:2:4', 'B:4']); }; @@ -93,8 +89,8 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); - $.flushSync(() => $.set(count, 2)); + $.flushSync(() => set(count, 1)); + $.flushSync(() => set(count, 2)); assert.deepEqual(log, ['A:0', 'B:0:0', 'A:2', 'B:1:2', 'A:4', 'B:2:4']); }; @@ -111,8 +107,8 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); - $.flushSync(() => $.set(count, 2)); + $.flushSync(() => set(count, 1)); + $.flushSync(() => set(count, 2)); assert.deepEqual(log, [0, 2, 4]); }; @@ -130,8 +126,8 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); - $.flushSync(() => $.set(count, 2)); + $.flushSync(() => set(count, 1)); + $.flushSync(() => set(count, 2)); assert.deepEqual(log, [0, 4, 8]); }; @@ -167,12 +163,12 @@ describe('signals', () => { let i = 2; while (--i) { res.length = 0; - $.set(B, 1); - $.set(A, 1 + i * 2); + set(B, 1); + set(A, 1 + i * 2); $.flushSync(); - $.set(A, 2 + i * 2); - $.set(B, 2); + set(A, 2 + i * 2); + set(B, 2); $.flushSync(); assert.equal(res.length, 4); @@ -195,13 +191,13 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); + $.flushSync(() => set(count, 1)); // Ensure we're not leaking consumers assert.deepEqual(count.c?.length, 1); - $.flushSync(() => $.set(count, 2)); + $.flushSync(() => set(count, 2)); // Ensure we're not leaking consumers assert.deepEqual(count.c?.length, 1); - $.flushSync(() => $.set(count, 3)); + $.flushSync(() => set(count, 3)); // Ensure we're not leaking consumers assert.deepEqual(count.c?.length, 1); assert.deepEqual(log, [0, 1, 2, 3]); @@ -224,11 +220,11 @@ describe('signals', () => { $.get(c); - $.flushSync(() => $.set(a, 1)); + $.flushSync(() => set(a, 1)); $.get(c); - $.flushSync(() => $.set(b, 1)); + $.flushSync(() => set(b, 1)); $.get(c); @@ -257,11 +253,11 @@ describe('signals', () => { }); return () => { - $.flushSync(() => $.set(count, 1)); - $.flushSync(() => $.set(count, 2)); - $.flushSync(() => $.set(count, 3)); - $.flushSync(() => $.set(count, 4)); - $.flushSync(() => $.set(count, 0)); + $.flushSync(() => set(count, 1)); + $.flushSync(() => set(count, 2)); + $.flushSync(() => set(count, 3)); + $.flushSync(() => set(count, 4)); + $.flushSync(() => set(count, 0)); // Ensure we're not leaking consumers assert.deepEqual(count.c?.length, 1); assert.deepEqual(log, [0, 2, 'limit', 0]); @@ -319,7 +315,7 @@ describe('signals', () => { const value = source({ count: 0 }); user_effect(() => { - $.set(value, { count: 0 }); + set(value, { count: 0 }); $.get(value); });