Skip to content

Commit 1377c40

Browse files
committed
Merge branch 'main' into print
2 parents a39ab49 + e896c55 commit 1377c40

File tree

16 files changed

+149
-121
lines changed

16 files changed

+149
-121
lines changed

.prettierignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ packages/**/config/*.js
77

88
# packages/svelte
99
packages/svelte/messages/**/*.md
10+
packages/svelte/scripts/_bundle.js
1011
packages/svelte/src/compiler/errors.js
1112
packages/svelte/src/compiler/warnings.js
1213
packages/svelte/src/internal/client/errors.js
@@ -25,8 +26,7 @@ packages/svelte/tests/hydration/samples/*/_expected.html
2526
packages/svelte/tests/hydration/samples/*/_override.html
2627
packages/svelte/types
2728
packages/svelte/compiler/index.js
28-
playgrounds/sandbox/input/**.svelte
29-
playgrounds/sandbox/output
29+
playgrounds/sandbox/src/*
3030

3131
# sites/svelte.dev
3232
sites/svelte.dev/static/svelte-app.json

documentation/docs/07-misc/03-typescript.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ If you're using tools like Rollup or Webpack instead, install their respective S
8383

8484
When using TypeScript, make sure your `tsconfig.json` is setup correctly.
8585

86-
- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2022`, or a `target` of at least `ES2015` alongside [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields). This ensures that rune declarations on class fields are not messed with, which would break the Svelte compiler
86+
- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2015` so classes are not compiled to functions
8787
- Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is
8888
- Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do.
8989

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export default [
8787
'**/*.d.ts',
8888
'**/tests',
8989
'packages/svelte/scripts/process-messages/templates/*.js',
90+
'packages/svelte/scripts/_bundle.js',
9091
'packages/svelte/src/compiler/errors.js',
9192
'packages/svelte/src/internal/client/errors.js',
9293
'packages/svelte/src/internal/client/warnings.js',

packages/svelte/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# svelte
22

3+
## 5.34.9
4+
5+
### Patch Changes
6+
7+
- fix: ensure unowned deriveds can add themselves as reactions while connected ([#16249](https://github.com/sveltejs/svelte/pull/16249))
8+
39
## 5.34.8
410

511
### Patch Changes

packages/svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.34.8",
5+
"version": "5.34.9",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/scripts/check-treeshakeability.js

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -118,36 +118,40 @@ const bundle = await bundle_code(
118118
).js.code
119119
);
120120

121-
if (!bundle.includes('hydrate_node') && !bundle.includes('hydrate_next')) {
122-
// eslint-disable-next-line no-console
123-
console.error(`✅ Hydration code treeshakeable`);
124-
} else {
125-
failed = true;
126-
// eslint-disable-next-line no-console
127-
console.error(`❌ Hydration code not treeshakeable`);
128-
}
121+
/**
122+
* @param {string} case_name
123+
* @param {string[]} strings
124+
*/
125+
function check_bundle(case_name, ...strings) {
126+
for (const string of strings) {
127+
const index = bundle.indexOf(string);
128+
if (index >= 0) {
129+
// eslint-disable-next-line no-console
130+
console.error(`❌ ${case_name} not treeshakeable`);
131+
failed = true;
129132

130-
if (!bundle.includes('component_context.l')) {
131-
// eslint-disable-next-line no-console
132-
console.error(`✅ Legacy code treeshakeable`);
133-
} else {
134-
failed = true;
133+
let lines = bundle.slice(index - 500, index + 500).split('\n');
134+
const target_line = lines.findIndex((line) => line.includes(string));
135+
// mark the failed line
136+
lines = lines
137+
.map((line, i) => (i === target_line ? `> ${line}` : `| ${line}`))
138+
.slice(target_line - 5, target_line + 6);
139+
// eslint-disable-next-line no-console
140+
console.error('The first failed line:\n' + lines.join('\n'));
141+
return;
142+
}
143+
}
135144
// eslint-disable-next-line no-console
136-
console.error(`❌ Legacy code not treeshakeable`);
145+
console.error(`${case_name} treeshakeable`);
137146
}
138147

139-
if (!bundle.includes(`'CreatedAt'`)) {
140-
// eslint-disable-next-line no-console
141-
console.error(`✅ $inspect.trace code treeshakeable`);
142-
} else {
143-
failed = true;
144-
// eslint-disable-next-line no-console
145-
console.error(`❌ $inspect.trace code not treeshakeable`);
146-
}
148+
check_bundle('Hydration code', 'hydrate_node', 'hydrate_next');
149+
check_bundle('Legacy code', 'component_context.l');
150+
check_bundle('$inspect.trace', `'CreatedAt'`);
147151

148152
if (failed) {
149153
// eslint-disable-next-line no-console
150-
console.error(bundle);
154+
console.error('Full bundle at', path.resolve('scripts/_bundle.js'));
151155
fs.writeFileSync('scripts/_bundle.js', bundle);
152156
}
153157

packages/svelte/src/compiler/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
/** @import { AST } from './public.js' */
44
import { walk as zimmerframe_walk } from 'zimmerframe';
55
import { convert } from './legacy.js';
6-
import { parse as parse_acorn } from './phases/1-parse/acorn.js';
76
import { parse as _parse } from './phases/1-parse/index.js';
87
import { remove_typescript_nodes } from './phases/1-parse/remove_typescript_nodes.js';
98
import { analyze_component, analyze_module } from './phases/2-analyze/index.js';

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,7 @@ import {
2222
build_set_style
2323
} from './shared/element.js';
2424
import { process_children } from './shared/fragment.js';
25-
import {
26-
build_render_statement,
27-
build_template_chunk,
28-
build_update_assignment,
29-
get_expression_id,
30-
memoize_expression
31-
} from './shared/utils.js';
25+
import { build_render_statement, build_template_chunk, get_expression_id } from './shared/utils.js';
3226
import { visit_event_attribute } from './shared/events.js';
3327

3428
/**
@@ -200,24 +194,23 @@ export function RegularElement(node, context) {
200194

201195
const node_id = context.state.node;
202196

197+
/** If true, needs `__value` for inputs */
198+
const needs_special_value_handling =
199+
node.name === 'option' ||
200+
node.name === 'select' ||
201+
bindings.has('group') ||
202+
bindings.has('checked');
203+
203204
if (has_spread) {
204205
build_attribute_effect(attributes, class_directives, style_directives, context, node, node_id);
205206
} else {
206-
/** If true, needs `__value` for inputs */
207-
const needs_special_value_handling =
208-
node.name === 'option' ||
209-
node.name === 'select' ||
210-
bindings.has('group') ||
211-
bindings.has('checked');
212-
213207
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
214208
if (is_event_attribute(attribute)) {
215209
visit_event_attribute(attribute, context);
216210
continue;
217211
}
218212

219213
if (needs_special_value_handling && attribute.name === 'value') {
220-
build_element_special_value_attribute(node.name, node_id, attribute, context);
221214
continue;
222215
}
223216

@@ -392,6 +385,15 @@ export function RegularElement(node, context) {
392385
context.state.update.push(b.stmt(b.assignment('=', dir, dir)));
393386
}
394387

388+
if (!has_spread && needs_special_value_handling) {
389+
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
390+
if (attribute.name === 'value') {
391+
build_element_special_value_attribute(node.name, node_id, attribute, context);
392+
break;
393+
}
394+
}
395+
}
396+
395397
context.state.template.pop_element();
396398
}
397399

@@ -622,12 +624,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
622624
element === 'select' && attribute.value !== true && !is_text_attribute(attribute);
623625

624626
const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
625-
metadata.has_call
626-
? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately
627-
is_select_with_value
628-
? memoize_expression(state, value)
629-
: get_expression_id(state.expressions, value)
630-
: value
627+
metadata.has_call ? get_expression_id(state.expressions, value) : value
631628
);
632629

