From 75fc09a79e9e2ac015bede1bfd487776e025b4ad Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 4 Dec 2023 14:35:46 -0500 Subject: [PATCH] Proxied state (#9739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * magic objects * read length eagerly — triggers reconciliation * nested magic * tests * more tests * fix array memory leak * skipped, partially passing array test * Fix each revert bad changes * more 1337 * eliminate closures * maybe this is unnecessary? * only create sources for own properties * more * rename stuff * shuffle things around * emit $.proxy * remove proxy helper from public API * only create sources for writable properties * update test * get tests passing * fix * remove state-not-mutated warning, which is no longer valid * track reassignments separately from mutations * update test - effects no longer fire on mutations unnecessarily * move util into utils file * move each logic into its own module; breathe sigh of relief * tweaks * more tweaks * improve runtime * fix mistake * ensure we proxy when assigning to state * fix test * handle frozen * create sources when reading proxy properties inside deriveds * only mutate in legacy mode * add immutable to transform state * remove unused second argument to derived * remove unused second argument to source, and runtime immutable option to createRoot (not sure what that was doing there?) * oops, backwards * dedicated binding.kind for legacy reactive imports * avoid using prop_source in more cases. bit hacky, could be tidier, but it works * distinguish between source and mutable_source * remove immutable option from mount * remove unused apparatus around immutable option * deprecate immutable * fix * tweak * better default value handling * remove unnecessary exports * whitespace is free * remove obsolete symbol from proxy * cleanup ts * improve runtime perf by removing version reads in has() * add missing proxy call * tune perf of has() more * ensure we only create sources in effect_active() * fix proxy of computed fields * simplify and fix issue with indexed each blocks * fix compiler errors around exported state * only create source for state that is reassigned * temporary fix, we should come up with something better than this * readonly props * fix test * add test for bind: * make readonly dev-only * docs * forbid setPrototypeOf * lol whoops --------- Co-authored-by: Rich Harris Co-authored-by: Dominic Gannaway --- packages/svelte/src/compiler/errors.js | 4 +- .../src/compiler/phases/2-analyze/index.js | 37 +- .../compiler/phases/2-analyze/validation.js | 10 +- .../3-transform/client/transform-client.js | 20 +- .../phases/3-transform/client/utils.js | 93 +- .../client/visitors/javascript-runes.js | 26 +- .../3-transform/client/visitors/template.js | 24 +- .../3-transform/server/transform-server.js | 2 +- packages/svelte/src/compiler/phases/scope.js | 8 +- .../svelte/src/compiler/phases/types.d.ts | 2 + packages/svelte/src/compiler/types/index.d.ts | 5 +- packages/svelte/src/compiler/utils/ast.js | 33 + .../svelte/src/compiler/validate-options.js | 17 +- packages/svelte/src/compiler/warnings.js | 3 - packages/svelte/src/constants.js | 1 + packages/svelte/src/internal/client/each.js | 803 ++++++++++++++++++ .../svelte/src/internal/client/proxy/proxy.js | 176 ++++ .../src/internal/client/proxy/readonly.js | 65 ++ .../svelte/src/internal/client/reconciler.js | 441 +--------- packages/svelte/src/internal/client/render.js | 342 +------- .../svelte/src/internal/client/runtime.js | 111 ++- .../svelte/src/internal/client/transitions.js | 3 +- .../svelte/src/internal/client/types.d.ts | 10 +- packages/svelte/src/internal/index.js | 12 +- packages/svelte/src/internal/server/index.js | 5 +- packages/svelte/src/legacy/legacy-client.js | 1 - .../samples/export-derived-state/_config.js | 9 + .../export-derived-state/main.svelte.js | 3 + .../samples/export-state/_config.js | 9 + .../samples/export-state/main.svelte.js | 13 + packages/svelte/tests/compiler-errors/test.ts | 8 +- .../samples/action-duplicate/output.json | 4 +- .../samples/action-with-call/output.json | 4 +- .../action-with-identifier/output.json | 4 +- .../samples/action-with-literal/output.json | 4 +- .../parser-legacy/samples/action/output.json | 4 +- .../samples/animation/output.json | 40 +- .../attribute-class-directive/output.json | 4 +- .../attribute-containing-solidus/output.json | 8 +- .../attribute-curly-bracket/output.json | 8 +- .../attribute-dynamic-boolean/output.json | 8 +- .../samples/attribute-dynamic/output.json | 10 +- .../samples/attribute-empty/output.json | 16 +- .../samples/attribute-escaped/output.json | 6 +- .../samples/attribute-multiple/output.json | 8 +- .../samples/attribute-shorthand/output.json | 8 +- .../attribute-static-boolean/output.json | 6 +- .../samples/attribute-static/output.json | 6 +- .../output.json | 6 +- .../output.json | 4 +- .../output.json | 38 +- .../attribute-style-directive/output.json | 6 +- .../samples/attribute-style/output.json | 8 +- .../samples/attribute-unquoted/output.json | 6 +- .../attribute-with-whitespace/output.json | 6 +- .../samples/await-catch/output.json | 28 +- .../samples/await-then-catch/output.json | 38 +- .../samples/binding-shorthand/output.json | 6 +- .../parser-legacy/samples/binding/output.json | 6 +- .../samples/comment-with-ignores/output.json | 4 +- .../parser-legacy/samples/comment/output.json | 4 +- .../samples/component-dynamic/output.json | 12 +- .../convert-entities-in-element/output.json | 6 +- .../samples/convert-entities/output.json | 4 +- .../parser-legacy/samples/css/output.json | 32 +- .../dynamic-element-string/output.json | 28 +- .../dynamic-element-variable/output.json | 64 +- .../samples/dynamic-import/output.json | 2 +- .../each-block-destructured/output.json | 46 +- .../samples/each-block-else/output.json | 46 +- .../samples/each-block-indexed/output.json | 44 +- .../samples/each-block-keyed/output.json | 40 +- .../samples/each-block/output.json | 40 +- .../output.json | 12 +- .../element-with-attribute/output.json | 12 +- .../samples/element-with-mustache/output.json | 10 +- .../samples/element-with-text/output.json | 6 +- .../samples/elements/output.json | 6 +- .../samples/event-handler/output.json | 14 +- .../samples/if-block-else/output.json | 14 +- .../samples/if-block-elseif/output.json | 20 +- .../samples/if-block/output.json | 6 +- .../samples/implicitly-closed-li/output.json | 18 +- .../parser-legacy/samples/nbsp/output.json | 6 +- .../no-error-if-before-closing/output.json | 52 +- .../samples/raw-mustaches/output.json | 14 +- .../parser-legacy/samples/refs/output.json | 6 +- .../output.json | 12 +- .../output.json | 4 +- .../parser-legacy/samples/script/output.json | 12 +- .../samples/self-closing-element/output.json | 4 +- .../samples/self-reference/output.json | 12 +- .../samples/slotted-element/output.json | 8 +- .../space-between-mustaches/output.json | 18 +- .../parser-legacy/samples/spread/output.json | 6 +- .../samples/style-inside-head/output.json | 10 +- .../samples/textarea-children/output.json | 6 +- .../samples/textarea-end-tag/output.json | 6 +- .../transition-intro-no-params/output.json | 6 +- .../samples/transition-intro/output.json | 6 +- .../samples/unusual-identifier/output.json | 40 +- .../whitespace-after-script-tag/output.json | 12 +- .../whitespace-after-style-tag/output.json | 32 +- .../whitespace-leading-trailing/output.json | 8 +- .../samples/whitespace-normal/output.json | 16 +- .../main.svelte | 2 +- .../svelte/tests/runtime-legacy/shared.ts | 5 + .../samples/each-mutation/_config.js | 4 +- .../samples/each-mutation/main.svelte | 16 +- .../samples/effect-order/main.svelte | 13 +- .../samples/nullish-operator/_config.js | 2 +- .../samples/nullish-operator/main.svelte | 14 +- .../samples/proxy-array/_config.js | 66 ++ .../samples/proxy-array/main.svelte | 22 + .../samples/proxy-cyclical/_config.js | 12 + .../samples/proxy-cyclical/main.svelte | 8 + .../samples/proxy-deep/_config.js | 39 + .../samples/proxy-deep/main.svelte | 17 + .../samples/proxy-nested/_config.js | 12 + .../samples/proxy-nested/main.svelte | 15 + .../samples/proxy-prop-bound/Counter.svelte | 8 + .../samples/proxy-prop-bound/_config.js | 21 + .../samples/proxy-prop-bound/main.svelte | 9 + .../proxy-prop-readonly/Counter.svelte | 8 + .../samples/proxy-prop-readonly/_config.js | 18 + .../samples/proxy-prop-readonly/main.svelte | 7 + .../runtime-runes/samples/proxy/_config.js | 12 + .../runtime-runes/samples/proxy/main.svelte | 7 + packages/svelte/tests/signals/test.ts | 5 +- .../_expected/client/index.svelte.js | 2 +- .../_expected/client/main.svelte.js | 18 +- .../_expected/client/index.svelte.js | 4 + .../_expected/server/index.svelte.js | 5 + .../samples/export-state/index.svelte.js | 3 + .../_expected/client/index.svelte.js | 6 +- .../_expected/server/index.svelte.js | 2 +- .../_expected/client/index.svelte.js | 2 +- .../_expected/server/index.svelte.js | 2 +- .../runes-state-rune-not-mutated/_config.js | 3 - .../runes-state-rune-not-mutated/input.svelte | 10 - .../warnings.json | 14 - .../routes/docs/content/01-api/02-runes.md | 22 + .../02-examples/02-fine-grained-reactivity.md | 26 +- .../content/03-appendix/03-deprecations.md | 4 + 144 files changed, 2297 insertions(+), 1550 deletions(-) create mode 100644 packages/svelte/src/internal/client/each.js create mode 100644 packages/svelte/src/internal/client/proxy/proxy.js create mode 100644 packages/svelte/src/internal/client/proxy/readonly.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-state/_config.js create mode 100644 packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/proxy/main.svelte create mode 100644 packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/export-state/index.svelte.js delete mode 100644 packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/_config.js delete mode 100644 packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte delete mode 100644 packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index a4000fb111..2a2d969475 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -167,8 +167,8 @@ const runes = { 'invalid-legacy-export': () => `Cannot use \`export let\` in runes mode — use $props instead`, /** @param {string} rune */ 'invalid-rune-usage': (rune) => `Cannot use ${rune} rune in non-runes mode`, - /** @param {string} rune */ - 'invalid-rune-export': (rune) => `Cannot export value created with ${rune}`, + 'invalid-state-export': () => `Cannot export state if it is reassigned`, + 'invalid-derived-export': () => `Cannot export derived state`, 'invalid-props-id': () => `$props() can only be used with an object destructuring pattern`, 'invalid-props-pattern': () => `$props() assignment must not contain nested properties or computed keys`, diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index 517827f7e3..1be80cc169 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -183,7 +183,7 @@ function get_delegated_event(node, context) { // or any normal not reactive bindings that are mutated. binding.kind === 'normal' || // or any reactive imports (those are rewritten) (can only happen in legacy mode) - (binding.kind === 'state' && binding.declaration_kind === 'import')) && + binding.kind === 'legacy_reactive_import') && binding.mutated)) ) { return non_hoistable; @@ -221,20 +221,13 @@ export function analyze_module(ast, options) { merge(set_scope(scopes), validation_runes_js, runes_scope_js_tweaker) ); - // If we are in runes mode, then check for possible misuses of state runes - for (const [, scope] of scopes) { - for (const [name, binding] of scope.declarations) { - if (binding.kind === 'state' && !binding.mutated) { - warn(warnings, binding.node, [], 'state-not-mutated', name); - } - } - } - return { module: { ast, scope, scopes }, name: options.filename || 'module', warnings, - accessors: false + accessors: false, + runes: true, + immutable: true }; } @@ -315,6 +308,10 @@ export function analyze_component(root, options) { const component_name = get_component_name(options.filename ?? 'Component'); + const runes = + options.runes ?? + Array.from(module.scope.references).some(([name]) => Runes.includes(/** @type {any} */ (name))); + // TODO remove all the ?? stuff, we don't need it now that we're validating the config /** @type {import('../types.js').ComponentAnalysis} */ const analysis = { @@ -331,11 +328,8 @@ export function analyze_component(root, options) { component_name, get_css_hash: options.cssHash }), - runes: - options.runes ?? - Array.from(module.scope.references).some(([name]) => - Runes.includes(/** @type {any} */ (name)) - ), + runes, + immutable: runes || options.immutable, exports: [], uses_props: false, uses_rest_props: false, @@ -382,15 +376,6 @@ export function analyze_component(root, options) { merge(set_scope(scopes), validation_runes, runes_scope_tweaker, common_visitors) ); } - - // If we are in runes mode, then check for possible misuses of state runes - for (const [, scope] of instance.scopes) { - for (const [name, binding] of scope.declarations) { - if (binding.kind === 'state' && !binding.mutated) { - warn(warnings, binding.node, [], 'state-not-mutated', name); - } - } - } } else { instance.scope.declare(b.id('$$props'), 'prop', 'synthetic'); instance.scope.declare(b.id('$$restProps'), 'rest_prop', 'synthetic'); @@ -553,7 +538,7 @@ const legacy_scope_tweaker = { (state.reactive_statement || state.ast_type === 'template') && parent.type === 'MemberExpression' ) { - binding.kind = 'state'; + binding.kind = 'legacy_reactive_import'; } } else if ( binding.mutated && diff --git a/packages/svelte/src/compiler/phases/2-analyze/validation.js b/packages/svelte/src/compiler/phases/2-analyze/validation.js index 60aaf060f3..2323282893 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/validation.js +++ b/packages/svelte/src/compiler/phases/2-analyze/validation.js @@ -649,8 +649,14 @@ export const validation_legacy = merge(validation, a11y_validators, { */ function validate_export(node, scope, name) { const binding = scope.get(name); - if (binding && (binding.kind === 'derived' || binding.kind === 'state')) { - error(node, 'invalid-rune-export', `$${binding.kind}`); + if (!binding) return; + + if (binding.kind === 'derived') { + error(node, 'invalid-derived-export'); + } + + if (binding.kind === 'state' && binding.reassigned) { + error(node, 'invalid-state-export'); } } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 1afe2058b6..d69b70ec08 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -159,7 +159,7 @@ export function client_component(source, analysis, options) { // Very very dirty way of making import statements reactive in legacy mode if needed if (!analysis.runes) { for (const [name, binding] of analysis.module.scope.declarations) { - if (binding.kind === 'state' && binding.declaration_kind === 'import') { + if (binding.kind === 'legacy_reactive_import') { instance.body.unshift( b.var('$$_import_' + name, b.call('$.reactive_import', b.thunk(b.id(name)))) ); @@ -175,7 +175,7 @@ export function client_component(source, analysis, options) { for (const [name, binding] of analysis.instance.scope.declarations) { if (binding.kind === 'legacy_reactive') { - legacy_reactive_declarations.push(b.const(name, b.call('$.source'))); + legacy_reactive_declarations.push(b.const(name, b.call('$.mutable_source'))); } if (binding.kind === 'store_sub') { if (store_setup.length === 0) { @@ -240,11 +240,12 @@ export function client_component(source, analysis, options) { const properties = analysis.exports.map(({ name, alias }) => { const binding = analysis.instance.scope.get(name); + const is_source = + binding?.kind === 'state' && (!state.analysis.immutable || binding.reassigned); + // TODO This is always a getter because the `renamed-instance-exports` test wants it that way. // Should we for code size reasons make it an init in runes mode and/or non-dev mode? - return b.get(alias ?? name, [ - b.return(binding?.kind === 'state' ? b.call('$.get', b.id(name)) : b.id(name)) - ]); + return b.get(alias ?? name, [b.return(is_source ? b.call('$.get', b.id(name)) : b.id(name))]); }); if (analysis.accessors) { @@ -261,14 +262,7 @@ export function client_component(source, analysis, options) { } const component_block = b.block([ - b.stmt( - b.call( - '$.push', - b.id('$$props'), - b.literal(analysis.runes), - ...(options.immutable ? [b.literal(true)] : []) - ) - ), + b.stmt(b.call('$.push', b.id('$$props'), b.literal(analysis.runes))), ...store_setup, ...legacy_reactive_declarations, ...group_binding_declarations, diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js index 05633632c1..d6700add11 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js @@ -1,5 +1,5 @@ import * as b from '../../../utils/builders.js'; -import { extract_paths } from '../../../utils/ast.js'; +import { extract_paths, is_simple_expression } from '../../../utils/ast.js'; import { error } from '../../../errors.js'; /** @@ -67,19 +67,20 @@ export function serialize_get_binding(node, state) { if ( binding.kind === 'prop' && - !binding.mutated && + !(state.analysis.immutable ? binding.reassigned : binding.mutated) && !binding.initial && !state.analysis.accessors ) { return b.call(node); } - if (binding.kind === 'state' && binding.declaration_kind === 'import') { + if (binding.kind === 'legacy_reactive_import') { return b.call('$$_import_' + node.name); } if ( - binding.kind === 'state' || + (binding.kind === 'state' && + (!state.analysis.immutable || state.analysis.accessors || binding.reassigned)) || binding.kind === 'derived' || binding.kind === 'prop' || binding.kind === 'rest_prop' || @@ -99,7 +100,7 @@ export function serialize_get_binding(node, state) { * @returns {import('estree').Expression} */ export function serialize_set_binding(node, context, fallback) { - const { state, visit, path } = context; + const { state, visit } = context; if ( node.left.type === 'ArrayPattern' || @@ -152,7 +153,11 @@ export function serialize_set_binding(node, context, fallback) { if (left.object.type === 'ThisExpression' && left.property.type === 'PrivateIdentifier') { if (context.state.private_state.has(left.property.name) && !state.in_constructor) { const value = get_assignment_value(node, context); - return b.call('$.set', left, value); + return b.call( + '$.set', + left, + context.state.analysis.runes && should_proxy(value) ? b.call('$.proxy', value) : value + ); } } // @ts-expect-error @@ -171,7 +176,7 @@ export function serialize_set_binding(node, context, fallback) { return binding.mutation(node, context); } - if (binding.kind === 'state' && binding.declaration_kind === 'import') { + if (binding.kind === 'legacy_reactive_import') { return b.call( '$$_import_' + binding.node.name, b.assignment( @@ -202,7 +207,11 @@ export function serialize_set_binding(node, context, fallback) { if (is_store) { return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value); } else { - return b.call('$.set', b.id(left_name), value); + return b.call( + '$.set', + b.id(left_name), + context.state.analysis.runes && should_proxy(value) ? b.call('$.proxy', value) : value + ); } } else { if (is_store) { @@ -216,7 +225,7 @@ export function serialize_set_binding(node, context, fallback) { ), b.call('$' + left_name) ); - } else { + } else if (!state.analysis.runes) { return b.call( '$.mutate', b.id(left_name), @@ -226,6 +235,12 @@ export function serialize_set_binding(node, context, fallback) { value ) ); + } else { + return b.assignment( + node.operator, + /** @type {import('estree').Pattern} */ (visit(node.left)), + /** @type {import('estree').Expression} */ (visit(node.right)) + ); } } }; @@ -335,24 +350,38 @@ export function get_props_method(binding, state, name, default_value) { /** @type {import('estree').Expression[]} */ const args = [b.id('$$props'), b.literal(name)]; + // Use $.prop_source in the following cases: + // - accessors/mutated: needs to be able to set the prop value from within + // - default value: we set the fallback value only initially, and it's not possible to know this timing in $.prop + const needs_source = + default_value || + state.analysis.accessors || + (state.analysis.immutable ? binding.reassigned : binding.mutated); + + if (needs_source) { + args.push(b.literal(state.analysis.immutable)); + } + if (default_value) { // To avoid eagerly evaluating the right-hand-side, we wrap it in a thunk if necessary - if (default_value.type !== 'Literal' && default_value.type !== 'Identifier') { - args.push(b.thunk(default_value)); - args.push(b.true); - } else { + if (is_simple_expression(default_value)) { args.push(default_value); - args.push(b.false); + } else { + if ( + default_value.type === 'CallExpression' && + default_value.callee.type === 'Identifier' && + default_value.arguments.length === 0 + ) { + args.push(default_value.callee); + } else { + args.push(b.thunk(default_value)); + } + + args.push(b.true); } } - return b.call( - // Use $.prop_source in the following cases: - // - accessors/mutated: needs to be able to set the prop value from within - // - default value: we set the fallback value only initially, and it's not possible to know this timing in $.prop - binding.mutated || binding.initial || state.analysis.accessors ? '$.prop_source' : '$.prop', - ...args - ); + return b.call(needs_source ? '$.prop_source' : '$.prop', ...args); } /** @@ -364,7 +393,7 @@ export function get_props_method(binding, state, name, default_value) { export function create_state_declarators(declarator, scope, value) { // in the simple `let count = $state(0)` case, we rewrite `$state` as `$.source` if (declarator.id.type === 'Identifier') { - return [b.declarator(declarator.id, b.call('$.source', value))]; + return [b.declarator(declarator.id, b.call('$.mutable_source', value))]; } const tmp = scope.generate('tmp'); @@ -374,7 +403,25 @@ export function create_state_declarators(declarator, scope, value) { ...paths.map((path) => { const value = path.expression?.(b.id(tmp)); const binding = scope.get(/** @type {import('estree').Identifier} */ (path.node).name); - return b.declarator(path.node, binding?.kind === 'state' ? b.call('$.source', value) : value); + return b.declarator( + path.node, + binding?.kind === 'state' ? b.call('$.mutable_source', value) : value + ); }) ]; } + +/** @param {import('estree').Expression} node */ +export function should_proxy(node) { + if ( + !node || + node.type === 'Literal' || + node.type === 'ArrowFunctionExpression' || + node.type === 'FunctionExpression' || + (node.type === 'Identifier' && node.name === 'undefined') + ) { + return false; + } + + return true; +} diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js index 5bb5216be6..01a6ec7df3 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js @@ -2,7 +2,7 @@ import { get_rune } from '../../../scope.js'; import { is_hoistable_function } from '../../utils.js'; import * as b from '../../../../utils/builders.js'; import * as assert from '../../../../utils/assert.js'; -import { create_state_declarators, get_props_method } from '../utils.js'; +import { create_state_declarators, get_props_method, should_proxy } from '../utils.js'; import { unwrap_ts_expression } from '../../../../utils/ast.js'; /** @type {import('../types.js').ComponentVisitors} */ @@ -84,7 +84,7 @@ export const javascript_visitors_runes = { value = field.kind === 'state' - ? b.call('$.source', init) + ? b.call('$.source', should_proxy(init) ? b.call('$.proxy', init) : init) : b.call('$.derived', b.thunk(init)); } else { // if no arguments, we know it's state as `$derived()` is a compile error @@ -211,16 +211,28 @@ export const javascript_visitors_runes = { } const args = /** @type {import('estree').CallExpression} */ (init).arguments; - const value = + let value = args.length === 0 ? b.id('undefined') : /** @type {import('estree').Expression} */ (visit(args[0])); - const opts = args[1] && /** @type {import('estree').Expression} */ (visit(args[1])); if (declarator.id.type === 'Identifier') { - const callee = rune === '$state' ? '$.source' : '$.derived'; - const arg = rune === '$state' ? value : b.thunk(value); - declarations.push(b.declarator(declarator.id, b.call(callee, arg, opts))); + if (rune === '$state') { + const binding = /** @type {import('#compiler').Binding} */ ( + state.scope.get(declarator.id.name) + ); + if (should_proxy(value)) { + value = b.call('$.proxy', value); + } + + if (!state.analysis.immutable || state.analysis.accessors || binding.reassigned) { + value = b.call('$.source', value); + } + } else { + value = b.call('$.derived', b.thunk(value)); + } + + declarations.push(b.declarator(declarator.id, value)); continue; } diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index e619f25247..feb9174187 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -27,6 +27,7 @@ import { DOMBooleanAttributes, EACH_INDEX_REACTIVE, EACH_IS_CONTROLLED, + EACH_IS_IMMUTABLE, EACH_ITEM_REACTIVE, EACH_KEYED } from '../../../../../constants.js'; @@ -767,18 +768,23 @@ function serialize_inline_component(node, component_name, context) { const [, value] = serialize_attribute_value(attribute.value, context); if (attribute.metadata.dynamic) { + let arg = value; + const contains_call_expression = Array.isArray(attribute.value) && attribute.value.some((n) => { return n.type === 'ExpressionTag' && n.metadata.contains_call_expression; }); + if (contains_call_expression) { const id = b.id(context.state.scope.generate(attribute.name)); context.state.init.push(b.var(id, b.call('$.derived', b.thunk(value)))); - push_prop(b.get(attribute.name, [b.return(b.call('$.get', id))])); - } else { - push_prop(b.get(attribute.name, [b.return(value)])); + arg = b.call('$.get', id); } + + if (context.state.options.dev) arg = b.call('$.readonly', arg); + + push_prop(b.get(attribute.name, [b.return(arg)])); } else { push_prop(b.init(attribute.name, value)); } @@ -2128,14 +2134,13 @@ export const template_visitors = { // array or use nested reactivity through runes. // TODO this feels a bit "hidden performance boost"-style, investigate if there's a way // to make this apply in more cases - /** @type {number} */ - let each_type; + let each_type = 0; if ( node.key && (node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index) ) { - each_type = EACH_KEYED; + each_type |= EACH_KEYED; if ( node.key.type === 'Identifier' && node.context.type === 'Identifier' && @@ -2152,12 +2157,17 @@ export const template_visitors = { each_type |= EACH_INDEX_REACTIVE; } } else { - each_type = EACH_ITEM_REACTIVE; + each_type |= EACH_ITEM_REACTIVE; } + if (each_node_meta.is_controlled) { each_type |= EACH_IS_CONTROLLED; } + if (context.state.analysis.immutable) { + each_type |= EACH_IS_IMMUTABLE; + } + // Find the parent each blocks which contain the arrays to invalidate // TODO decide how much of this we want to keep for runes mode. For now we're bailing out below const indirect_dependencies = collect_parent_each_blocks(context).flatMap((block) => { diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 0bbd094498..798e58ed10 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -2094,7 +2094,7 @@ export function server_component(analysis, options) { } const component_block = b.block([ - b.stmt(b.call('$.push', b.literal(analysis.runes), ...(options.immutable ? [b.true] : []))), + b.stmt(b.call('$.push', b.literal(analysis.runes))), .../** @type {import('estree').Statement[]} */ (instance.body), .../** @type {import('estree').Statement[]} */ (template.body), b.stmt(b.call('$.pop')) diff --git a/packages/svelte/src/compiler/phases/scope.js b/packages/svelte/src/compiler/phases/scope.js index 60e29e61cb..09b3e8934c 100644 --- a/packages/svelte/src/compiler/phases/scope.js +++ b/packages/svelte/src/compiler/phases/scope.js @@ -105,7 +105,8 @@ export class Scope { is_called: false, prop_alias: null, expression: null, - mutation: null + mutation: null, + reassigned: false }; this.declarations.set(node.name, binding); this.root.conflicts.add(node.name); @@ -632,7 +633,10 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) { } else { extract_identifiers(node).forEach((identifier) => { const binding = scope.get(identifier.name); - if (binding) binding.mutated = true; + if (binding) { + binding.mutated = true; + binding.reassigned = true; + } }); } } diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 8ee2cce170..f2f9868e13 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -46,6 +46,8 @@ export interface Analysis { module: Js; name: string; // TODO should this be filename? it's used in `compileModule` as well as `compile` warnings: RawWarning[]; + runes: boolean; + immutable: boolean; // TODO figure out if we can move this to ComponentAnalysis accessors: boolean; diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 04b4a9d1c3..6435c20207 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -251,6 +251,7 @@ export interface Binding { * - `each`: An each block context variable * - `store_sub`: A $store value * - `legacy_reactive`: A `$:` declaration + * - `legacy_reactive_import`: An imported binding that is mutated inside the component */ kind: | 'normal' @@ -260,7 +261,8 @@ export interface Binding { | 'derived' | 'each' | 'store_sub' - | 'legacy_reactive'; + | 'legacy_reactive' + | 'legacy_reactive_import'; declaration_kind: DeclarationKind; /** * What the value was initialized with. @@ -270,6 +272,7 @@ export interface Binding { is_called: boolean; references: { node: Identifier; path: SvelteNode[] }[]; mutated: boolean; + reassigned: boolean; scope: Scope; /** For `legacy_reactive`: its reactive dependencies */ legacy_dependencies: Binding[]; diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js index 28a56d7c4a..dd9bbfc17a 100644 --- a/packages/svelte/src/compiler/utils/ast.js +++ b/packages/svelte/src/compiler/utils/ast.js @@ -304,3 +304,36 @@ export function get_parent(path, at) { } return /** @type {T} */ (node); } + +/** + * Returns `true` if the expression is an identifier, a literal, a function expression, + * or a logical expression that only contains simple expressions. Used to determine whether + * something needs to be treated as though accessing it could have side-effects (i.e. + * reading signals prematurely) + * @param {import('estree').Expression} node + * @returns {boolean} + */ +export function is_simple_expression(node) { + if ( + node.type === 'Literal' || + node.type === 'Identifier' || + node.type === 'ArrowFunctionExpression' || + node.type === 'FunctionExpression' + ) { + return true; + } + + if (node.type === 'ConditionalExpression') { + return ( + is_simple_expression(node.test) && + is_simple_expression(node.consequent) && + is_simple_expression(node.alternate) + ); + } + + if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { + return is_simple_expression(node.left) && is_simple_expression(node.right); + } + + return false; +} diff --git a/packages/svelte/src/compiler/validate-options.js b/packages/svelte/src/compiler/validate-options.js index e928e9bac7..5752435178 100644 --- a/packages/svelte/src/compiler/validate-options.js +++ b/packages/svelte/src/compiler/validate-options.js @@ -72,7 +72,10 @@ export const validate_component_options = discloseVersion: boolean(true), - immutable: boolean(false), + immutable: deprecate( + 'The immutable option has been deprecated. It has no effect in runes mode.', + boolean(false) + ), legacy: object({ componentApi: boolean(false) @@ -166,6 +169,18 @@ function warn_removed(message) { }; } +/** + * @param {string} message + * @param {Validator} validator + * @returns {Validator} + */ +function deprecate(message, validator) { + return (input, keypath) => { + if (input !== undefined) warn(message); + return validator(input, keypath); + }; +} + /** * @param {Record} children * @param {boolean} [allow_unknown] diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index f9e74786e3..67c2dc333b 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -22,9 +22,6 @@ const runes = { `It looks like you're using the $${name} rune, but there is a local binding called ${name}. ` + `Referencing a local variable with a $ prefix will create a store subscription. Please rename ${name} to avoid the ambiguity.`, /** @param {string} name */ - 'state-not-mutated': (name) => - `${name} is declared with $state(...) but is never updated. Did you mean to create a function that changes its value?`, - /** @param {string} name */ 'non-state-reference': (name) => `${name} is updated, but is not declared with $state(...). Changing its value will not correctly trigger updates.` }; diff --git a/packages/svelte/src/constants.js b/packages/svelte/src/constants.js index a8abda2237..6b7cc123f5 100644 --- a/packages/svelte/src/constants.js +++ b/packages/svelte/src/constants.js @@ -3,6 +3,7 @@ export const EACH_INDEX_REACTIVE = 1 << 1; export const EACH_KEYED = 1 << 2; export const EACH_IS_CONTROLLED = 1 << 3; export const EACH_IS_ANIMATED = 1 << 4; +export const EACH_IS_IMMUTABLE = 1 << 6; /** List of Element events that will be delegated */ export const DelegatedEvents = [ diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js new file mode 100644 index 0000000000..f3306ab3a0 --- /dev/null +++ b/packages/svelte/src/internal/client/each.js @@ -0,0 +1,803 @@ +import { + EACH_INDEX_REACTIVE, + EACH_IS_ANIMATED, + EACH_IS_CONTROLLED, + EACH_IS_IMMUTABLE, + EACH_ITEM_REACTIVE, + EACH_KEYED +} from '../../constants.js'; +import { create_each_block, create_each_item_block } from './block.js'; +import { + current_hydration_fragment, + get_hydration_fragment, + hydrate_block_anchor, + set_current_hydration_fragment +} from './hydration.js'; +import { clear_text_content, map_get, map_set } from './operations.js'; +import { STATE_SYMBOL } from './proxy/proxy.js'; +import { insert, remove } from './reconciler.js'; +import { empty } from './render.js'; +import { + destroy_signal, + execute_effect, + is_lazy_property, + lazy_property, + mutable_source, + push_destroy_fn, + render_effect, + schedule_task, + set_signal_value, + source +} from './runtime.js'; +import { trigger_transitions } from './transitions.js'; +import { is_array } from './utils.js'; + +const NEW_BLOCK = -1; +const MOVED_BLOCK = 99999999; +const LIS_BLOCK = -2; + +function no_op() {} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn + * @returns {void} + */ +function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const block = create_each_block(flags, anchor_node); + + /** @type {null | import('./types.js').Render} */ + let current_fallback = null; + hydrate_block_anchor(anchor_node, is_controlled); + + /** @type {V[]} */ + let array; + + /** @type {Array | null} */ + let keys = null; + + /** @type {null | import('./types.js').EffectSignal} */ + let render = null; + block.r = + /** @param {import('./types.js').Transition} transition */ + (transition) => { + const fallback = /** @type {import('./types.js').Render} */ (current_fallback); + const transitions = fallback.s; + transitions.add(transition); + transition.f(() => { + transitions.delete(transition); + if (transitions.size === 0) { + if (fallback.e !== null) { + if (fallback.d !== null) { + remove(fallback.d); + fallback.d = null; + } + destroy_signal(fallback.e); + fallback.e = null; + } + } + }); + }; + + const create_fallback_effect = () => { + /** @type {import('./types.js').Render} */ + const fallback = { + d: null, + e: null, + s: new Set(), + p: current_fallback + }; + // Managed effect + const effect = render_effect( + () => { + const dom = block.d; + if (dom !== null) { + remove(dom); + block.d = null; + } + let anchor = block.a; + const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0; + if (is_controlled) { + anchor = empty(); + block.a.appendChild(anchor); + } + /** @type {(anchor: Node) => void} */ (fallback_fn)(anchor); + fallback.d = block.d; + block.d = null; + }, + block, + true + ); + fallback.e = effect; + current_fallback = fallback; + }; + + const each = render_effect( + () => { + /** @type {V[]} */ + const maybe_array = collection(); + array = is_array(maybe_array) + ? maybe_array + : maybe_array == null + ? [] + : Array.from(maybe_array); + if (key_fn !== null) { + keys = array.map(key_fn); + } else if ((flags & EACH_KEYED) === 0) { + array.map(no_op); + } + const length = array.length; + if (fallback_fn !== null) { + if (length === 0) { + if (block.v.length !== 0 || render === null) { + create_fallback_effect(); + } + } else if (block.v.length === 0 && current_fallback !== null) { + const fallback = current_fallback; + const transitions = fallback.s; + if (transitions.size === 0) { + if (fallback.d !== null) { + remove(fallback.d); + fallback.d = null; + } + } else { + trigger_transitions(transitions, 'out'); + } + } + } + if (render !== null) { + execute_effect(render); + } + }, + block, + false + ); + + render = render_effect( + /** @param {import('./types.js').EachBlock} block */ + (block) => { + const flags = block.f; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + const anchor_node = block.a; + reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); + }, + block, + true + ); + + push_destroy_fn(each, () => { + const flags = block.f; + const anchor_node = block.a; + const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; + let fallback = current_fallback; + while (fallback !== null) { + const dom = fallback.d; + if (dom !== null) { + remove(dom); + } + const effect = fallback.e; + if (effect !== null) { + destroy_signal(effect); + } + fallback = fallback.p; + } + // Clear the array + reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); + }); + + block.e = each; +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {null | ((item: V) => string)} key_fn + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { + each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); +} + +/** + * @template V + * @param {Element | Comment} anchor_node + * @param {() => V[]} collection + * @param {number} flags + * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn + * @param {null | ((anchor: Node) => void)} fallback_fn + * @returns {void} + */ +export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { + each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); +} + +/** + * @template V + * @param {Array} array + * @param {import('./types.js').EachBlock} each_block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn + * @param {number} flags + * @param {boolean} apply_transitions + * @returns {void} + */ +function reconcile_indexed_array( + array, + each_block, + dom, + is_controlled, + render_fn, + flags, + apply_transitions +) { + var is_proxied_array = STATE_SYMBOL in array; + var a_blocks = each_block.v; + var active_transitions = each_block.s; + + if (is_proxied_array) { + flags &= ~EACH_ITEM_REACTIVE; + } + + /** @type {number | void} */ + var a = a_blocks.length; + + /** @type {number} */ + var b = array.length; + var length = Math.max(a, b); + var index = 0; + + /** @type {Array} */ + var b_blocks; + var block; + + if (active_transitions.length !== 0) { + destroy_active_transition_blocks(active_transitions); + } + + if (b === 0) { + b_blocks = []; + // Remove old blocks + if (is_controlled && a !== 0) { + clear_text_content(dom); + } + while (index < length) { + block = a_blocks[index++]; + destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); + } + } else { + var item; + b_blocks = Array(b); + if (current_hydration_fragment !== null) { + /** @type {Node} */ + var hydrating_node = current_hydration_fragment[0]; + for (; index < length; index++) { + // Hydrate block + item = is_proxied_array ? lazy_property(array, index) : array[index]; + var fragment = /** @type {Array} */ ( + get_hydration_fragment(hydrating_node) + ); + set_current_hydration_fragment(fragment); + hydrating_node = /** @type {Node} */ ( + /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling + ); + block = each_item_block(item, null, index, render_fn, flags); + b_blocks[index] = block; + } + } else { + for (; index < length; index++) { + if (index >= a) { + // Add block + item = is_proxied_array ? lazy_property(array, index) : array[index]; + block = each_item_block(item, null, index, render_fn, flags); + b_blocks[index] = block; + insert_each_item_block(block, dom, is_controlled, null); + } else if (index >= b) { + // Remove block + block = a_blocks[index]; + destroy_each_item_block(block, active_transitions, apply_transitions); + } else { + // Update block + item = array[index]; + block = a_blocks[index]; + b_blocks[index] = block; + update_each_item_block(block, item, index, flags); + } + } + } + } + + each_block.v = b_blocks; +} +// Reconcile arrays by the equality of the elements in the array. This algorithm +// is based on Ivi's reconcilation logic: +// +// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 +// + +/** + * @template V + * @param {Array} array + * @param {import('./types.js').EachBlock} each_block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn + * @param {number} flags + * @param {boolean} apply_transitions + * @param {Array | null} keys + * @returns {void} + */ +function reconcile_tracked_array( + array, + each_block, + dom, + is_controlled, + render_fn, + flags, + apply_transitions, + keys +) { + var a_blocks = each_block.v; + const is_computed_key = keys !== null; + var is_proxied_array = STATE_SYMBOL in array; + var active_transitions = each_block.s; + + if (is_proxied_array) { + flags &= ~EACH_ITEM_REACTIVE; + } + + /** @type {number | void} */ + var a = a_blocks.length; + + /** @type {number} */ + var b = array.length; + + /** @type {Array} */ + var b_blocks; + var block; + + if (active_transitions.length !== 0) { + destroy_active_transition_blocks(active_transitions); + } + + if (b === 0) { + b_blocks = []; + // Remove old blocks + if (is_controlled && a !== 0) { + clear_text_content(dom); + } + while (a > 0) { + block = a_blocks[--a]; + destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); + } + } else { + var a_end = a - 1; + var b_end = b - 1; + var key; + var item; + var idx; + b_blocks = Array(b); + if (current_hydration_fragment !== null) { + var fragment; + + /** @type {Node} */ + var hydrating_node = current_hydration_fragment[0]; + while (b > 0) { + // Hydrate block + idx = b_end - --b; + item = array[idx]; + key = is_computed_key ? keys[idx] : item; + fragment = /** @type {Array} */ ( + get_hydration_fragment(hydrating_node) + ); + set_current_hydration_fragment(fragment); + // Get the tag of the next item in the list + // The fragment array can be empty if each block has no content + hydrating_node = /** @type {Node} */ ( + /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling + ); + block = each_item_block(item, key, idx, render_fn, flags); + b_blocks[idx] = block; + } + } else if (a === 0) { + // Create new blocks + while (b > 0) { + idx = b_end - --b; + item = array[idx]; + key = is_computed_key ? keys[idx] : item; + block = each_item_block(item, key, idx, render_fn, flags); + b_blocks[idx] = block; + insert_each_item_block(block, dom, is_controlled, null); + } + } else { + var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; + var start = 0; + + /** @type {null | Text | Element | Comment} */ + var sibling = null; + item = array[b_end]; + key = is_computed_key ? keys[b_end] : item; + // Step 1 + outer: while (true) { + // From the end + while (a_blocks[a_end].k === key) { + block = a_blocks[a_end--]; + item = array[b_end]; + if (should_update_block) { + update_each_item_block(block, item, b_end, flags); + } + sibling = get_first_child(block); + b_blocks[b_end] = block; + if (start > --b_end || start > a_end) { + break outer; + } + key = is_computed_key ? keys[b_end] : item; + } + item = array[start]; + key = is_computed_key ? keys[start] : item; + // At the start + while (start <= a_end && start <= b_end && a_blocks[start].k === key) { + item = array[start]; + block = a_blocks[start]; + if (should_update_block) { + update_each_item_block(block, item, start, flags); + } + b_blocks[start] = block; + ++start; + key = is_computed_key ? keys[start] : array[start]; + } + break; + } + // Step 2 + if (start > a_end) { + while (b_end >= start) { + item = array[b_end]; + key = is_computed_key ? keys[b_end] : item; + block = each_item_block(item, key, b_end, render_fn, flags); + b_blocks[b_end--] = block; + sibling = insert_each_item_block(block, dom, is_controlled, sibling); + } + } else if (start > b_end) { + b = start; + do { + if ((block = a_blocks[b++]) !== null) { + destroy_each_item_block(block, active_transitions, apply_transitions); + } + } while (b <= a_end); + } else { + // Step 3 + var pos = 0; + var b_length = b_end - start + 1; + var sources = new Int32Array(b_length); + var item_index = new Map(); + for (b = 0; b < b_length; ++b) { + a = b + start; + sources[b] = NEW_BLOCK; + item = array[a]; + key = is_computed_key ? keys[a] : item; + map_set(item_index, key, a); + } + for (b = start; b <= a_end; ++b) { + a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); + block = a_blocks[b]; + if (a !== undefined) { + pos = pos < a ? a : MOVED_BLOCK; + sources[a - start] = b; + b_blocks[a] = block; + } else if (block !== null) { + destroy_each_item_block(block, active_transitions, apply_transitions); + } + } + // Step 4 + if (pos === MOVED_BLOCK) { + mark_lis(sources); + } + // If keys are animated, we need to do updates before actual moves + var is_animated = (flags & EACH_IS_ANIMATED) !== 0; + var should_create; + if (is_animated) { + var i = b_length; + while (i-- > 0) { + b_end = i + start; + a = sources[i]; + if (pos === MOVED_BLOCK && a !== LIS_BLOCK) { + block = b_blocks[b_end]; + update_each_item_block(block, item, b_end, flags); + } + } + } + var last_block; + var last_sibling; + while (b_length-- > 0) { + b_end = b_length + start; + a = sources[b_length]; + should_create = a === -1; + item = array[b_end]; + if (should_create) { + key = is_computed_key ? keys[b_end] : item; + block = each_item_block(item, key, b_end, render_fn, flags); + } else { + block = b_blocks[b_end]; + if (!is_animated && should_update_block) { + update_each_item_block(block, item, b_end, flags); + } + } + if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) { + last_sibling = last_block === undefined ? sibling : get_first_child(last_block); + sibling = insert_each_item_block(block, dom, is_controlled, last_sibling); + } + b_blocks[b_end] = block; + last_block = block; + } + } + } + } + + each_block.v = b_blocks; +} + +/** + * Longest Increased Subsequence algorithm + * @param {Int32Array} a + * @returns {void} + */ +function mark_lis(a) { + var length = a.length; + var parent = new Int32Array(length); + var index = new Int32Array(length); + var index_length = 0; + var i = 0; + + /** @type {number} */ + var j; + + /** @type {number} */ + var k; + + /** @type {number} */ + var lo; + + /** @type {number} */ + var hi; + + // Skip -1 values at the start of the input array `a`. + for (; a[i] === NEW_BLOCK; ++i) { + /**/ + } + + index[0] = i++; + + for (; i < length; ++i) { + k = a[i]; + + if (k !== NEW_BLOCK) { + // Ignore -1 values. + j = index[index_length]; + + if (a[j] < k) { + parent[i] = j; + index[++index_length] = i; + } else { + lo = 0; + hi = index_length; + + while (lo < hi) { + j = (lo + hi) >> 1; + if (a[index[j]] < k) { + lo = j + 1; + } else { + hi = j; + } + } + + if (k < a[index[lo]]) { + if (lo > 0) { + parent[i] = index[lo - 1]; + } + index[lo] = i; + } + } + } + } + + // Mutate input array `a` and assign -2 value to all nodes that are part of LIS. + j = index[index_length]; + + while (index_length-- >= 0) { + a[j] = LIS_BLOCK; + j = parent[j]; + } +} + +/** + * @param {import('./types.js').Block} block + * @param {Element | Comment | Text} dom + * @param {boolean} is_controlled + * @param {null | Text | Element | Comment} sibling + * @returns {Text | Element | Comment} + */ +function insert_each_item_block(block, dom, is_controlled, sibling) { + var current = /** @type {import('./types.js').TemplateNode} */ (block.d); + + if (sibling === null) { + if (is_controlled) { + return insert(current, /** @type {Element} */ (dom), null); + } else { + return insert(current, /** @type {Element} */ (dom.parentNode), dom); + } + } + + return insert(current, null, sibling); +} + +/** + * @param {import('./types.js').Block} block + * @returns {Text | Element | Comment} + */ +function get_first_child(block) { + var current = block.d; + + if (is_array(current)) { + return /** @type {Text | Element | Comment} */ (current[0]); + } + + return /** @type {Text | Element | Comment} */ (current); +} + +/** + * @param {Array} active_transitions + * @returns {void} + */ +function destroy_active_transition_blocks(active_transitions) { + var length = active_transitions.length; + + if (length > 0) { + var i = 0; + var block; + var transition; + + for (; i < length; i++) { + block = active_transitions[i]; + transition = block.r; + if (transition !== null) { + block.r = null; + destroy_each_item_block(block, null, false); + } + } + + active_transitions.length = 0; + } +} + +/** + * @param {import('./types.js').Block} block + * @returns {Text | Element | Comment} + */ +function get_first_element(block) { + const current = block.d; + + if (is_array(current)) { + for (let i = 0; i < current.length; i++) { + const node = current[i]; + if (node.nodeType !== 8) { + return node; + } + } + } + + return /** @type {Text | Element | Comment} */ (current); +} + +/** + * @param {import('./types.js').EachItemBlock} block + * @param {any} item + * @param {number} index + * @param {number} type + * @returns {void} + */ +function update_each_item_block(block, item, index, type) { + if ((type & EACH_ITEM_REACTIVE) !== 0) { + set_signal_value(block.v, item); + } else if (is_lazy_property(block.v)) { + block.v.o[block.v.p] = item; + } + const transitions = block.s; + const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0; + // Handle each item animations + if (transitions !== null && (type & EACH_KEYED) !== 0) { + let prev_index = block.i; + if (index_is_reactive) { + prev_index = /** @type {import('./types.js').Signal} */ (prev_index).v; + } + const items = block.p.v; + if (prev_index !== index && /** @type {number} */ (index) < items.length) { + const from_dom = /** @type {Element} */ (get_first_element(block)); + const from = from_dom.getBoundingClientRect(); + schedule_task(() => { + trigger_transitions(transitions, 'key', from); + }); + } + } + if (index_is_reactive) { + set_signal_value(/** @type {import('./types.js').Signal} */ (block.i), index); + } else { + block.i = index; + } +} + +/** + * @param {import('./types.js').EachItemBlock} block + * @param {null | Array} transition_block + * @param {boolean} apply_transitions + * @param {any} controlled + * @returns {void} + */ +export function destroy_each_item_block( + block, + transition_block, + apply_transitions, + controlled = false +) { + const transitions = block.s; + + if (apply_transitions && transitions !== null) { + trigger_transitions(transitions, 'out'); + if (transition_block !== null) { + transition_block.push(block); + } + } else { + const dom = block.d; + if (!controlled && dom !== null) { + remove(dom); + } + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); + } +} + +/** + * @template V + * @template O + * @template P + * @param {V | import('./types.js').LazyProperty} item + * @param {unknown} key + * @param {number} index + * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn + * @param {number} flags + * @returns {import('./types.js').EachItemBlock} + */ +function each_item_block(item, key, index, render_fn, flags) { + const each_item_not_reactive = (flags & EACH_ITEM_REACTIVE) === 0; + + const item_value = each_item_not_reactive + ? item + : (flags & EACH_IS_IMMUTABLE) === 0 + ? mutable_source(item) + : source(item); + + const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); + const block = create_each_item_block(item_value, index_value, key); + + const effect = render_effect( + /** @param {import('./types.js').EachItemBlock} block */ + (block) => { + render_fn(null, block.v, block.i); + }, + block, + true + ); + + block.e = effect; + return block; +} diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy/proxy.js new file mode 100644 index 0000000000..22e0b959b6 --- /dev/null +++ b/packages/svelte/src/internal/client/proxy/proxy.js @@ -0,0 +1,176 @@ +import { DEV } from 'esm-env'; +import { + effect_active, + get, + set, + increment, + source, + updating_derived, + UNINITIALIZED +} from '../runtime.js'; +import { define_property, get_descriptor, is_array } from '../utils.js'; +import { READONLY_SYMBOL } from './readonly.js'; + +/** @typedef {{ s: Map>; v: import('../types.js').SourceSignal; a: boolean }} Metadata */ +/** @typedef {Record & { [STATE_SYMBOL]: Metadata }} StateObject */ + +export const STATE_SYMBOL = Symbol('$state'); + +const object_prototype = Object.prototype; +const array_prototype = Array.prototype; +const get_prototype_of = Object.getPrototypeOf; +const is_frozen = Object.isFrozen; + +/** + * @template {StateObject} T + * @param {T} value + * @returns {T} + */ +export function proxy(value) { + if (typeof value === 'object' && value != null && !is_frozen(value) && !(STATE_SYMBOL in value)) { + const prototype = get_prototype_of(value); + + // TODO handle Map and Set as well + if (prototype === object_prototype || prototype === array_prototype) { + define_property(value, STATE_SYMBOL, { value: init(value), writable: false }); + + // @ts-expect-error not sure how to fix this + return new Proxy(value, handler); + } + } + + return value; +} + +/** + * @param {StateObject} value + * @returns {Metadata} + */ +function init(value) { + return { + s: new Map(), + v: source(0), + a: is_array(value) + }; +} + +/** @type {ProxyHandler} */ +const handler = { + defineProperty(target, prop, descriptor) { + if (descriptor.value) { + const metadata = target[STATE_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, proxy(descriptor.value)); + } + + return Reflect.defineProperty(target, prop, descriptor); + }, + + deleteProperty(target, prop) { + const metadata = target[STATE_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, UNINITIALIZED); + + if (prop in target) increment(metadata.v); + + return delete target[prop]; + }, + + get(target, prop, receiver) { + if (prop === READONLY_SYMBOL) return target[READONLY_SYMBOL]; + + const metadata = target[STATE_SYMBOL]; + let s = metadata.s.get(prop); + + // if we're reading a property in a reactive context, create a source, + // but only if it's an own property and not a prototype property + if ( + s === undefined && + (effect_active() || updating_derived) && + (!(prop in target) || get_descriptor(target, prop)?.writable) + ) { + s = source(proxy(target[prop])); + metadata.s.set(prop, s); + } + + const value = s !== undefined ? get(s) : Reflect.get(target, prop, receiver); + return value === UNINITIALIZED ? undefined : value; + }, + + getOwnPropertyDescriptor(target, prop) { + const descriptor = Reflect.getOwnPropertyDescriptor(target, prop); + if (descriptor && 'value' in descriptor) { + const metadata = target[STATE_SYMBOL]; + const s = metadata.s.get(prop); + + if (s) { + descriptor.value = get(s); + } + } + + return descriptor; + }, + + has(target, prop) { + if (prop === STATE_SYMBOL) { + return true; + } + const metadata = target[STATE_SYMBOL]; + const has = Reflect.has(target, prop); + + let s = metadata.s.get(prop); + if (s !== undefined || (effect_active() && (!has || get_descriptor(target, prop)?.writable))) { + if (s === undefined) { + s = source(has ? proxy(target[prop]) : UNINITIALIZED); + metadata.s.set(prop, s); + } + const value = get(s); + if (value === UNINITIALIZED) { + return false; + } + } + return has; + }, + + set(target, prop, value) { + if (prop === READONLY_SYMBOL) { + target[READONLY_SYMBOL] = value; + return true; + } + + const metadata = target[STATE_SYMBOL]; + + const s = metadata.s.get(prop); + if (s !== undefined) set(s, proxy(value)); + + if (metadata.a && prop === 'length') { + for (let i = value; i < target.length; i += 1) { + const s = metadata.s.get(i + ''); + if (s !== undefined) set(s, UNINITIALIZED); + } + } + + if (!(prop in target)) increment(metadata.v); + // @ts-ignore + target[prop] = value; + + return true; + }, + + ownKeys(target) { + const metadata = target[STATE_SYMBOL]; + + get(metadata.v); + return Reflect.ownKeys(target); + } +}; + +if (DEV) { + handler.setPrototypeOf = () => { + throw new Error('Cannot set prototype of $state object'); + }; +} + +export { readonly } from './readonly.js'; diff --git a/packages/svelte/src/internal/client/proxy/readonly.js b/packages/svelte/src/internal/client/proxy/readonly.js new file mode 100644 index 0000000000..fa698c3b08 --- /dev/null +++ b/packages/svelte/src/internal/client/proxy/readonly.js @@ -0,0 +1,65 @@ +import { define_property, get_descriptor } from '../utils.js'; + +/** + * @template {Record} T + * @typedef {T & { [READONLY_SYMBOL]: Proxy }} StateObject + */ + +export const READONLY_SYMBOL = Symbol('readonly'); + +const object_prototype = Object.prototype; +const array_prototype = Array.prototype; +const get_prototype_of = Object.getPrototypeOf; +const is_frozen = Object.isFrozen; + +/** + * @template {Record} T + * @template {StateObject} U + * @param {U} value + * @returns {Proxy | U} + */ +export function readonly(value) { + const proxy = value && value[READONLY_SYMBOL]; + if (proxy) return proxy; + + if ( + typeof value === 'object' && + value != null && + !is_frozen(value) && + !(READONLY_SYMBOL in value) + ) { + const prototype = get_prototype_of(value); + + // TODO handle Map and Set as well + if (prototype === object_prototype || prototype === array_prototype) { + const proxy = new Proxy(value, handler); + define_property(value, READONLY_SYMBOL, { value: proxy, writable: false }); + + return proxy; + } + } + + return value; +} + +/** @returns {never} */ +const readonly_error = () => { + throw new Error(`Props are read-only, unless used with \`bind:\``); +}; + +/** @type {ProxyHandler>} */ +const handler = { + defineProperty: readonly_error, + deleteProperty: readonly_error, + set: readonly_error, + + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + + if (!(prop in target)) { + return readonly(value); + } + + return value; + } +}; diff --git a/packages/svelte/src/internal/client/reconciler.js b/packages/svelte/src/internal/client/reconciler.js index a5fcff6873..c96d9460a9 100644 --- a/packages/svelte/src/internal/client/reconciler.js +++ b/packages/svelte/src/internal/client/reconciler.js @@ -1,17 +1,6 @@ -import { append_child, map_get, map_set, clear_text_content } from './operations.js'; -import { - current_hydration_fragment, - get_hydration_fragment, - hydrate_block_anchor, - set_current_hydration_fragment -} from './hydration.js'; +import { append_child } from './operations.js'; +import { current_hydration_fragment, hydrate_block_anchor } from './hydration.js'; import { is_array } from './utils.js'; -import { each_item_block, destroy_each_item_block, update_each_item_block } from './render.js'; -import { EACH_INDEX_REACTIVE, EACH_IS_ANIMATED, EACH_ITEM_REACTIVE } from '../../constants.js'; - -const NEW_BLOCK = -1; -const MOVED_BLOCK = 99999999; -const LIS_BLOCK = -2; /** @param {string} html */ export function create_fragment_from_html(html) { @@ -103,429 +92,3 @@ export function reconcile_html(dom, value, svg) { target.before(svg ? /** @type {Node} */ (clone.firstChild) : clone); return /** @type {Array} */ (frag_nodes); } - -/** - * @param {import('./types.js').Block} block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {null | Text | Element | Comment} sibling - * @returns {Text | Element | Comment} - */ -function insert_each_item_block(block, dom, is_controlled, sibling) { - var current = /** @type {import('./types.js').TemplateNode} */ (block.d); - if (sibling === null) { - if (is_controlled) { - return insert(current, /** @type {Element} */ (dom), null); - } else { - return insert(current, /** @type {Element} */ (dom.parentNode), dom); - } - } - return insert(current, null, sibling); -} - -/** - * @param {import('./types.js').Block} block - * @returns {Text | Element | Comment} - */ -function get_first_child(block) { - var current = block.d; - if (is_array(current)) { - return /** @type {Text | Element | Comment} */ (current[0]); - } - return /** @type {Text | Element | Comment} */ (current); -} - -/** - * @param {Array} active_transitions - * @returns {void} - */ -function destroy_active_transition_blocks(active_transitions) { - var length = active_transitions.length; - if (length > 0) { - var i = 0; - var block; - var transition; - for (; i < length; i++) { - block = active_transitions[i]; - transition = block.r; - if (transition !== null) { - block.r = null; - destroy_each_item_block(block, null, false); - } - } - active_transitions.length = 0; - } -} - -/** - * @template V - * @param {Array} array - * @param {import('./types.js').EachBlock} each_block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn - * @param {number} flags - * @param {boolean} apply_transitions - * @returns {void} - */ -export function reconcile_indexed_array( - array, - each_block, - dom, - is_controlled, - render_fn, - flags, - apply_transitions -) { - var a_blocks = each_block.v; - var active_transitions = each_block.s; - - /** @type {number | void} */ - var a = a_blocks.length; - - /** @type {number} */ - var b = array.length; - var length = Math.max(a, b); - var index = 0; - - /** @type {Array} */ - var b_blocks; - var block; - if (active_transitions.length !== 0) { - destroy_active_transition_blocks(active_transitions); - } - if (b === 0) { - b_blocks = []; - // Remove old blocks - if (is_controlled && a !== 0) { - clear_text_content(dom); - } - while (index < length) { - block = a_blocks[index++]; - destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); - } - } else { - var item; - b_blocks = Array(b); - if (current_hydration_fragment !== null) { - /** @type {Node} */ - var hydrating_node = current_hydration_fragment[0]; - for (; index < length; index++) { - // Hydrate block - item = array[index]; - var fragment = /** @type {Array} */ ( - get_hydration_fragment(hydrating_node) - ); - set_current_hydration_fragment(fragment); - hydrating_node = /** @type {Node} */ ( - /** @type {Node} */ (/** @type {Node} */ (fragment.at(-1)).nextSibling).nextSibling - ); - block = each_item_block(item, null, index, render_fn, flags); - b_blocks[index] = block; - } - } else { - for (; index < length; index++) { - if (index >= a) { - // Add block - item = array[index]; - block = each_item_block(item, null, index, render_fn, flags); - b_blocks[index] = block; - insert_each_item_block(block, dom, is_controlled, null); - } else if (index >= b) { - // Remove block - block = a_blocks[index]; - destroy_each_item_block(block, active_transitions, apply_transitions); - } else { - // Update block - item = array[index]; - block = a_blocks[index]; - b_blocks[index] = block; - update_each_item_block(block, item, index, flags); - } - } - } - } - each_block.v = b_blocks; -} -// Reconcile arrays by the equality of the elements in the array. This algorithm -// is based on Ivi's reconcilation logic: -// -// https://github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968 -// - -/** - * @template V - * @param {Array} array - * @param {import('./types.js').EachBlock} each_block - * @param {Element | Comment | Text} dom - * @param {boolean} is_controlled - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn - * @param {number} flags - * @param {boolean} apply_transitions - * @param {Array | null} keys - * @returns {void} - */ -export function reconcile_tracked_array( - array, - each_block, - dom, - is_controlled, - render_fn, - flags, - apply_transitions, - keys -) { - var a_blocks = each_block.v; - const is_computed_key = keys !== null; - var active_transitions = each_block.s; - - /** @type {number | void} */ - var a = a_blocks.length; - - /** @type {number} */ - var b = array.length; - - /** @type {Array} */ - var b_blocks; - var block; - if (active_transitions.length !== 0) { - destroy_active_transition_blocks(active_transitions); - } - if (b === 0) { - b_blocks = []; - // Remove old blocks - if (is_controlled && a !== 0) { - clear_text_content(dom); - } - while (a > 0) { - block = a_blocks[--a]; - destroy_each_item_block(block, active_transitions, apply_transitions, is_controlled); - } - } else { - var a_end = a - 1; - var b_end = b - 1; - var key; - var item; - var idx; - b_blocks = Array(b); - if (current_hydration_fragment !== null) { - var fragment; - - /** @type {Node} */ - var hydrating_node = current_hydration_fragment[0]; - while (b > 0) { - // Hydrate block - idx = b_end - --b; - item = array[idx]; - key = is_computed_key ? keys[idx] : item; - fragment = /** @type {Array} */ ( - get_hydration_fragment(hydrating_node) - ); - set_current_hydration_fragment(fragment); - // Get the tag of the next item in the list - // The fragment array can be empty if each block has no content - hydrating_node = /** @type {Node} */ ( - /** @type {Node} */ ((fragment.at(-1) || hydrating_node).nextSibling).nextSibling - ); - block = each_item_block(item, key, idx, render_fn, flags); - b_blocks[idx] = block; - } - } else if (a === 0) { - // Create new blocks - while (b > 0) { - idx = b_end - --b; - item = array[idx]; - key = is_computed_key ? keys[idx] : item; - block = each_item_block(item, key, idx, render_fn, flags); - b_blocks[idx] = block; - insert_each_item_block(block, dom, is_controlled, null); - } - } else { - var should_update_block = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; - var start = 0; - - /** @type {null | Text | Element | Comment} */ - var sibling = null; - item = array[b_end]; - key = is_computed_key ? keys[b_end] : item; - // Step 1 - outer: while (true) { - // From the end - while (a_blocks[a_end].k === key) { - block = a_blocks[a_end--]; - item = array[b_end]; - if (should_update_block) { - update_each_item_block(block, item, b_end, flags); - } - sibling = get_first_child(block); - b_blocks[b_end] = block; - if (start > --b_end || start > a_end) { - break outer; - } - key = is_computed_key ? keys[b_end] : item; - } - item = array[start]; - key = is_computed_key ? keys[start] : item; - // At the start - while (start <= a_end && start <= b_end && a_blocks[start].k === key) { - item = array[start]; - block = a_blocks[start]; - if (should_update_block) { - update_each_item_block(block, item, start, flags); - } - b_blocks[start] = block; - ++start; - key = is_computed_key ? keys[start] : array[start]; - } - break; - } - // Step 2 - if (start > a_end) { - while (b_end >= start) { - item = array[b_end]; - key = is_computed_key ? keys[b_end] : item; - block = each_item_block(item, key, b_end, render_fn, flags); - b_blocks[b_end--] = block; - sibling = insert_each_item_block(block, dom, is_controlled, sibling); - } - } else if (start > b_end) { - b = start; - do { - if ((block = a_blocks[b++]) !== null) { - destroy_each_item_block(block, active_transitions, apply_transitions); - } - } while (b <= a_end); - } else { - // Step 3 - var pos = 0; - var b_length = b_end - start + 1; - var sources = new Int32Array(b_length); - var item_index = new Map(); - for (b = 0; b < b_length; ++b) { - a = b + start; - sources[b] = NEW_BLOCK; - item = array[a]; - key = is_computed_key ? keys[a] : item; - map_set(item_index, key, a); - } - for (b = start; b <= a_end; ++b) { - a = map_get(item_index, /** @type {V} */ (a_blocks[b].k)); - block = a_blocks[b]; - if (a !== undefined) { - pos = pos < a ? a : MOVED_BLOCK; - sources[a - start] = b; - b_blocks[a] = block; - } else if (block !== null) { - destroy_each_item_block(block, active_transitions, apply_transitions); - } - } - // Step 4 - if (pos === MOVED_BLOCK) { - mark_lis(sources); - } - // If keys are animated, we need to do updates before actual moves - var is_animated = (flags & EACH_IS_ANIMATED) !== 0; - var should_create; - if (is_animated) { - var i = b_length; - while (i-- > 0) { - b_end = i + start; - a = sources[i]; - if (pos === MOVED_BLOCK && a !== LIS_BLOCK) { - block = b_blocks[b_end]; - update_each_item_block(block, item, b_end, flags); - } - } - } - var last_block; - var last_sibling; - while (b_length-- > 0) { - b_end = b_length + start; - a = sources[b_length]; - should_create = a === -1; - item = array[b_end]; - if (should_create) { - key = is_computed_key ? keys[b_end] : item; - block = each_item_block(item, key, b_end, render_fn, flags); - } else { - block = b_blocks[b_end]; - if (!is_animated && should_update_block) { - update_each_item_block(block, item, b_end, flags); - } - } - if (should_create || (pos === MOVED_BLOCK && a !== LIS_BLOCK)) { - last_sibling = last_block === undefined ? sibling : get_first_child(last_block); - sibling = insert_each_item_block(block, dom, is_controlled, last_sibling); - } - b_blocks[b_end] = block; - last_block = block; - } - } - } - } - each_block.v = b_blocks; -} -// Longest Increased Subsequence algorithm. - -/** - * @param {Int32Array} a - * @returns {void} - */ -function mark_lis(a) { - var length = a.length; - var parent = new Int32Array(length); - var index = new Int32Array(length); - var index_length = 0; - var i = 0; - - /** @type {number} */ - var j; - - /** @type {number} */ - var k; - - /** @type {number} */ - var lo; - - /** @type {number} */ - var hi; - // Skip -1 values at the start of the input array `a`. - for (; a[i] === NEW_BLOCK; ++i) { - /**/ - } - index[0] = i++; - for (; i < length; ++i) { - k = a[i]; - if (k !== NEW_BLOCK) { - // Ignore -1 values. - j = index[index_length]; - if (a[j] < k) { - parent[i] = j; - index[++index_length] = i; - } else { - lo = 0; - hi = index_length; - while (lo < hi) { - j = (lo + hi) >> 1; - if (a[index[j]] < k) { - lo = j + 1; - } else { - hi = j; - } - } - if (k < a[index[lo]]) { - if (lo > 0) { - parent[i] = index[lo - 1]; - } - index[lo] = i; - } - } - } - } - // Mutate input array `a` and assign -2 value to all nodes that are part of LIS. - j = index[index_length]; - while (index_length-- >= 0) { - a[j] = LIS_BLOCK; - j = parent[j]; - } -} diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index c23b38de5a..2111f412e4 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -11,8 +11,6 @@ import { } from './operations.js'; import { create_root_block, - create_each_item_block, - create_each_block, create_if_block, create_key_block, create_await_block, @@ -21,23 +19,8 @@ import { create_dynamic_component_block, create_snippet_block } from './block.js'; -import { - EACH_KEYED, - EACH_IS_CONTROLLED, - EACH_INDEX_REACTIVE, - EACH_ITEM_REACTIVE, - PassiveDelegatedEvents, - DelegatedEvents, - AttributeAliases -} from '../../constants.js'; -import { - create_fragment_from_html, - insert, - reconcile_tracked_array, - reconcile_html, - remove, - reconcile_indexed_array -} from './reconciler.js'; +import { PassiveDelegatedEvents, DelegatedEvents, AttributeAliases } from '../../constants.js'; +import { create_fragment_from_html, insert, reconcile_html, remove } from './reconciler.js'; import { render_effect, destroy_signal, @@ -54,14 +37,13 @@ import { expose, safe_not_equal, current_block, - set_signal_value, source, managed_effect, - safe_equal, push, current_component_context, pop, - schedule_task + unwrap, + mutable_source } from './runtime.js'; import { current_hydration_fragment, @@ -2019,288 +2001,6 @@ export function key(anchor_node, key, render_fn) { block.e = key_effect; } -/** - * @param {import('./types.js').Block} block - * @returns {Text | Element | Comment} - */ -function get_first_element(block) { - const current = block.d; - if (is_array(current)) { - for (let i = 0; i < current.length; i++) { - const node = current[i]; - if (node.nodeType !== 8) { - return node; - } - } - } - return /** @type {Text | Element | Comment} */ (current); -} - -/** - * @param {import('./types.js').EachItemBlock} block - * @param {any} item - * @param {number} index - * @param {number} type - * @returns {void} - */ -export function update_each_item_block(block, item, index, type) { - if ((type & EACH_ITEM_REACTIVE) !== 0) { - set_signal_value(block.v, item); - } - const transitions = block.s; - const index_is_reactive = (type & EACH_INDEX_REACTIVE) !== 0; - // Handle each item animations - if (transitions !== null && (type & EACH_KEYED) !== 0) { - let prev_index = block.i; - if (index_is_reactive) { - prev_index = /** @type {import('./types.js').Signal} */ (prev_index).v; - } - const items = block.p.v; - if (prev_index !== index && /** @type {number} */ (index) < items.length) { - const from_dom = /** @type {Element} */ (get_first_element(block)); - const from = from_dom.getBoundingClientRect(); - schedule_task(() => { - trigger_transitions(transitions, 'key', from); - }); - } - } - if (index_is_reactive) { - set_signal_value(/** @type {import('./types.js').Signal} */ (block.i), index); - } else { - block.i = index; - } -} - -/** - * @param {import('./types.js').EachItemBlock} block - * @param {null | Array} transition_block - * @param {boolean} apply_transitions - * @param {any} controlled - * @returns {void} - */ -export function destroy_each_item_block( - block, - transition_block, - apply_transitions, - controlled = false -) { - const transitions = block.s; - if (apply_transitions && transitions !== null) { - trigger_transitions(transitions, 'out'); - if (transition_block !== null) { - transition_block.push(block); - } - } else { - const dom = block.d; - if (!controlled && dom !== null) { - remove(dom); - } - destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.e)); - } -} - -/** - * @template V - * @param {V} item - * @param {unknown} key - * @param {number} index - * @param {(anchor: null, item: V, index: number | import('./types.js').Signal) => void} render_fn - * @param {number} flags - * @returns {import('./types.js').EachItemBlock} - */ -export function each_item_block(item, key, index, render_fn, flags) { - const item_value = (flags & EACH_ITEM_REACTIVE) === 0 ? item : source(item); - const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index); - const block = create_each_item_block(item_value, index_value, key); - const effect = render_effect( - /** @param {import('./types.js').EachItemBlock} block */ - (block) => { - render_fn(null, block.v, block.i); - }, - block, - true - ); - block.e = effect; - return block; -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {null | ((item: V) => string)} key_fn - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @param {typeof reconcile_indexed_array | reconcile_tracked_array} reconcile_fn - * @returns {void} - */ -function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) { - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const block = create_each_block(flags, anchor_node); - - /** @type {null | import('./types.js').Render} */ - let current_fallback = null; - hydrate_block_anchor(anchor_node, is_controlled); - - /** @type {V[]} */ - let array; - - /** @type {Array | null} */ - let keys = null; - - /** @type {null | import('./types.js').EffectSignal} */ - let render = null; - block.r = - /** @param {import('./types.js').Transition} transition */ - (transition) => { - const fallback = /** @type {import('./types.js').Render} */ (current_fallback); - const transitions = fallback.s; - transitions.add(transition); - transition.f(() => { - transitions.delete(transition); - if (transitions.size === 0) { - if (fallback.e !== null) { - if (fallback.d !== null) { - remove(fallback.d); - fallback.d = null; - } - destroy_signal(fallback.e); - fallback.e = null; - } - } - }); - }; - const create_fallback_effect = () => { - /** @type {import('./types.js').Render} */ - const fallback = { - d: null, - e: null, - s: new Set(), - p: current_fallback - }; - // Managed effect - const effect = render_effect( - () => { - const dom = block.d; - if (dom !== null) { - remove(dom); - block.d = null; - } - let anchor = block.a; - const is_controlled = (block.f & EACH_IS_CONTROLLED) !== 0; - if (is_controlled) { - anchor = empty(); - block.a.appendChild(anchor); - } - /** @type {(anchor: Node) => void} */ (fallback_fn)(anchor); - fallback.d = block.d; - block.d = null; - }, - block, - true - ); - fallback.e = effect; - current_fallback = fallback; - }; - const each = render_effect( - () => { - /** @type {V[]} */ - const maybe_array = collection(); - array = is_array(maybe_array) - ? maybe_array - : maybe_array == null - ? [] - : Array.from(maybe_array); - if (key_fn !== null) { - keys = array.map(key_fn); - } - if (fallback_fn !== null) { - if (array.length === 0) { - if (block.v.length !== 0 || render === null) { - create_fallback_effect(); - } - } else if (block.v.length === 0 && current_fallback !== null) { - const fallback = current_fallback; - const transitions = fallback.s; - if (transitions.size === 0) { - if (fallback.d !== null) { - remove(fallback.d); - fallback.d = null; - } - } else { - trigger_transitions(transitions, 'out'); - } - } - } - if (render !== null) { - execute_effect(render); - } - }, - block, - false - ); - render = render_effect( - /** @param {import('./types.js').EachBlock} block */ - (block) => { - const flags = block.f; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - const anchor_node = block.a; - reconcile_fn(array, block, anchor_node, is_controlled, render_fn, flags, true, keys); - }, - block, - true - ); - push_destroy_fn(each, () => { - const flags = block.f; - const anchor_node = block.a; - const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; - let fallback = current_fallback; - while (fallback !== null) { - const dom = fallback.d; - if (dom !== null) { - remove(dom); - } - const effect = fallback.e; - if (effect !== null) { - destroy_signal(effect); - } - fallback = fallback.p; - } - // Clear the array - reconcile_fn([], block, anchor_node, is_controlled, render_fn, flags, false, keys); - destroy_signal(/** @type {import('./types.js').EffectSignal} */ (render)); - }); - block.e = each; -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {null | ((item: V) => string)} key_fn - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @returns {void} - */ -export function each_keyed(anchor_node, collection, flags, key_fn, render_fn, fallback_fn) { - each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_tracked_array); -} - -/** - * @template V - * @param {Element | Comment} anchor_node - * @param {() => V[]} collection - * @param {number} flags - * @param {(anchor: null, item: V, index: import('./types.js').MaybeSignal) => void} render_fn - * @param {null | ((anchor: Node) => void)} fallback_fn - * @returns {void} - */ -export function each_indexed(anchor_node, collection, flags, render_fn, fallback_fn) { - each(anchor_node, collection, flags, null, render_fn, fallback_fn, reconcile_indexed_array); -} - /** * @param {Element | Text | Comment} anchor * @param {boolean} is_html @@ -2890,20 +2590,6 @@ export function spread_props(props) { return merged_props; } -/** - * @template V - * @param {V} value - * @returns {import('./types.js').UnwrappedSignal} - */ -export function unwrap(value) { - if (is_signal(value)) { - // @ts-ignore - return get(value); - } - // @ts-ignore - return value; -} - /** * Mounts the given component to the given target and returns a handle to the component's public accessors * as well as a `$set` and `$destroy` method to update the props of the component or destroy it. @@ -2920,7 +2606,6 @@ export function unwrap(value) { * events?: Events; * context?: Map; * intro?: boolean; - * immutable?: boolean; * recover?: false; * }} options * @returns {Exports & { $destroy: () => void; $set: (props: Partial) => void; }} @@ -2938,15 +2623,7 @@ export function createRoot(component, options) { * @param {any} value */ function add_prop(name, value) { - const prop = source( - value, - options.immutable - ? /** - * @param {any} a - * @param {any} b - */ (a, b) => a === b - : safe_equal - ); + const prop = source(value); _sources[name] = prop; define_property(_props, name, { get() { @@ -2980,11 +2657,9 @@ export function createRoot(component, options) { return _props[property]; } }); - const props_source = source( - props_proxy, - // We're resetting the same proxy instance for updates, therefore bypass equality checks - () => false - ); + + // We're resetting the same proxy instance for updates, therefore bypass equality checks + const props_source = mutable_source(props_proxy); let [accessors, $destroy] = mount(component, { ...options, @@ -3040,7 +2715,6 @@ export function createRoot(component, options) { * events?: Events; * context?: Map; * intro?: boolean; - * immutable?: boolean; * recover?: false; * }} options * @returns {[Exports, () => void]} diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 6b3d7abecc..2d3e5204c7 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1,7 +1,6 @@ import { DEV } from 'esm-env'; import { subscribe_to_store } from '../../store/utils.js'; import { EMPTY_FUNC, run_all } from '../common.js'; -import { unwrap } from './render.js'; import { get_descriptors, is_array } from './utils.js'; export const SOURCE = 1; @@ -24,6 +23,7 @@ const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; export const UNINITIALIZED = Symbol(); +export const LAZY_PROPERTY = Symbol(); // Used for controlling the flush of effects. let current_scheduler_mode = FLUSH_MICROTASK; @@ -87,6 +87,8 @@ export let current_block = null; export let current_component_context = null; export let is_ssr = false; +export let updating_derived = false; + /** * @param {boolean} ssr * @returns {void} @@ -108,8 +110,6 @@ export function create_component_context(props) { c: null, // effects e: null, - // immutable - i: false, // mounted m: false, // parent @@ -145,7 +145,7 @@ export function set_current_component_context(context_stack_item) { * @param {unknown} b * @returns {boolean} */ -function default_equals(a, b) { +export function default_equals(a, b) { return a === b; } @@ -161,7 +161,7 @@ function create_source_signal(flags, value) { // consumers c: null, // equals - e: null, + e: default_equals, // flags f: flags, // value @@ -176,7 +176,7 @@ function create_source_signal(flags, value) { // consumers c: null, // equals - e: null, + e: default_equals, // flags f: flags, // value @@ -688,7 +688,10 @@ export async function tick() { * @returns {void} */ function update_derived(signal, force_schedule) { + const previous_updating_derived = updating_derived; + updating_derived = true; const value = execute_signal_fn(signal); + updating_derived = previous_updating_derived; const status = current_skip_consumer || (current_effect === null && (signal.f & UNOWNED) !== 0) ? DIRTY @@ -726,7 +729,7 @@ export function store_get(store, store_name, stores) { entry = { store: null, last_value: null, - value: source(UNINITIALIZED), + value: mutable_source(UNINITIALIZED), unsubscribe: EMPTY_FUNC }; // TODO: can we remove this code? it was refactored out when we split up source/comptued signals @@ -1157,11 +1160,10 @@ export function destroy_signal(signal) { /** * @template V * @param {() => V} init - * @param {import('./types.js').EqualsFunctions} [equals] * @returns {import('./types.js').ComputationSignal} */ /*#__NO_SIDE_EFFECTS__*/ -export function derived(init, equals) { +export function derived(init) { const is_unowned = current_effect === null; const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; const signal = /** @type {import('./types.js').ComputationSignal} */ ( @@ -1169,7 +1171,7 @@ export function derived(init, equals) { ); signal.i = init; signal.x = current_component_context; - signal.e = get_equals_method(equals); + signal.e = default_equals; if (!is_unowned) { push_reference(/** @type {import('./types.js').EffectSignal} */ (current_effect), signal); } @@ -1179,30 +1181,25 @@ export function derived(init, equals) { /** * @template V * @param {V} initial_value - * @param {import('./types.js').EqualsFunctions} [equals] * @returns {import('./types.js').SourceSignal} */ /*#__NO_SIDE_EFFECTS__*/ -export function source(initial_value, equals) { +export function source(initial_value) { const source = create_source_signal(SOURCE | CLEAN, initial_value); source.x = current_component_context; - source.e = get_equals_method(equals); return source; } /** - * @param {import('./types.js').EqualsFunctions} [equals] - * @returns {import('./types.js').EqualsFunctions} + * @template V + * @param {V} initial_value + * @returns {import('./types.js').SourceSignal} */ -function get_equals_method(equals) { - if (equals !== undefined) { - return equals; - } - const context = current_component_context; - if (context && !context.i) { - return safe_equal; - } - return default_equals; +/*#__NO_SIDE_EFFECTS__*/ +export function mutable_source(initial_value) { + const s = source(initial_value); + s.e = safe_equal; + return s; } /** @@ -1425,6 +1422,20 @@ export function is_signal(val) { ); } +/** + * @template O + * @template P + * @param {any} val + * @returns {val is import('./types.js').LazyProperty} + */ +export function is_lazy_property(val) { + return ( + typeof val === 'object' && + val !== null && + /** @type {import('./types.js').LazyProperty} */ (val).t === LAZY_PROPERTY + ); +} + /** * @template V * @param {unknown} val @@ -1452,11 +1463,12 @@ export function is_store(val) { * @template V * @param {import('./types.js').MaybeSignal>} props_obj * @param {string} key + * @param {boolean} immutable * @param {V | (() => V)} [default_value] * @param {boolean} [call_default_value] * @returns {import('./types.js').Signal | (() => V)} */ -export function prop_source(props_obj, key, default_value, call_default_value) { +export function prop_source(props_obj, key, immutable, default_value, call_default_value) { const props = is_signal(props_obj) ? get(props_obj) : props_obj; const possible_signal = /** @type {import('./types.js').MaybeSignal} */ ( expose(() => props[key]) @@ -1468,8 +1480,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) { if ( is_signal(possible_signal) && possible_signal.v === value && - update_bound_prop === undefined && - get_equals_method() === possible_signal.e + update_bound_prop === undefined ) { if (should_set_default_value) { set( @@ -1487,13 +1498,11 @@ export function prop_source(props_obj, key, default_value, call_default_value) { call_default_value ? default_value() : default_value; } - const source_signal = source(value); + const source_signal = immutable ? source(value) : mutable_source(value); // Synchronize prop changes with source signal. // Needs special equality checking because the prop in the // parent could be changed through `foo.bar = 'new value'`. - const immutable = /** @type {import('./types.js').ComponentContext} */ (current_component_context) - .i; let ignore_next1 = false; let ignore_next2 = false; let did_update_to_defined = !should_set_default_value; @@ -1552,16 +1561,12 @@ export function prop_source(props_obj, key, default_value, call_default_value) { /** * If the prop is readonly and has no fallback value, we can use this function, else we need to use `prop_source`. - * @template V * @param {import('./types.js').MaybeSignal>} props_obj * @param {string} key * @returns {any} */ export function prop(props_obj, key) { - return () => { - const props = is_signal(props_obj) ? get(props_obj) : props_obj; - return /** @type {V} */ (props[key]); - }; + return is_signal(props_obj) ? () => get(props_obj)[key] : () => props_obj[key]; } /** @@ -1804,13 +1809,11 @@ export function onDestroy(fn) { /** * @param {import('./types.js').MaybeSignal>} props * @param {any} runes - * @param {any} immutable * @returns {void} */ -export function push(props, runes = false, immutable = false) { +export function push(props, runes = false) { const context_stack_item = create_component_context(props); context_stack_item.r = runes; - context_stack_item.i = immutable; current_component_context = context_stack_item; } @@ -1901,3 +1904,35 @@ export function inspect(get_value, inspect = console.log) { }; }); } + +/** + * @template O + * @template P + * @param {O} o + * @param {P} p + * @returns {import('./types.js').LazyProperty} + */ +export function lazy_property(o, p) { + return { + o, + p, + t: LAZY_PROPERTY + }; +} + +/** + * @template V + * @param {V} value + * @returns {import('./types.js').UnwrappedSignal} + */ +export function unwrap(value) { + if (is_signal(value)) { + // @ts-ignore + return get(value); + } + if (is_lazy_property(value)) { + return value.o[value.p]; + } + // @ts-ignore + return value; +} diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 8b2666a713..27db05fdd2 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -9,8 +9,9 @@ import { KEY_BLOCK, ROOT_BLOCK } from './block.js'; +import { destroy_each_item_block } from './each.js'; import { append_child } from './operations.js'; -import { destroy_each_item_block, empty } from './render.js'; +import { empty } from './render.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 ee7bc6eea1..13bfbc16cd 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -10,7 +10,7 @@ import { DYNAMIC_ELEMENT_BLOCK, SNIPPET_BLOCK } from './block.js'; -import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT } from './runtime.js'; +import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT, LAZY_PROPERTY } from './runtime.js'; // Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file @@ -47,8 +47,6 @@ export type ComponentContext = { p: null | ComponentContext; /** context */ c: null | Map; - /** immutable */ - i: boolean; /** runes */ r: boolean; /** update_callbacks */ @@ -116,6 +114,12 @@ export type MaybeSignal = T | Signal; export type UnwrappedSignal = T extends Signal ? U : T; +export type LazyProperty = { + o: O; + p: P; + t: typeof LAZY_PROPERTY; +}; + export type EqualsFunctions = (a: T, v: T) => boolean; export type BlockType = diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 3cdfbeaf4a..840e8cfd43 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -7,6 +7,7 @@ export { expose, exposable, source, + mutable_source, derived, prop, prop_source, @@ -38,12 +39,15 @@ export { reactive_import, effect_active, user_root_effect, - inspect + inspect, + unwrap } from './client/runtime.js'; -export * from './client/validate.js'; - +export * from './client/each.js'; export * from './client/render.js'; +export * from './client/validate.js'; +export { raf } from './client/timing.js'; +export { proxy, readonly } from './client/proxy/proxy.js'; export { create_custom_element } from './client/custom-element.js'; @@ -54,5 +58,3 @@ export { $window as window, $document as document } from './client/operations.js'; - -export { raf } from './client/timing.js'; diff --git a/packages/svelte/src/internal/server/index.js b/packages/svelte/src/internal/server/index.js index dc0afe7ec2..562ee22701 100644 --- a/packages/svelte/src/internal/server/index.js +++ b/packages/svelte/src/internal/server/index.js @@ -115,10 +115,9 @@ export function render(component, options) { /** * @param {boolean} runes - * @param {boolean} [immutable] */ -export function push(runes, immutable) { - $.push({}, runes, immutable); +export function push(runes) { + $.push({}, runes); } export function pop() { diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js index 8c63213208..9ad99401d8 100644 --- a/packages/svelte/src/legacy/legacy-client.js +++ b/packages/svelte/src/legacy/legacy-client.js @@ -68,7 +68,6 @@ class Svelte4Component { target: options.target, props: { ...options.props, $$events: this.#events }, context: options.context, - immutable: options.immutable, intro: options.intro, recover: options.recover }); diff --git a/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js b/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js new file mode 100644 index 0000000000..801d1d8a30 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-derived-state/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'invalid-derived-export', + message: 'Cannot export derived state', + position: process.platform === 'win32' ? [26, 68] : [24, 66] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js new file mode 100644 index 0000000000..0906cedf74 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-derived-state/main.svelte.js @@ -0,0 +1,3 @@ +let count = $state(0); + +export const double = $derived(count * 2); diff --git a/packages/svelte/tests/compiler-errors/samples/export-state/_config.js b/packages/svelte/tests/compiler-errors/samples/export-state/_config.js new file mode 100644 index 0000000000..755b891586 --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-state/_config.js @@ -0,0 +1,9 @@ +import { test } from '../../test'; + +export default test({ + error: { + code: 'invalid-state-export', + message: 'Cannot export state if it is reassigned', + position: process.platform === 'win32' ? [50, 90] : [46, 86] + } +}); diff --git a/packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js b/packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js new file mode 100644 index 0000000000..39d9920f7d --- /dev/null +++ b/packages/svelte/tests/compiler-errors/samples/export-state/main.svelte.js @@ -0,0 +1,13 @@ +export const object = $state({ + ok: true +}); + +export const primitive = $state('nope'); + +export function update_object() { + object.ok = !object.ok; +} + +export function update_primitive() { + primitive = 'yep'; +} diff --git a/packages/svelte/tests/compiler-errors/test.ts b/packages/svelte/tests/compiler-errors/test.ts index b7a76c896f..376e790db6 100644 --- a/packages/svelte/tests/compiler-errors/test.ts +++ b/packages/svelte/tests/compiler-errors/test.ts @@ -37,11 +37,11 @@ const { test, run } = suite((config, cwd) => { } } - if (fs.existsSync(`${cwd}/main.js`)) { + if (fs.existsSync(`${cwd}/main.svelte.js`)) { let caught_error = false; try { - compileModule(fs.readFileSync(`${cwd}/main.js`, 'utf-8'), { + compileModule(fs.readFileSync(`${cwd}/main.svelte.js`, 'utf-8'), { generate: 'client' }); } catch (e) { @@ -51,6 +51,10 @@ const { test, run } = suite((config, cwd) => { expect(error.code).toMatch(config.error.code); expect(error.message).toMatch(config.error.message); + + if (config.error.position) { + expect(error.position).toEqual(config.error.position); + } } if (!caught_error) { diff --git a/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json b/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json index 99e9a0d154..3dad9bb4e5 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-duplicate/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 35, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 35, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json index f1d5293746..66ce187c62 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-with-call/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 40, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 40, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json index 4931f29ea9..39a6f5f647 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-with-identifier/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 29, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 29, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json b/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json index e59e5e2d70..94c60b701a 100644 --- a/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action-with-literal/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 37, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 37, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/action/output.json b/packages/svelte/tests/parser-legacy/samples/action/output.json index 0ed95d4ee8..d72bf7db10 100644 --- a/packages/svelte/tests/parser-legacy/samples/action/output.json +++ b/packages/svelte/tests/parser-legacy/samples/action/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 21, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 21, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/animation/output.json b/packages/svelte/tests/parser-legacy/samples/animation/output.json index 3f967587f8..6a43c13c2f 100644 --- a/packages/svelte/tests/parser-legacy/samples/animation/output.json +++ b/packages/svelte/tests/parser-legacy/samples/animation/output.json @@ -1,34 +1,18 @@ { "html": { + "type": "Fragment", "start": 0, "end": 70, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 70, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 13, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 13 - } - }, - "name": "things" - }, "children": [ { + "type": "Element", "start": 33, "end": 62, - "type": "Element", "name": "div", "attributes": [ { @@ -42,9 +26,9 @@ ], "children": [ { + "type": "Text", "start": 51, "end": 56, - "type": "Text", "raw": "flips", "data": "flips" } @@ -57,6 +41,22 @@ "start": 17, "end": 22 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "name": "things" + }, "key": { "type": "Identifier", "start": 24, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json index 88708ebb6e..9efe9acf8d 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-class-directive/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 29, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 29, - "type": "Element", "name": "div", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json index a02fb12ae8..2c63b3a43d 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-containing-solidus/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 41, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 41, - "type": "Element", "name": "a", "attributes": [ { + "type": "Attribute", "start": 3, "end": 30, - "type": "Attribute", "name": "href", "value": [ { @@ -28,9 +28,9 @@ ], "children": [ { + "type": "Text", "start": 31, "end": 37, - "type": "Text", "raw": "Google", "data": "Google" } diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json index 077e307dff..2453dc9e0a 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-curly-bracket/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 18, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 18, - "type": "Element", "name": "input", "attributes": [ { + "type": "Attribute", "start": 7, "end": 15, - "type": "Attribute", "name": "foo", "value": [ { @@ -24,9 +24,9 @@ "data": "a" }, { + "type": "MustacheTag", "start": 12, "end": 15, - "type": "MustacheTag", "expression": { "type": "Literal", "start": 13, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json index b87a654670..5793afe896 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic-boolean/output.json @@ -1,25 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 41, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 41, - "type": "Element", "name": "textarea", "attributes": [ { + "type": "Attribute", "start": 10, "end": 29, - "type": "Attribute", "name": "readonly", "value": [ { + "type": "MustacheTag", "start": 19, "end": 29, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 20, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json index 4926b645ab..9fd98c80ec 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-dynamic/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 42, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 42, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 28, - "type": "Attribute", "name": "style", "value": [ { @@ -24,9 +24,9 @@ "data": "color: " }, { + "type": "MustacheTag", "start": 19, "end": 26, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 20, @@ -56,9 +56,9 @@ ], "children": [ { + "type": "MustacheTag", "start": 29, "end": 36, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 30, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json index 04b28f8d4a..d2a3dcd93b 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-empty/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 38, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 38, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 9, - "type": "Attribute", "name": "a", "value": [ { @@ -26,15 +26,15 @@ ] }, { + "type": "Attribute", "start": 10, "end": 16, - "type": "Attribute", "name": "b", "value": [ { + "type": "MustacheTag", "start": 12, "end": 16, - "type": "MustacheTag", "expression": { "type": "Literal", "start": 13, @@ -56,9 +56,9 @@ ] }, { + "type": "Attribute", "start": 17, "end": 21, - "type": "Attribute", "name": "c", "value": [ { @@ -71,15 +71,15 @@ ] }, { + "type": "Attribute", "start": 22, "end": 30, - "type": "Attribute", "name": "d", "value": [ { + "type": "MustacheTag", "start": 25, "end": 29, - "type": "MustacheTag", "expression": { "type": "Literal", "start": 26, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json index 71eb553a71..e2eb99f327 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-escaped/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 83, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 83, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 76, - "type": "Attribute", "name": "data-foo", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json index ce098fa537..66b780e536 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-multiple/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 28, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 28, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 11, - "type": "Attribute", "name": "id", "value": [ { @@ -26,9 +26,9 @@ ] }, { + "type": "Attribute", "start": 12, "end": 21, - "type": "Attribute", "name": "class", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json index 4bfb7af187..5daf29018c 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-shorthand/output.json @@ -1,25 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 11, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 11, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 9, - "type": "Attribute", "name": "id", "value": [ { + "type": "AttributeShorthand", "start": 6, "end": 8, - "type": "AttributeShorthand", "expression": { "start": 6, "end": 8, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json index cf65d54592..8cb93b75ec 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-static-boolean/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 30, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 30, - "type": "Element", "name": "textarea", "attributes": [ { + "type": "Attribute", "start": 10, "end": 18, - "type": "Attribute", "name": "readonly", "value": true } diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json index b4fff2cd06..3e19a4727e 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-static/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 23, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 23, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 16, - "type": "Attribute", "name": "class", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json index 1587c2a3fb..b7de71ff5a 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-modifiers/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 43, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 43, - "type": "Element", "name": "div", "attributes": [ { @@ -18,9 +18,9 @@ "modifiers": ["important"], "value": [ { + "type": "MustacheTag", "start": 27, "end": 36, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 28, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json index 80fb4f741f..d7f53cb00c 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-shorthand/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 23, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 23, - "type": "Element", "name": "div", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json index 505e9333e1..5acf7d797e 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive-string/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 252, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 29, - "type": "Element", "name": "div", "attributes": [ { @@ -30,24 +30,24 @@ "children": [] }, { + "type": "Text", "start": 29, "end": 30, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 30, "end": 59, - "type": "Element", "name": "div", "attributes": [ { "start": 35, "end": 52, "type": "StyleDirective", - "modifiers": [], "name": "color", + "modifiers": [], "value": [ { "start": 48, @@ -62,16 +62,16 @@ "children": [] }, { + "type": "Text", "start": 59, "end": 60, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 60, "end": 87, - "type": "Element", "name": "div", "attributes": [ { @@ -94,16 +94,16 @@ "children": [] }, { + "type": "Text", "start": 87, "end": 88, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 88, "end": 127, - "type": "Element", "name": "div", "attributes": [ { @@ -121,9 +121,9 @@ "data": "red" }, { + "type": "MustacheTag", "start": 109, "end": 119, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 110, @@ -147,16 +147,16 @@ "children": [] }, { + "type": "Text", "start": 127, "end": 128, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 128, "end": 167, - "type": "Element", "name": "div", "attributes": [ { @@ -174,9 +174,9 @@ "data": "red" }, { + "type": "MustacheTag", "start": 149, "end": 159, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 150, @@ -200,16 +200,16 @@ "children": [] }, { + "type": "Text", "start": 167, "end": 168, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 168, "end": 205, - "type": "Element", "name": "div", "attributes": [ { @@ -227,9 +227,9 @@ "data": "red" }, { + "type": "MustacheTag", "start": 188, "end": 198, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 189, @@ -253,16 +253,16 @@ "children": [] }, { + "type": "Text", "start": 205, "end": 206, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 206, "end": 252, - "type": "Element", "name": "div", "attributes": [ { @@ -273,9 +273,9 @@ "modifiers": [], "value": [ { + "type": "MustacheTag", "start": 223, "end": 245, - "type": "MustacheTag", "expression": { "type": "TemplateLiteral", "start": 224, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json index b7983a769e..2cce9fef95 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style-directive/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 33, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 33, - "type": "Element", "name": "div", "attributes": [ { @@ -18,9 +18,9 @@ "modifiers": [], "value": [ { + "type": "MustacheTag", "start": 17, "end": 26, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 18, diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json index 8dd604523d..1d9a528d6d 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-style/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 34, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 34, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 24, - "type": "Attribute", "name": "style", "value": [ { @@ -28,9 +28,9 @@ ], "children": [ { + "type": "Text", "start": 25, "end": 28, - "type": "Text", "raw": "red", "data": "red" } 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 582ca0a2f3..5df4d66ab6 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-unquoted/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 21, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 21, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 5, "end": 14, - "type": "Attribute", "name": "class", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json b/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json index dbd1d8d60a..4d3a291808 100644 --- a/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json +++ b/packages/svelte/tests/parser-legacy/samples/attribute-with-whitespace/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 38, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 38, - "type": "Element", "name": "button", "attributes": [ { @@ -36,9 +36,9 @@ ], "children": [ { + "type": "Text", "start": 24, "end": 29, - "type": "Text", "raw": "Click", "data": "Click" } diff --git a/packages/svelte/tests/parser-legacy/samples/await-catch/output.json b/packages/svelte/tests/parser-legacy/samples/await-catch/output.json index 163d061302..06a73d522c 100644 --- a/packages/svelte/tests/parser-legacy/samples/await-catch/output.json +++ b/packages/svelte/tests/parser-legacy/samples/await-catch/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 99, - "type": "Fragment", "children": [ { + "type": "AwaitBlock", "start": 0, "end": 99, - "type": "AwaitBlock", "expression": { "type": "Identifier", "start": 8, @@ -32,37 +32,37 @@ "end": 55 }, "pending": { + "type": "PendingBlock", "start": 19, "end": 39, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 19, "end": 21, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 21, "end": 38, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 24, "end": 34, - "type": "Text", "raw": "loading...", "data": "loading..." } ] }, { + "type": "Text", "start": 38, "end": 39, - "type": "Text", "raw": "\n", "data": "\n" } @@ -70,42 +70,42 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": null, "end": null, - "type": "ThenBlock", "children": [], "skip": true }, "catch": { + "type": "CatchBlock", "start": 39, "end": 91, - "type": "CatchBlock", "children": [ { + "type": "Text", "start": 56, "end": 58, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 58, "end": 90, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 61, "end": 68, - "type": "Text", "raw": "oh no! ", "data": "oh no! " }, { + "type": "MustacheTag", "start": 68, "end": 86, - "type": "MustacheTag", "expression": { "type": "MemberExpression", "start": 69, @@ -159,9 +159,9 @@ ] }, { + "type": "Text", "start": 90, "end": 91, - "type": "Text", "raw": "\n", "data": "\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json b/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json index 18b484b0c0..a2ccb995e0 100644 --- a/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json +++ b/packages/svelte/tests/parser-legacy/samples/await-then-catch/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 148, - "type": "Fragment", "children": [ { + "type": "AwaitBlock", "start": 0, "end": 148, - "type": "AwaitBlock", "expression": { "type": "Identifier", "start": 8, @@ -37,37 +37,37 @@ "end": 104 }, "pending": { + "type": "PendingBlock", "start": 19, "end": 39, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 19, "end": 21, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 21, "end": 38, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 24, "end": 34, - "type": "Text", "raw": "loading...", "data": "loading..." } ] }, { + "type": "Text", "start": 38, "end": 39, - "type": "Text", "raw": "\n", "data": "\n" } @@ -75,35 +75,35 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": 39, "end": 88, - "type": "ThenBlock", "children": [ { + "type": "Text", "start": 55, "end": 57, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 57, "end": 87, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 60, "end": 73, - "type": "Text", "raw": "the value is ", "data": "the value is " }, { + "type": "MustacheTag", "start": 73, "end": 83, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 74, @@ -124,9 +124,9 @@ ] }, { + "type": "Text", "start": 87, "end": 88, - "type": "Text", "raw": "\n", "data": "\n" } @@ -134,35 +134,35 @@ "skip": false }, "catch": { + "type": "CatchBlock", "start": 88, "end": 140, - "type": "CatchBlock", "children": [ { + "type": "Text", "start": 105, "end": 107, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 107, "end": 139, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 110, "end": 117, - "type": "Text", "raw": "oh no! ", "data": "oh no! " }, { + "type": "MustacheTag", "start": 117, "end": 135, - "type": "MustacheTag", "expression": { "type": "MemberExpression", "start": 118, @@ -216,9 +216,9 @@ ] }, { + "type": "Text", "start": 139, "end": 140, - "type": "Text", "raw": "\n", "data": "\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json b/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json index a86d3f635d..6720146297 100644 --- a/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json +++ b/packages/svelte/tests/parser-legacy/samples/binding-shorthand/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 30, "end": 48, - "type": "Fragment", "children": [ { + "type": "Text", "start": 28, "end": 30, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "InlineComponent", "start": 30, "end": 48, - "type": "InlineComponent", "name": "Widget", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/binding/output.json b/packages/svelte/tests/parser-legacy/samples/binding/output.json index 3dad94d3da..4ce069bd37 100644 --- a/packages/svelte/tests/parser-legacy/samples/binding/output.json +++ b/packages/svelte/tests/parser-legacy/samples/binding/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 31, "end": 56, - "type": "Fragment", "children": [ { + "type": "Text", "start": 29, "end": 31, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 31, "end": 56, - "type": "Element", "name": "input", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json b/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json index e15150a09d..b149436a27 100644 --- a/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json +++ b/packages/svelte/tests/parser-legacy/samples/comment-with-ignores/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 30, - "type": "Fragment", "children": [ { + "type": "Comment", "start": 0, "end": 30, - "type": "Comment", "data": " svelte-ignore foo bar ", "ignores": ["foo", "bar"] } diff --git a/packages/svelte/tests/parser-legacy/samples/comment/output.json b/packages/svelte/tests/parser-legacy/samples/comment/output.json index b3f050d650..6017db404c 100644 --- a/packages/svelte/tests/parser-legacy/samples/comment/output.json +++ b/packages/svelte/tests/parser-legacy/samples/comment/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 18, - "type": "Fragment", "children": [ { + "type": "Comment", "start": 0, "end": 18, - "type": "Comment", "data": " a comment ", "ignores": [] } diff --git a/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json b/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json index 06e17f8092..f624a04c61 100644 --- a/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json +++ b/packages/svelte/tests/parser-legacy/samples/component-dynamic/output.json @@ -1,16 +1,14 @@ { "html": { + "type": "Fragment", "start": 0, "end": 62, - "type": "Fragment", "children": [ { - "start": 0, - "end": 62, "type": "InlineComponent", "name": "svelte:component", - "attributes": [], - "children": [], + "start": 0, + "end": 62, "expression": { "type": "ConditionalExpression", "start": 25, @@ -73,7 +71,9 @@ }, "name": "Bar" } - } + }, + "attributes": [], + "children": [] } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json b/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json index 15784cbcb6..f7a8a3c677 100644 --- a/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json +++ b/packages/svelte/tests/parser-legacy/samples/convert-entities-in-element/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 0, "end": 24, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 24, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 3, "end": 20, - "type": "Text", "raw": "Hello & World", "data": "Hello & World" } diff --git a/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json b/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json index 5a3ad64c20..c336a3978c 100644 --- a/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json +++ b/packages/svelte/tests/parser-legacy/samples/convert-entities/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 17, - "type": "Fragment", "children": [ { + "type": "Text", "start": 0, "end": 17, - "type": "Text", "raw": "Hello & World", "data": "Hello & World" } diff --git a/packages/svelte/tests/parser-legacy/samples/css/output.json b/packages/svelte/tests/parser-legacy/samples/css/output.json index 31d3d145a9..d1256eef45 100644 --- a/packages/svelte/tests/parser-legacy/samples/css/output.json +++ b/packages/svelte/tests/parser-legacy/samples/css/output.json @@ -1,29 +1,29 @@ { "html": { + "type": "Fragment", "start": 0, "end": 14, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 14, - "type": "Element", "name": "div", "attributes": [], "children": [ { + "type": "Text", "start": 5, "end": 8, - "type": "Text", "raw": "foo", "data": "foo" } ] }, { + "type": "Text", "start": 14, "end": 16, - "type": "Text", "raw": "\n\n", "data": "\n\n" } @@ -39,9 +39,13 @@ "type": "Rule", "prelude": { "type": "SelectorList", + "start": 25, + "end": 28, "children": [ { "type": "Selector", + "start": 25, + "end": 28, "children": [ { "type": "TypeSelector", @@ -49,27 +53,23 @@ "start": 25, "end": 28 } - ], - "start": 25, - "end": 28 + ] } - ], - "start": 25, - "end": 28 + ] }, "block": { "type": "Block", + "start": 29, + "end": 47, "children": [ { "type": "Declaration", - "property": "color", - "value": "red", "start": 33, - "end": 43 + "end": 43, + "property": "color", + "value": "red" } - ], - "start": 29, - "end": 47 + ] }, "start": 25, "end": 47 diff --git a/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json b/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json index 9e9245b1ae..6dd18f0a4e 100644 --- a/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json +++ b/packages/svelte/tests/parser-legacy/samples/dynamic-element-string/output.json @@ -1,30 +1,31 @@ { "html": { + "type": "Fragment", "start": 0, "end": 101, - "type": "Fragment", "children": [ { - "start": 0, - "end": 44, "type": "Element", "name": "svelte:element", + "start": 0, + "end": 44, + "tag": "div", "attributes": [], - "children": [], - "tag": "div" + "children": [] }, { "type": "Text", "start": 44, "end": 45, - "data": "\n", - "raw": "\n" + "raw": "\n", + "data": "\n" }, { - "start": 45, - "end": 101, "type": "Element", "name": "svelte:element", + "start": 45, + "end": 101, + "tag": "div", "attributes": [ { "type": "Attribute", @@ -33,17 +34,16 @@ "name": "class", "value": [ { - "type": "Text", "start": 79, "end": 82, - "data": "foo", - "raw": "foo" + "type": "Text", + "raw": "foo", + "data": "foo" } ] } ], - "children": [], - "tag": "div" + "children": [] } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json b/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json index 53769e94e3..291cdaa734 100644 --- a/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json +++ b/packages/svelte/tests/parser-legacy/samples/dynamic-element-variable/output.json @@ -1,17 +1,16 @@ { "html": { + "type": "Fragment", "start": 0, "end": 101, - "type": "Fragment", "children": [ { - "start": 0, - "end": 44, "type": "Element", "name": "svelte:element", - "attributes": [], - "children": [], + "start": 0, + "end": 44, "tag": { + "type": "Identifier", "start": 22, "end": 25, "loc": { @@ -24,23 +23,39 @@ "column": 25 } }, - "name": "tag", - "type": "Identifier" - } + "name": "tag" + }, + "attributes": [], + "children": [] }, { "type": "Text", "start": 44, "end": 45, - "data": "\n", - "raw": "\n" + "raw": "\n", + "data": "\n" }, { - "start": 45, - "end": 101, "type": "Element", "name": "svelte:element", - "children": [], + "start": 45, + "end": 101, + "tag": { + "type": "Identifier", + "start": 67, + "end": 70, + "loc": { + "start": { + "line": 2, + "column": 22 + }, + "end": { + "line": 2, + "column": 25 + } + }, + "name": "tag" + }, "attributes": [ { "type": "Attribute", @@ -49,31 +64,16 @@ "name": "class", "value": [ { - "type": "Text", "start": 79, "end": 82, - "data": "foo", - "raw": "foo" + "type": "Text", + "raw": "foo", + "data": "foo" } ] } ], - "tag": { - "start": 67, - "end": 70, - "loc": { - "start": { - "line": 2, - "column": 22 - }, - "end": { - "line": 2, - "column": 25 - } - }, - "name": "tag", - "type": "Identifier" - } + "children": [] } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json b/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json index 14ae36ae3a..a439b65dd0 100644 --- a/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json +++ b/packages/svelte/tests/parser-legacy/samples/dynamic-import/output.json @@ -1,8 +1,8 @@ { "html": { + "type": "Fragment", "start": null, "end": null, - "type": "Fragment", "children": [] }, "instance": { diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json index 9cbeb2be6c..d19f5cbbfd 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-destructured/output.json @@ -1,48 +1,32 @@ { "html": { + "type": "Fragment", "start": 41, "end": 112, - "type": "Fragment", "children": [ { + "type": "Text", "start": 39, "end": 41, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "EachBlock", "start": 41, "end": 112, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 48, - "end": 55, - "loc": { - "start": { - "line": 5, - "column": 7 - }, - "end": { - "line": 5, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 83, "end": 104, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 86, "end": 91, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 87, @@ -61,16 +45,16 @@ } }, { + "type": "Text", "start": 91, "end": 93, - "type": "Text", "raw": ": ", "data": ": " }, { + "type": "MustacheTag", "start": 93, "end": 100, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 94, @@ -170,6 +154,22 @@ } } ] + }, + "expression": { + "type": "Identifier", + "start": 48, + "end": 55, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 14 + } + }, + "name": "animals" } } ] diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json index 935195985f..5af3bff86d 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-else/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 77, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 77, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 14, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 27, "end": 42, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 30, "end": 38, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 31, @@ -62,22 +46,38 @@ "start": 18, "end": 24 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "animals" + }, "else": { + "type": "ElseBlock", "start": 50, "end": 70, - "type": "ElseBlock", "children": [ { + "type": "Element", "start": 52, "end": 69, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 55, "end": 65, - "type": "Text", "raw": "no animals", "data": "no animals" } diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json index ff5d574326..915dd6228b 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-indexed/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 58, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 58, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 14, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 30, "end": 50, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 33, "end": 36, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 34, @@ -54,16 +38,16 @@ } }, { + "type": "Text", "start": 36, "end": 38, - "type": "Text", "raw": ": ", "data": ": " }, { + "type": "MustacheTag", "start": 38, "end": 46, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 39, @@ -90,6 +74,22 @@ "start": 18, "end": 24 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "animals" + }, "index": "i" } ] diff --git a/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json b/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json index e4e9689851..9582d45465 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block-keyed/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 54, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 54, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 12, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 12 - } - }, - "name": "todos" - }, "children": [ { + "type": "Element", "start": 33, "end": 46, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 36, "end": 42, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 37, @@ -62,6 +46,22 @@ "start": 16, "end": 20 }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "todos" + }, "key": { "type": "MemberExpression", "start": 22, diff --git a/packages/svelte/tests/parser-legacy/samples/each-block/output.json b/packages/svelte/tests/parser-legacy/samples/each-block/output.json index 39373ea2a5..1b418dac5a 100644 --- a/packages/svelte/tests/parser-legacy/samples/each-block/output.json +++ b/packages/svelte/tests/parser-legacy/samples/each-block/output.json @@ -1,41 +1,25 @@ { "html": { + "type": "Fragment", "start": 0, "end": 50, - "type": "Fragment", "children": [ { + "type": "EachBlock", "start": 0, "end": 50, - "type": "EachBlock", - "expression": { - "type": "Identifier", - "start": 7, - "end": 14, - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 14 - } - }, - "name": "animals" - }, "children": [ { + "type": "Element", "start": 27, "end": 42, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "MustacheTag", "start": 30, "end": 38, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 31, @@ -61,6 +45,22 @@ "name": "animal", "start": 18, "end": 24 + }, + "expression": { + "type": "Identifier", + "start": 7, + "end": 14, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 14 + } + }, + "name": "animals" } } ] diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json index 8e8768eb62..7773256d44 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-attribute-empty-string/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 43, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 21, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 6, "end": 13, - "type": "Attribute", "name": "attr", "value": [ { @@ -29,22 +29,22 @@ "children": [] }, { + "type": "Text", "start": 21, "end": 22, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 22, "end": 43, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 28, "end": 35, - "type": "Attribute", "name": "attr", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json index a80344101e..9477886bb2 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-attribute/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 49, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 24, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 6, "end": 16, - "type": "Attribute", "name": "attr", "value": [ { @@ -29,22 +29,22 @@ "children": [] }, { + "type": "Text", "start": 24, "end": 25, - "type": "Text", "raw": "\n", "data": "\n" }, { + "type": "Element", "start": 25, "end": 49, - "type": "Element", "name": "span", "attributes": [ { + "type": "Attribute", "start": 31, "end": 41, - "type": "Attribute", "name": "attr", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json index cb4ededf05..ce0dc25e85 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-mustache/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 22, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 22, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 4, "end": 10, - "type": "Text", "raw": "hello ", "data": "hello " }, { + "type": "MustacheTag", "start": 10, "end": 16, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 11, @@ -40,9 +40,9 @@ } }, { + "type": "Text", "start": 16, "end": 17, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json b/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json index 0106e75b45..fde57c470a 100644 --- a/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json +++ b/packages/svelte/tests/parser-legacy/samples/element-with-text/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 0, "end": 17, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 17, - "type": "Element", "name": "span", "attributes": [], "children": [ { + "type": "Text", "start": 6, "end": 10, - "type": "Text", "raw": "test", "data": "test" } diff --git a/packages/svelte/tests/parser-legacy/samples/elements/output.json b/packages/svelte/tests/parser-legacy/samples/elements/output.json index c530d4376c..6b51383d93 100644 --- a/packages/svelte/tests/parser-legacy/samples/elements/output.json +++ b/packages/svelte/tests/parser-legacy/samples/elements/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 15, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 15, - "type": "Element", "name": "!doctype", "attributes": [ { + "type": "Attribute", "start": 10, "end": 14, - "type": "Attribute", "name": "html", "value": true } diff --git a/packages/svelte/tests/parser-legacy/samples/event-handler/output.json b/packages/svelte/tests/parser-legacy/samples/event-handler/output.json index e19804f20f..45b6256677 100644 --- a/packages/svelte/tests/parser-legacy/samples/event-handler/output.json +++ b/packages/svelte/tests/parser-legacy/samples/event-handler/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 97, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 61, - "type": "Element", "name": "button", "attributes": [ { @@ -105,25 +105,25 @@ ], "children": [ { + "type": "Text", "start": 46, "end": 52, - "type": "Text", "raw": "toggle", "data": "toggle" } ] }, { + "type": "Text", "start": 61, "end": 63, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "IfBlock", "start": 63, "end": 97, - "type": "IfBlock", "expression": { "type": "Identifier", "start": 68, @@ -142,16 +142,16 @@ }, "children": [ { + "type": "Element", "start": 78, "end": 91, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 81, "end": 87, - "type": "Text", "raw": "hello!", "data": "hello!" } diff --git a/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json b/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json index 6427685583..4ba9370d88 100644 --- a/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json +++ b/packages/svelte/tests/parser-legacy/samples/if-block-else/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 51, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 51, - "type": "IfBlock", "expression": { "type": "Identifier", "start": 5, @@ -26,16 +26,16 @@ }, "children": [ { + "type": "Element", "start": 11, "end": 21, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 14, "end": 17, - "type": "Text", "raw": "foo", "data": "foo" } @@ -43,21 +43,21 @@ } ], "else": { + "type": "ElseBlock", "start": 29, "end": 46, - "type": "ElseBlock", "children": [ { + "type": "Element", "start": 31, "end": 45, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 34, "end": 41, - "type": "Text", "raw": "not foo", "data": "not foo" } diff --git a/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json b/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json index 2fdeddcf8c..3e6953ab0b 100644 --- a/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json +++ b/packages/svelte/tests/parser-legacy/samples/if-block-elseif/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 89, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 89, - "type": "IfBlock", "expression": { "type": "BinaryExpression", "start": 5, @@ -59,16 +59,16 @@ }, "children": [ { + "type": "Element", "start": 14, "end": 41, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 17, "end": 37, - "type": "Text", "raw": "x is greater than 10", "data": "x is greater than 10" } @@ -76,15 +76,14 @@ } ], "else": { + "type": "ElseBlock", "start": 58, "end": 84, - "type": "ElseBlock", "children": [ { + "type": "IfBlock", "start": 58, "end": 89, - "type": "IfBlock", - "elseif": true, "expression": { "type": "BinaryExpression", "start": 52, @@ -136,22 +135,23 @@ }, "children": [ { + "type": "Element", "start": 60, "end": 83, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 63, "end": 79, - "type": "Text", "raw": "x is less than 5", "data": "x is less than 5" } ] } - ] + ], + "elseif": true } ] } diff --git a/packages/svelte/tests/parser-legacy/samples/if-block/output.json b/packages/svelte/tests/parser-legacy/samples/if-block/output.json index 4c6fd97520..61ecf6e3b2 100644 --- a/packages/svelte/tests/parser-legacy/samples/if-block/output.json +++ b/packages/svelte/tests/parser-legacy/samples/if-block/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 17, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 17, - "type": "IfBlock", "expression": { "type": "Identifier", "start": 5, @@ -26,9 +26,9 @@ }, "children": [ { + "type": "Text", "start": 9, "end": 12, - "type": "Text", "raw": "bar", "data": "bar" } diff --git a/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json b/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json index d46de69dd6..a56701894e 100644 --- a/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json +++ b/packages/svelte/tests/parser-legacy/samples/implicitly-closed-li/output.json @@ -1,66 +1,66 @@ { "html": { + "type": "Fragment", "start": 0, "end": 31, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 31, - "type": "Element", "name": "ul", "attributes": [], "children": [ { + "type": "Text", "start": 4, "end": 6, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 6, "end": 13, - "type": "Element", "name": "li", "attributes": [], "children": [ { + "type": "Text", "start": 10, "end": 13, - "type": "Text", "raw": "a\n\t", "data": "a\n\t" } ] }, { + "type": "Element", "start": 13, "end": 20, - "type": "Element", "name": "li", "attributes": [], "children": [ { + "type": "Text", "start": 17, "end": 20, - "type": "Text", "raw": "b\n\t", "data": "b\n\t" } ] }, { + "type": "Element", "start": 20, "end": 26, - "type": "Element", "name": "li", "attributes": [], "children": [ { + "type": "Text", "start": 24, "end": 26, - "type": "Text", "raw": "c\n", "data": "c\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/nbsp/output.json b/packages/svelte/tests/parser-legacy/samples/nbsp/output.json index ec231bbe73..7d2158955a 100644 --- a/packages/svelte/tests/parser-legacy/samples/nbsp/output.json +++ b/packages/svelte/tests/parser-legacy/samples/nbsp/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 0, "end": 19, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 19, - "type": "Element", "name": "span", "attributes": [], "children": [ { + "type": "Text", "start": 6, "end": 12, - "type": "Text", "raw": " ", "data": " " } diff --git a/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json b/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json index 4350d3cb5b..79a26426de 100644 --- a/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json +++ b/packages/svelte/tests/parser-legacy/samples/no-error-if-before-closing/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 148, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 33, - "type": "IfBlock", "expression": { "type": "Literal", "start": 5, @@ -27,32 +27,32 @@ }, "children": [ { + "type": "Element", "start": 12, "end": 19, - "type": "Element", "name": "input", "attributes": [], "children": [] } ], "else": { + "type": "ElseBlock", "start": 27, "end": 28, - "type": "ElseBlock", "children": [] } }, { + "type": "Text", "start": 33, "end": 35, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "IfBlock", "start": 35, "end": 65, - "type": "IfBlock", "expression": { "type": "Literal", "start": 40, @@ -72,32 +72,32 @@ }, "children": [ { + "type": "Element", "start": 47, "end": 51, - "type": "Element", "name": "br", "attributes": [], "children": [] } ], "else": { + "type": "ElseBlock", "start": 59, "end": 60, - "type": "ElseBlock", "children": [] } }, { + "type": "Text", "start": 65, "end": 67, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "AwaitBlock", "start": 67, "end": 108, - "type": "AwaitBlock", "expression": { "type": "Literal", "start": 75, @@ -123,29 +123,29 @@ }, "error": null, "pending": { + "type": "PendingBlock", "start": 80, "end": 90, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 80, "end": 82, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 82, "end": 89, - "type": "Element", "name": "input", "attributes": [], "children": [] }, { + "type": "Text", "start": 89, "end": 90, - "type": "Text", "raw": "\n", "data": "\n" } @@ -153,14 +153,14 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": 90, "end": 100, - "type": "ThenBlock", "children": [ { + "type": "Text", "start": 99, "end": 100, - "type": "Text", "raw": "\n", "data": "\n" } @@ -168,24 +168,24 @@ "skip": false }, "catch": { + "type": "CatchBlock", "start": null, "end": null, - "type": "CatchBlock", "children": [], "skip": true } }, { + "type": "Text", "start": 108, "end": 110, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "AwaitBlock", "start": 110, "end": 148, - "type": "AwaitBlock", "expression": { "type": "Literal", "start": 118, @@ -211,29 +211,29 @@ }, "error": null, "pending": { + "type": "PendingBlock", "start": 123, "end": 130, - "type": "PendingBlock", "children": [ { + "type": "Text", "start": 123, "end": 125, - "type": "Text", "raw": "\n\t", "data": "\n\t" }, { + "type": "Element", "start": 125, "end": 129, - "type": "Element", "name": "br", "attributes": [], "children": [] }, { + "type": "Text", "start": 129, "end": 130, - "type": "Text", "raw": "\n", "data": "\n" } @@ -241,14 +241,14 @@ "skip": false }, "then": { + "type": "ThenBlock", "start": 130, "end": 140, - "type": "ThenBlock", "children": [ { + "type": "Text", "start": 139, "end": 140, - "type": "Text", "raw": "\n", "data": "\n" } @@ -256,9 +256,9 @@ "skip": false }, "catch": { + "type": "CatchBlock", "start": null, "end": null, - "type": "CatchBlock", "children": [], "skip": true } diff --git a/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json b/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json index 3c349b472f..7db0bb7ec1 100644 --- a/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json +++ b/packages/svelte/tests/parser-legacy/samples/raw-mustaches/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 34, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 34, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 3, "end": 4, - "type": "Text", "raw": " ", "data": " " }, { + "type": "RawMustacheTag", "start": 4, "end": 16, - "type": "RawMustacheTag", "expression": { "type": "Identifier", "start": 11, @@ -40,16 +40,16 @@ } }, { + "type": "Text", "start": 16, "end": 17, - "type": "Text", "raw": " ", "data": " " }, { + "type": "RawMustacheTag", "start": 17, "end": 29, - "type": "RawMustacheTag", "expression": { "type": "Identifier", "start": 24, @@ -68,9 +68,9 @@ } }, { + "type": "Text", "start": 29, "end": 30, - "type": "Text", "raw": " ", "data": " " } diff --git a/packages/svelte/tests/parser-legacy/samples/refs/output.json b/packages/svelte/tests/parser-legacy/samples/refs/output.json index 75f5d2d52a..e2bda741fa 100644 --- a/packages/svelte/tests/parser-legacy/samples/refs/output.json +++ b/packages/svelte/tests/parser-legacy/samples/refs/output.json @@ -1,20 +1,20 @@ { "html": { + "type": "Fragment", "start": 30, "end": 63, - "type": "Fragment", "children": [ { + "type": "Text", "start": 28, "end": 30, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 30, "end": 63, - "type": "Element", "name": "canvas", "attributes": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json b/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json index af4ec56a68..e0b50e3b92 100644 --- a/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json +++ b/packages/svelte/tests/parser-legacy/samples/script-attribute-with-curly-braces/output.json @@ -1,34 +1,34 @@ { "html": { + "type": "Fragment", "start": 79, "end": 101, - "type": "Fragment", "children": [ { + "type": "Text", "start": 77, "end": 79, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 79, "end": 101, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 83, "end": 89, - "type": "Text", "raw": "Hello ", "data": "Hello " }, { + "type": "MustacheTag", "start": 89, "end": 95, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 90, @@ -47,9 +47,9 @@ } }, { + "type": "Text", "start": 95, "end": 96, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json b/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json index 072e101782..03a526f04e 100644 --- a/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json +++ b/packages/svelte/tests/parser-legacy/samples/script-context-module-unquoted/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 62, "end": 60, - "type": "Fragment", "children": [ { + "type": "Text", "start": 60, "end": 62, - "type": "Text", "raw": "\n\n", "data": "\n\n" } diff --git a/packages/svelte/tests/parser-legacy/samples/script/output.json b/packages/svelte/tests/parser-legacy/samples/script/output.json index efa31bddb8..d3d4abd6b2 100644 --- a/packages/svelte/tests/parser-legacy/samples/script/output.json +++ b/packages/svelte/tests/parser-legacy/samples/script/output.json @@ -1,34 +1,34 @@ { "html": { + "type": "Fragment", "start": 41, "end": 63, - "type": "Fragment", "children": [ { + "type": "Text", "start": 39, "end": 41, - "type": "Text", "raw": "\n\n", "data": "\n\n" }, { + "type": "Element", "start": 41, "end": 63, - "type": "Element", "name": "h1", "attributes": [], "children": [ { + "type": "Text", "start": 45, "end": 51, - "type": "Text", "raw": "Hello ", "data": "Hello " }, { + "type": "MustacheTag", "start": 51, "end": 57, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 52, @@ -47,9 +47,9 @@ } }, { + "type": "Text", "start": 57, "end": 58, - "type": "Text", "raw": "!", "data": "!" } diff --git a/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json b/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json index 34052615d7..f0ba3a5c0d 100644 --- a/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json +++ b/packages/svelte/tests/parser-legacy/samples/self-closing-element/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 6, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 6, - "type": "Element", "name": "div", "attributes": [], "children": [] diff --git a/packages/svelte/tests/parser-legacy/samples/self-reference/output.json b/packages/svelte/tests/parser-legacy/samples/self-reference/output.json index 77bffaaa3d..34310fcce4 100644 --- a/packages/svelte/tests/parser-legacy/samples/self-reference/output.json +++ b/packages/svelte/tests/parser-legacy/samples/self-reference/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 57, - "type": "Fragment", "children": [ { + "type": "IfBlock", "start": 0, "end": 57, - "type": "IfBlock", "expression": { "type": "BinaryExpression", "start": 5, @@ -59,21 +59,21 @@ }, "children": [ { - "start": 17, - "end": 51, "type": "InlineComponent", "name": "svelte:self", + "start": 17, + "end": 51, "attributes": [ { + "type": "Attribute", "start": 30, "end": 49, - "type": "Attribute", "name": "depth", "value": [ { + "type": "MustacheTag", "start": 37, "end": 48, - "type": "MustacheTag", "expression": { "type": "BinaryExpression", "start": 38, diff --git a/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json b/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json index 7028a367ff..90ded68103 100644 --- a/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json +++ b/packages/svelte/tests/parser-legacy/samples/slotted-element/output.json @@ -1,26 +1,26 @@ { "html": { + "type": "Fragment", "start": 0, "end": 45, - "type": "Fragment", "children": [ { + "type": "InlineComponent", "start": 0, "end": 45, - "type": "InlineComponent", "name": "Component", "attributes": [], "children": [ { + "type": "Element", "start": 11, "end": 33, - "type": "Element", "name": "div", "attributes": [ { + "type": "Attribute", "start": 16, "end": 26, - "type": "Attribute", "name": "slot", "value": [ { diff --git a/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json b/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json index c552a84b02..bb6de0bea9 100644 --- a/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json +++ b/packages/svelte/tests/parser-legacy/samples/space-between-mustaches/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 24, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 24, - "type": "Element", "name": "p", "attributes": [], "children": [ { + "type": "Text", "start": 3, "end": 4, - "type": "Text", "raw": " ", "data": " " }, { + "type": "MustacheTag", "start": 4, "end": 7, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 5, @@ -40,16 +40,16 @@ } }, { + "type": "Text", "start": 7, "end": 8, - "type": "Text", "raw": " ", "data": " " }, { + "type": "MustacheTag", "start": 8, "end": 11, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 9, @@ -68,16 +68,16 @@ } }, { + "type": "Text", "start": 11, "end": 14, - "type": "Text", "raw": " : ", "data": " : " }, { + "type": "MustacheTag", "start": 14, "end": 17, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 15, @@ -96,9 +96,9 @@ } }, { + "type": "Text", "start": 17, "end": 20, - "type": "Text", "raw": " : ", "data": " : " } diff --git a/packages/svelte/tests/parser-legacy/samples/spread/output.json b/packages/svelte/tests/parser-legacy/samples/spread/output.json index e0a45f9d75..3b79aa9670 100644 --- a/packages/svelte/tests/parser-legacy/samples/spread/output.json +++ b/packages/svelte/tests/parser-legacy/samples/spread/output.json @@ -1,19 +1,19 @@ { "html": { + "type": "Fragment", "start": 0, "end": 22, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 22, - "type": "Element", "name": "div", "attributes": [ { + "type": "Spread", "start": 5, "end": 15, - "type": "Spread", "expression": { "type": "Identifier", "start": 9, diff --git a/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json b/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json index 5082c55343..733f9e76ff 100644 --- a/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json +++ b/packages/svelte/tests/parser-legacy/samples/style-inside-head/output.json @@ -1,27 +1,27 @@ { "html": { + "type": "Fragment", "start": 0, "end": 42, - "type": "Fragment", "children": [ { - "start": 0, - "end": 42, "type": "Head", "name": "svelte:head", + "start": 0, + "end": 42, "attributes": [], "children": [ { + "type": "Element", "start": 13, "end": 28, - "type": "Element", "name": "style", "attributes": [], "children": [ { + "type": "Text", "start": 20, "end": 20, - "type": "Text", "data": "" } ] diff --git a/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json b/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json index cdb4b885d1..60477807b7 100644 --- a/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json +++ b/packages/svelte/tests/parser-legacy/samples/textarea-children/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 61, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 61, - "type": "Element", "name": "textarea", "attributes": [], "children": [ @@ -19,9 +19,9 @@ "data": "\n\t

not actually an element. " }, { + "type": "MustacheTag", "start": 40, "end": 45, - "type": "MustacheTag", "expression": { "type": "Identifier", "start": 41, diff --git a/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json b/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json index 9693db2729..ed3c85b09a 100644 --- a/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json +++ b/packages/svelte/tests/parser-legacy/samples/textarea-end-tag/output.json @@ -1,13 +1,13 @@ { "html": { + "type": "Fragment", "start": 0, "end": 117, - "type": "Fragment", "children": [ { + "type": "Element", "start": 0, "end": 117, - "type": "Element", "name": "textarea", "attributes": [], "children": [ @@ -19,9 +19,9 @@ "data": "\n\t

not actu

{item.description}

{/each} -

{numCompleted} completed

\ No newline at end of file +

{numCompleted} completed

diff --git a/packages/svelte/tests/runtime-legacy/shared.ts b/packages/svelte/tests/runtime-legacy/shared.ts index 6ffffe33ef..a1944d47e5 100644 --- a/packages/svelte/tests/runtime-legacy/shared.ts +++ b/packages/svelte/tests/runtime-legacy/shared.ts @@ -173,6 +173,11 @@ async function run_test_variant( window.document.head.innerHTML = styles ? `` : ''; window.document.body.innerHTML = '
'; + window.addEventListener('error', (e) => { + unhandled_rejection = e.error; + e.preventDefault(); + }); + let mod = await import(`${cwd}/_output/client/main.svelte.js`); const target = window.document.querySelector('main') as HTMLElement; diff --git a/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js b/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js index 399f29a435..0dac8c623b 100644 --- a/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/each-mutation/_config.js @@ -4,7 +4,7 @@ export default test({ html: ` - + `, async test({ assert, target }) { @@ -25,7 +25,7 @@ export default test({ ` - + ` ); } diff --git a/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte b/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte index 5bf92b274b..6e0223d93b 100644 --- a/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/each-mutation/main.svelte @@ -1,20 +1,20 @@ -{#each works1 as item, i} - +{#each a as item, i} + {/each} -{#each works2 as item} +{#each b as item} {/each} -{#each doesntwork as item} +{#each c as item} {/each} diff --git a/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte index b83c73b958..6f19672425 100644 --- a/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/effect-order/main.svelte @@ -1,21 +1,20 @@ -

Hello {myList[0].age}

+

{s}

- + diff --git a/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js b/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js index 90c7fd7f32..4c425bc5f7 100644 --- a/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js +++ b/packages/svelte/tests/runtime-runes/samples/nullish-operator/_config.js @@ -8,6 +8,6 @@ export default test({ async test({ assert, target, component }) { await Promise.resolve(); await Promise.resolve(); - assert.deepEqual(component.log, ['a1: ', {}, 'b1: ', {}]); + assert.deepEqual(component.log, ['a1: ', true, 'b1: ', true]); } }); diff --git a/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte b/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte index 8eb78927c1..892973063a 100644 --- a/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/nullish-operator/main.svelte @@ -3,26 +3,26 @@ let b1 = $state(); const {log} = $props(); - + $effect(() => { log.push('a1: ', a1); }); $effect(() => { log.push('b1: ', b1); }); - + a(); queueMicrotask(a); - + b(); queueMicrotask(b); - + function a() { - a1 ??= {}; + a1 ??= true; } - + function b() { - b1 ?? (b1 = {}); + b1 ?? (b1 = true); } diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js new file mode 100644 index 0000000000..e878dad88f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-array/_config.js @@ -0,0 +1,66 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ` + + + + 1 + 2 + 3 + array[1]: 2 + `, + + async test({ assert, target }) { + const [add, clear, reverse] = target.querySelectorAll('button'); + + await add?.click(); + assert.htmlEqual( + target.innerHTML, + ` + + + + 1 + 2 + 3 + 4 + array[1]: 2 + ` + ); + + flushSync(() => { + reverse?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + ` + + + + 4 + 3 + 2 + 1 + array[1]: 3 + ` + ); + + flushSync(() => { + clear?.click(); + }); + + assert.htmlEqual( + target.innerHTML, + ` + + + + 4 + array[1]: + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte new file mode 100644 index 0000000000..f2a9f5cfcc --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-array/main.svelte @@ -0,0 +1,22 @@ + + + + + + + + +{#each array as n} + {n} +{/each} + +array[1]: {array[1]} diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js new file mode 100644 index 0000000000..185a198ca6 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte new file mode 100644 index 0000000000..f157772963 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-cyclical/main.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js new file mode 100644 index 0000000000..25a7172853 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-deep/_config.js @@ -0,0 +1,39 @@ +import { test } from '../../test'; + +export default test({ + html: ` + + + `, + + async test({ assert, target }) { + const [btn1, btn2] = target.querySelectorAll('button'); + + await btn1?.click(); + assert.htmlEqual( + target.innerHTML, + ` + + + ` + ); + + await btn2?.click(); + assert.htmlEqual( + target.innerHTML, + ` + + + ` + ); + + await btn1?.click(); + assert.htmlEqual( + target.innerHTML, + ` + + + ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte new file mode 100644 index 0000000000..0acba3173d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-deep/main.svelte @@ -0,0 +1,17 @@ + + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js new file mode 100644 index 0000000000..fdf60d91fd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-nested/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte new file mode 100644 index 0000000000..a0f78ba3bb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-nested/main.svelte @@ -0,0 +1,15 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte new file mode 100644 index 0000000000..d1be326830 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/Counter.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js new file mode 100644 index 0000000000..bdab6c9e39 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/_config.js @@ -0,0 +1,21 @@ +import { test } from '../../test'; + +export default test({ + html: ` + +

object.count: 0

+ `, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual( + target.innerHTML, + ` + +

object.count: 1

+ ` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte new file mode 100644 index 0000000000..d9d6d7ec30 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-bound/main.svelte @@ -0,0 +1,9 @@ + + + + +

object.count: {object.count}

diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte new file mode 100644 index 0000000000..d1be326830 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/Counter.svelte @@ -0,0 +1,8 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js new file mode 100644 index 0000000000..5e0eb0e2da --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/_config.js @@ -0,0 +1,18 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + compileOptions: { + dev: true + }, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, ``); + }, + + runtime_error: 'Props are read-only, unless used with `bind:`' +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte new file mode 100644 index 0000000000..e9617af17d --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy-prop-readonly/main.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/runtime-runes/samples/proxy/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy/_config.js new file mode 100644 index 0000000000..fdf60d91fd --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + html: ``, + + async test({ assert, target }) { + const btn = target.querySelector('button'); + await btn?.click(); + + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/proxy/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy/main.svelte new file mode 100644 index 0000000000..b83d9ec2b0 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/proxy/main.svelte @@ -0,0 +1,7 @@ + + + diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index ab886d5651..8f8f96c12e 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -112,10 +112,7 @@ describe('signals', () => { const A = $.source(0); const B = $.source(0); const C = $.derived(() => ($.get(A) % 2) + ($.get(B) % 2)); - const D = $.derived( - () => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2)), - (l: number[], r: number[]) => l.length === r.length && l.every((v, i) => v === r[i]) - ); + const D = $.derived(() => numbers.map((i) => i + ($.get(A) % 2) - ($.get(B) % 2))); const E = $.derived(() => hard($.get(C) + $.get(A) + $.get(D)[0]!, 'E')); const F = $.derived(() => hard($.get(D)[0]! && $.get(B), 'F')); const G = $.derived(() => $.get(C) + ($.get(C) || $.get(E) % 2) + $.get(D)[0]! + $.get(F)); diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js index 836fd0ed08..4add64c66f 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js @@ -26,4 +26,4 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro } $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js index 6f53521673..713b4e1337 100644 --- a/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js +++ b/packages/svelte/tests/snapshot/samples/dynamic-attributes-casing/_expected/client/main.svelte.js @@ -9,8 +9,8 @@ export default function Main($$anchor, $$props) { $.push($$props, true); // needs to be a snapshot test because jsdom does auto-correct the attribute casing - let x = $.source('test'); - let y = $.source(() => 'test'); + let x = 'test'; + let y = () => 'test'; /* Init */ var fragment = $.open_frag($$anchor, false, frag); var node = $.child_frag(fragment); @@ -21,28 +21,28 @@ export default function Main($$anchor, $$props) { var custom_element_1 = $.sibling($.sibling(svg_1)); /* Update */ - $.attr_effect(div, "foobar", () => $.get(y)()); - $.attr_effect(svg_1, "viewBox", () => $.get(y)()); - $.set_custom_element_data_effect(custom_element_1, "fooBar", () => $.get(y)()); + $.attr_effect(div, "foobar", () => y()); + $.attr_effect(svg_1, "viewBox", () => y()); + $.set_custom_element_data_effect(custom_element_1, "fooBar", () => y()); var node_foobar; var svg_viewBox; var custom_element_fooBar; $.render_effect(() => { - if (node_foobar !== (node_foobar = $.get(x))) { + if (node_foobar !== (node_foobar = x)) { $.attr(node, "foobar", node_foobar); } - if (svg_viewBox !== (svg_viewBox = $.get(x))) { + if (svg_viewBox !== (svg_viewBox = x)) { $.attr(svg, "viewBox", svg_viewBox); } - if (custom_element_fooBar !== (custom_element_fooBar = $.get(x))) { + if (custom_element_fooBar !== (custom_element_fooBar = x)) { $.set_custom_element_data(custom_element, "fooBar", custom_element_fooBar); } }); $.close_frag($$anchor, fragment); $.pop(); -} \ No newline at end of file +} diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js new file mode 100644 index 0000000000..c9ba1e7c73 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/client/index.svelte.js @@ -0,0 +1,4 @@ +/* index.svelte.js generated by Svelte VERSION */ +import * as $ from "svelte/internal"; + +export const object = $.proxy({ ok: true }); diff --git a/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js new file mode 100644 index 0000000000..770f2e5e4f --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/export-state/_expected/server/index.svelte.js @@ -0,0 +1,5 @@ +/* index.svelte.js generated by Svelte VERSION */ +import * as $ from "svelte/internal/server"; + +export const object = { ok: true }; + diff --git a/packages/svelte/tests/snapshot/samples/export-state/index.svelte.js b/packages/svelte/tests/snapshot/samples/export-state/index.svelte.js new file mode 100644 index 0000000000..023fcb9b35 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/export-state/index.svelte.js @@ -0,0 +1,3 @@ +export const object = $state({ + ok: true +}); diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js index fb455beeff..8fb2fbcf65 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/client/index.svelte.js @@ -9,7 +9,7 @@ export default function Function_prop_no_getter($$anchor, $$props) { let count = $.source(0); function onmouseup() { - $.set(count, $.get(count) + 2); + $.set(count, $.proxy($.get(count) + 2)); } /* Init */ @@ -17,7 +17,7 @@ export default function Function_prop_no_getter($$anchor, $$props) { var node = $.child_frag(fragment); Button(node, { - onmousedown: () => $.set(count, $.get(count) + 1), + onmousedown: () => $.set(count, $.proxy($.get(count) + 1)), onmouseup, children: ($$anchor, $$slotProps) => { /* Init */ @@ -31,4 +31,4 @@ export default function Function_prop_no_getter($$anchor, $$props) { $.close_frag($$anchor, fragment); $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js index 578409e0a4..ffd6a820bc 100644 --- a/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/function-prop-no-getter/_expected/server/index.svelte.js @@ -25,4 +25,4 @@ export default function Function_prop_no_getter($$payload, $$props) { $$payload.out += `${anchor}`; $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js index 263b00e526..b4df70d14c 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/client/index.svelte.js @@ -6,7 +6,7 @@ import * as $ from "svelte/internal"; export default function Svelte_element($$anchor, $$props) { $.push($$props, true); - let tag = $.prop_source($$props, "tag", 'hr', false); + let tag = $.prop_source($$props, "tag", true, 'hr'); /* Init */ var fragment = $.comment($$anchor); var node = $.child_frag(fragment); diff --git a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js index 6c4e6ee8b0..405a452271 100644 --- a/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/svelte-element/_expected/server/index.svelte.js @@ -24,4 +24,4 @@ export default function Svelte_element($$payload, $$props) { $$payload.out += `${anchor}`; $.bind_props($$props, { tag }); $.pop(); -} +} \ No newline at end of file diff --git a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/_config.js b/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/_config.js deleted file mode 100644 index f47bee71df..0000000000 --- a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from '../../test'; - -export default test({}); diff --git a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte b/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte deleted file mode 100644 index eb4e709f4d..0000000000 --- a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/input.svelte +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json b/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json deleted file mode 100644 index 5d2b639c8d..0000000000 --- a/packages/svelte/tests/validator/samples/runes-state-rune-not-mutated/warnings.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "code": "state-not-mutated", - "end": { - "column": 11, - "line": 3 - }, - "message": "count is declared with $state(...) but is never updated. Did you mean to create a function that changes its value?", - "start": { - "column": 6, - "line": 3 - } - } -] 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 index e763dbecd2..8d84544217 100644 --- 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 @@ -40,6 +40,26 @@ class Todo { > In this example, the compiler transforms `done` and `text` into `get`/`set` methods on the class prototype referencing private fields +Objects and arrays [are made reactive](/#H4sIAAAAAAAAE42QwWrDMBBEf2URhUhUNEl7c21DviPOwZY3jVpZEtIqUBz9e-UUt9BTj7M784bdmZ21wciq48xsPyGr2MF7Jhl9-kXEKxrCoqNLQS2TOqqgPbWd7cgggU3TgCFCAw-RekJ-3Et4lvByEq-drbe_dlsPichZcFYZrT6amQto2pXw5FO88FUYtG90gUfYi3zvWrYL75vxL57zfA07_zfr23k1vjtt-aZ0bQTcbrDL5ZifZcAxKeS8lzDc8X0xDhJ2ItdbX1jlOZMb9VnjyCoKCfMpfwG975NFVwEAAA==): + +```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. @@ -235,6 +255,8 @@ type MyProps = any; let { a, b, c, ...everythingElse } = $props(); ``` +Props cannot be mutated, unless the parent component uses `bind:`. During development, attempts to mutate props will result in an error. + ### 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. diff --git a/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md b/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md index efc692f706..f257769adf 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md +++ b/sites/svelte-5-preview/src/routes/docs/content/02-examples/02-fine-grained-reactivity.md @@ -44,22 +44,13 @@ In Svelte 4, reactivity centres on the _component_ and the top-level state decla Worse, everything inside the `each` block needs to be checked for updates. When a list gets large enough, this behaviour has the potential to cause performance headaches. -With runes, it's easy to make reactivity _fine-grained_, meaning that things will only update when they need to. Mark `todos` as `$state`, create a `Todo` class with `done` and `text` state fields, then instantiate the class inside `addTodo`: +With runes, it's easy to make reactivity _fine-grained_, meaning that things will only update when they need to: ```diff ``` -In [this version of the app](/#H4sIAAAAAAAAE21SwW6DMAz9lSybBEhTuDNA2mF_sFvpIUtMGzVNUGK6TYh_XxKgSNtOsWO_Z_vZE-2VBk-rw0QNvwKt6Osw0GeK30N0_A00QvC9HZ2IP7UXTg3YdqZDDUjQSutJQ548coT8cCxeQigEhebek_cQJlP0O5TWwJ7Zc-0hJYcQwhfuoY0ikFjj0Y0CrctjTrFxBchZebbi4rMyzfGZF3w_GoHKGuLgypVR5pSndu8skd5qYNqe8syB4FqMmmNIzLbOHODozDImC2IhuERCmpY8RIPFsQqmwZzw_PJfdS5llCGHG5h9AtWT5Ydd4Js8NA3J3kxgzwqy1LyLsEl8YIwl-5kY-CQ7J0PuToDsxvUIxfEO_BsMLFm2NVmX-y5NrcwwIrGmCu1I-2maae17JmXKmB6Bi_O6cO6TkdSupbq1S8WV5UMZWaWCzZQ0igtaefaseGNNR8UZxOXDfnV0wSUf5IqM6m7IulwqTWXsJMlcD-30e7vzvu-6HNpwvVcrVa9A0iocE8zH-QeS_FSn-AIAAA==), editing the `text` of a todo won't cause unrelated things to be updated. - -## Gotchas - -If we only do the first step (adding `$state`) and skip the second (creating a class with state fields), [the app breaks](/#H4sIAAAAAAAAE2VSyU7DMBD9FWOQ0kqVcw9JJA78AbemB2NPWgvXjuxJAVn5d7wkVILbLG_mvVkCHZUGT5tjoIZfgTb0ZZrogeL3lBx_A40QfW9nJ1Kk9cKpCfvBDKgBCVppPenIk0eOsDue9s8xFZPjbAQqa4iDK1dGmfMuY_ckpPSAwhpvNTBtz7vKgeBazJpjBFa5R4Q4wNmZwsGiUgSXm5CuJw_JYNIa2DMN5oyXXLT8YedSvkXgDm5g8JdbjaRE2Ad8k4euI9Wrid2rPSmc6xADbvMdizsgYyzHDlsgbMaASU1DRq49HO5RhC9sSKFD7s6A7Mb1DBtiKcbpl_M_NAqoqm2-tr7fwLTKTDMSa5o4ibSfpgvryAupMyI8AheX9VDcZyNTtlLd-sK4dnlXRjaZsAt5vUn62ueOSr_RDVRcQHy826-Blrrsg1wr0yq2yrYuTKFOSvKF2qkPfx9jub9KW099_LqrlWpUIGmDbobltPwAmGXpQrACAAA=) — toggling the checkboxes won't cause `remaining(todos)` to be recalculated. - -That's because in runes mode, Svelte no longer invalidates everything when you change something inside an `each` block. Previously, Svelte tried to statically determine the dependencies of the mutated value in order to invalidate them, causing confusing bugs related to overfiring (invalidating things that weren't actually affected) and underfiring (missing affected variables). It made apps slower by default and harder to reason about, especially in more complex scenarios. - -In runes mode, the rules around triggering updates are simpler: Only state declared with `$state` or `$derived` or `$props` causes a rerender. In the [broken example](/#H4sIAAAAAAAAE2VSyU7DMBD9FWOQ0kqVcw9JJA78AbemB2NPWgvXjuxJAVn5d7wkVILbLG_mvVkCHZUGT5tjoIZfgTb0ZZrogeL3lBx_A40QfW9nJ1Kk9cKpCfvBDKgBCVppPenIk0eOsDue9s8xFZPjbAQqa4iDK1dGmfMuY_ckpPSAwhpvNTBtz7vKgeBazJpjBFa5R4Q4wNmZwsGiUgSXm5CuJw_JYNIa2DMN5oyXXLT8YedSvkXgDm5g8JdbjaRE2Ad8k4euI9Wrid2rPSmc6xADbvMdizsgYyzHDlsgbMaASU1DRq49HO5RhC9sSKFD7s6A7Mb1DBtiKcbpl_M_NAqoqm2-tr7fwLTKTDMSa5o4ibSfpgvryAupMyI8AheX9VDcZyNTtlLd-sK4dnlXRjaZsAt5vUn62ueOSr_RDVRcQHy826-Blrrsg1wr0yq2yrYuTKFOSvKF2qkPfx9jub9KW099_LqrlWpUIGmDbobltPwAmGXpQrACAAA=), `todo` is declared by the `#each` block, and neither the `text` nor the `done` property are referencing state. One solution would be to turn `text` and `done` into `$state` fields, as shown above. [The other solution](/#H4sIAAAAAAAACmVS226jMBD9lam7EokUmXcKSH3YP9i3EK1ce0isOjayh7QV4t9rG2iq9m0uZ86Z28R6bTCw6jgxK67IKvY8DOzA6GNITrihIYx-cKOXKVIH6fVAbWc7MkhATrkADfwJJAh3x9P-KaZish-tJO0seLwKbbU97zJ2D1NKdySdDc4gN-68KzxKYeRoBEVgkTkixCON3i4aPHZK6DMJNC08JIMrZ3HPDdozXXLR_ENdKPUvAnd4Q0tf2rqHJcJf8QMemgaKvzayF3tYNNchOtrmO3LOs33YODpK4hX0wgQ8bDHCd6pg4Sbhz0j8JsyIS34-fRH_hkSVotiGqMv7om2t7TASOFvFdpV7s820zjVDmRHTIwp5Wa8hAvw_gE6roFrpW7soriwv2qoqCzZTxh_1iae2V647Mj1B0zF5Qfn64t47ttRmH9W36rSIrbouF8WpTB3lc9RDO_38gvn-F3U5tPHFrk7pXqNiFfkR59P8CWtDxuCdAgAA) would be to bind to `todos[i].text` instead of `todo.text` — this way, Svelte picks up the reference to the `todos` `$state` and invalidates it as a whole. Keep in mind that you lose the fine-grained reactivity this way — the whole array is invalidated on every keystroke. +In [this version of the app](/#H4sIAAAAAAAAE2VSy07EMAz8lRCQ2kqovZe2Egf-gBvlEBJ3N9qsUyXuAqr67-TRZSW4xfZ4xh5n5ZM24Hn7tnIUZ-Atf55n_sjpe46Bv4AhCLG3i5Mx03np9EzDiCMZIEZWWc969uBJEJRv79VTKIXitKAkbZE5OAuNGg9lwlZsjeWRpEVvDdTGHsrCgRRGLkZQABaJI0Ac0OIwa9RhUgKXSFg_sLv4qJVFqGoDeKBjatr-qAulXgOwhAsg_WrrieVMfYJvdtf3rHjBwF5ULGvuS4yUtefFH8u9d6Qo2rJJGA-P1xzBF7Usc5JwB6D6IswCub5Vv4T_IcG9orgO3zU3g7HTOC_ELLZhTGU_sV_3fTbWJMR6D0Ie9ysInx7RAuqUvgxZcWf50KjaJNivybs48s5zQ8XD9yOXR5CnD_s18tyXYlB7ZzTg2tk1WWlt4iTJ_m4e1r9X327_oGvmIXyps1V60qB4S26B7X37AXGd34ONAgAA), editing the `text` of a todo won't cause unrelated things to be updated. diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md index 6ffd8a9576..b498862c47 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/03-deprecations.md @@ -39,3 +39,7 @@ These functions run indiscriminately when _anything_ changes. By using `$effect. ``` Note that using `$effect` and `$effect.pre` will put you in [runes mode](/docs/runes) — be sure to update your props and state accordingly. + +## `immutable` + +The `immutable` compiler option is deprecated. Use runes mode instead, where all state is immutable (which means that assigning to `object.property` won't cause updates for anything that is observing `object` itself, or a different property of it).