633630
const evaluated = context.state.scope.evaluate(value);
@@ -652,23 +649,21 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
652649
: inner_assignment
653650
);
654651

655-
if (is_select_with_value) {
656-
state.init.push(b.stmt(b.call('$.init_select', node_id, b.thunk(value))));
657-
}
658-
659652
if (has_state) {
660-
const id = state.scope.generate(`${node_id.name}_value`);
661-
build_update_assignment(
662-
state,
663-
id,
664-
// `<option>` is a special case: The value property reflects to the DOM. If the value is set to undefined,
665-
// that means the value should be set to the empty string. To be able to do that when the value is
666-
// initially undefined, we need to set a value that is guaranteed to be different.
667-
element === 'option' ? b.object([]) : undefined,
668-
value,
669-
update
670-
);
653+
const id = b.id(state.scope.generate(`${node_id.name}_value`));
654+
655+
// `<option>` is a special case: The value property reflects to the DOM. If the value is set to undefined,
656+
// that means the value should be set to the empty string. To be able to do that when the value is
657+
// initially undefined, we need to set a value that is guaranteed to be different.
658+
const init = element === 'option' ? b.object([]) : undefined;
659+
660+
state.init.push(b.var(id, init));
661+
state.update.push(b.if(b.binary('!==', id, b.assignment('=', id, value)), b.block([update])));
671662
} else {
672663
state.init.push(update);
673664
}
665+
666+
if (is_select_with_value) {
667+
state.init.push(b.stmt(b.call('$.init_select', node_id)));
668+
}
674669
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/utils.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,6 @@ export function parse_directive_name(name) {
165165
return expression;
166166
}
167167

168-
/**
169-
* @param {ComponentClientTransformState} state
170-
* @param {string} id
171-
* @param {Expression | undefined} init
172-
* @param {Expression} value
173-
* @param {ExpressionStatement} update
174-
*/
175-
export function build_update_assignment(state, id, init, value, update) {
176-
state.init.push(b.var(id, init));
177-
state.update.push(
178-
b.if(b.binary('!==', b.id(id), b.assignment('=', b.id(id), value)), b.block([update]))
179-
);
180-
}
181-
182168
/**
183169
* Serializes `bind:this` for components and elements.
184170
* @param {Identifier | MemberExpression | SequenceExpression} expression

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { clsx } from '../../../shared/attributes.js';
2020
import { set_class } from './class.js';
2121
import { set_style } from './style.js';
2222
import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js';
23-
import { block, branch, destroy_effect } from '../../reactivity/effects.js';
23+
import { block, branch, destroy_effect, effect } from '../../reactivity/effects.js';
2424
import { derived } from '../../reactivity/deriveds.js';
2525
import { init_select, select_option } from './bindings/select.js';
2626

@@ -513,10 +513,12 @@ export function attribute_effect(
513513
});
514514

515515
if (is_select) {
516-
init_select(
517-
/** @type {HTMLSelectElement} */ (element),
518-
() => /** @type {Record<string | symbol, any>} */ (prev).value
519-
);
516+
var select = /** @type {HTMLSelectElement} */ (element);
517+
518+
effect(() => {
519+
select_option(select, /** @type {Record<string | symbol, any>} */ (prev).value);
520+
init_select(select);
521+
});
520522
}
521523

522524
inited = true;

packages/svelte/src/internal/client/dom/elements/bindings/select.js

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { effect } from '../../../reactivity/effects.js';
1+
import { effect, teardown } from '../../../reactivity/effects.js';
22
import { listen_to_event_and_reset_event } from './shared.js';
3-
import { untrack } from '../../../runtime.js';
43
import { is } from '../../../proxy.js';
54
import { is_array } from '../../../../shared/utils.js';
65
import * as w from '../../../warnings.js';
@@ -51,40 +50,29 @@ export function select_option(select, value, mounting) {
5150
* current selection to the dom when it changes. Such
5251
* changes could for example occur when options are
5352
* inside an `#each` block.
54-
* @template V
5553
* @param {HTMLSelectElement} select
56-
* @param {() => V} [get_value]
5754
*/
58-
export function init_select(select, get_value) {
59-
let mounting = true;
60-
effect(() => {
61-
if (get_value) {
62-
select_option(select, untrack(get_value), mounting);
63-
}
64-
mounting = false;
55+
export function init_select(select) {
56+
var observer = new MutationObserver(() => {
57+
// @ts-ignore
58+
select_option(select, select.__value);
59+
// Deliberately don't update the potential binding value,
60+
// the model should be preserved unless explicitly changed
61+
});
62+
63+
observer.observe(select, {
64+
// Listen to option element changes
65+
childList: true,
66+
subtree: true, // because of <optgroup>
67+
// Listen to option element value attribute changes
68+
// (doesn't get notified of select value changes,
69+
// because that property is not reflected as an attribute)
70+
attributes: true,
71+
attributeFilter: ['value']
72+
});
6573

66-
var observer = new MutationObserver(() => {
67-
// @ts-ignore
68-
var value = select.__value;
69-
select_option(select, value);
70-
// Deliberately don't update the potential binding value,
71-
// the model should be preserved unless explicitly changed
72-
});
73-
74-
observer.observe(select, {
75-
// Listen to option element changes
76-
childList: true,
77-
subtree: true, // because of <optgroup>
78-
// Listen to option element value attribute changes
79-
// (doesn't get notified of select value changes,
80-
// because that property is not reflected as an attribute)
81-
attributes: true,
82-
attributeFilter: ['value']
83-
});
84-
85-
return () => {
86-
observer.disconnect();
87-
};
74+
teardown(() => {
75+
observer.disconnect();
8876
});
8977
}
9078

@@ -136,7 +124,6 @@ export function bind_select_value(select, get, set = get) {
136124
mounting = false;
137125
});
138126

139-
// don't pass get_value, we already initialize it in the effect above
140127
init_select(select);
141128
}
142129

packages/svelte/src/internal/client/runtime.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,12 @@ export function update_reaction(reaction) {
294294
reaction.deps = deps = new_deps;
295295
}
296296

297-
if (!skip_reaction) {
297+
if (
298+
!skip_reaction ||
299+
// Deriveds that already have reactions can cleanup, so we still add them as reactions
300+
((flags & DERIVED) !== 0 &&
301+
/** @type {import('#client').Derived} */ (reaction).reactions !== null)
302+
) {
298303
for (i = skipped_deps; i < deps.length; i++) {
299304
(deps[i].reactions ??= []).push(reaction);
300305
}

packages/svelte/src/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
* The current version, as set in package.json.
55
* @type {string}
66
*/
7-
export const VERSION = '5.34.8';
7+
export const VERSION = '5.34.9';
88
export const PUBLIC_VERSION = '5';

packages/svelte/tests/runtime-runes/samples/inspect-state-unsafe-mutation/_config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ export default test({
55
compileOptions: {
66
dev: true
77
},
8-
error: 'state_unsafe_mutation'
8+
9+
error: 'state_unsafe_mutation',
10+
11+
// silence the logs
12+
test({ logs }) {}
913
});

0 commit comments

Comments
 (0)