Skip to content

Commit 8634231

Browse files
committed
robustify for multiple blocks:
- revert to previous value on unmount, if it was the last one changing the value - special handling for classes: merge them
1 parent 0c1c8b9 commit 8634231

File tree

4 files changed

+81
-32
lines changed

4 files changed

+81
-32
lines changed
Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
/** @import { ExpressionStatement } from 'estree' */
1+
/** @import { ExpressionStatement, Property } from 'estree' */
22
/** @import { AST } from '#compiler' */
33
/** @import { ComponentContext } from '../types' */
4-
import { is_dom_property, normalize_attribute } from '../../../../../utils.js';
5-
import { is_ignored } from '../../../../state.js';
4+
import { normalize_attribute } from '../../../../../utils.js';
65
import { is_event_attribute } from '../../../../utils/ast.js';
76
import * as b from '../../../../utils/builders.js';
87
import { build_attribute_value } from './shared/element.js';
@@ -13,40 +12,18 @@ import { visit_event_attribute } from './shared/events.js';
1312
* @param {ComponentContext} context
1413
*/
1514
export function SvelteHTML(element, context) {
16-
const node_id = b.id('$.document.documentElement');
15+
/** @type {Property[]} */
16+
const attributes = [];
1717

1818
for (const attribute of element.attributes) {
1919
if (attribute.type === 'Attribute') {
2020
if (is_event_attribute(attribute)) {
2121
visit_event_attribute(attribute, context);
2222
} else {
2323
const name = normalize_attribute(attribute.name);
24-
const { value, has_state } = build_attribute_value(attribute.value, context);
24+
const { value } = build_attribute_value(attribute.value, context);
2525

26-
/** @type {ExpressionStatement} */
27-
let update;
28-
29-
if (name === 'class') {
30-
update = b.stmt(b.call('$.set_class', node_id, value));
31-
} else if (is_dom_property(name)) {
32-
update = b.stmt(b.assignment('=', b.member(node_id, name), value));
33-
} else {
34-
update = b.stmt(
35-
b.call(
36-
'$.set_attribute',
37-
node_id,
38-
b.literal(name),
39-
value,
40-
is_ignored(element, 'hydration_attribute_changed') && b.true
41-
)
42-
);
43-
}
44-
45-
if (has_state) {
46-
context.state.update.push(update);
47-
} else {
48-
context.state.init.push(update);
49-
}
26+
attributes.push(b.init(name, value));
5027

5128
if (context.state.options.dev) {
5229
context.state.init.push(
@@ -56,4 +33,8 @@ export function SvelteHTML(element, context) {
5633
}
5734
}
5835
}
36+
37+
if (attributes.length > 0) {
38+
context.state.init.push(b.stmt(b.call('$.svelte_html', b.arrow([], b.object(attributes)))));
39+
}
5940
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { render_effect, teardown } from '../../reactivity/effects.js';
2+
import { set_attribute } from '../elements/attributes.js';
3+
import { set_class } from '../elements/class.js';
4+
import { hydrating } from '../hydration.js';
5+
6+
/**
7+
* @param {() => Record<string, any>} get_attributes
8+
* @returns {void}
9+
*/
10+
export function svelte_html(get_attributes) {
11+
const node = document.documentElement;
12+
const own = {};
13+
14+
/** @type {Record<string, Array<[any, any]>>} to check who set the last value of each attribute */
15+
// @ts-expect-error
16+
const current_setters = (node.__attributes_setters ??= {});
17+
18+
/** @type {Record<string, any>} */
19+
let attributes;
20+
21+
render_effect(() => {
22+
attributes = get_attributes();
23+
24+
for (const name in attributes) {
25+
let value = attributes[name];
26+
current_setters[name] = (current_setters[name] ?? []).filter(([owner]) => owner !== own);
27+
current_setters[name].unshift([own, value]);
28+
29+
// Do nothing on initial render during hydration: If there are attribute duplicates, the last value
30+
// wins, which could result in needless hydration repairs from earlier values.
31+
if (hydrating) continue;
32+
33+
if (name === 'class') {
34+
set_class(node, current_setters[name].map(([_, value]) => value).join(' '));
35+
} else {
36+
set_attribute(node, name, value);
37+
}
38+
}
39+
});
40+
41+
teardown(() => {
42+
for (const name in attributes) {
43+
const old = current_setters[name];
44+
current_setters[name] = old.filter(([owner]) => owner !== own);
45+
const current = current_setters[name];
46+
47+
if (name === 'class') {
48+
set_class(node, current.map(([_, value]) => value).join(' '));
49+
} else if (old[0][0] === own) {
50+
set_attribute(node, name, current[0]?.[1]);
51+
}
52+
}
53+
});
54+
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export { snippet, wrap_snippet } from './dom/blocks/snippet.js';
2424
export { component } from './dom/blocks/svelte-component.js';
2525
export { element } from './dom/blocks/svelte-element.js';
2626
export { head } from './dom/blocks/svelte-head.js';
27+
export { svelte_html } from './dom/blocks/svelte-html.js';
2728
export { append_styles } from './dom/css.js';
2829
export { action } from './dom/elements/actions.js';
2930
export {
@@ -149,7 +150,12 @@ export {
149150
setContext,
150151
hasContext
151152
} from './runtime.js';
152-
export { validate_binding, validate_each_keys, validate_prop_bindings } from './validate.js';
153+
export {
154+
validate_binding,
155+
validate_each_keys,
156+
validate_prop_bindings,
157+
validate_svelte_html_attribute
158+
} from './validate.js';
153159
export { raf } from './timing.js';
154160
export { proxy } from './proxy.js';
155161
export { create_custom_element } from './dom/elements/custom-element.js';

packages/svelte/src/internal/server/blocks/svelte-html.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ import { svelte_html_duplicate_attribute } from '../../shared/warnings.js';
99
*/
1010
export function svelte_html(payload, attributes) {
1111
for (const name in attributes) {
12+
let value = attributes[name];
13+
1214
if (payload.htmlAttributes.has(name)) {
13-
svelte_html_duplicate_attribute(name);
15+
if (name === 'class') {
16+
// Don't bother deduplicating class names, the browser handles it just fine
17+
value = `${payload.htmlAttributes.get(name)} ${value}`;
18+
} else {
19+
svelte_html_duplicate_attribute(name);
20+
}
1421
}
15-
payload.htmlAttributes.set(name, escape_html(attributes[name], true));
22+
23+
payload.htmlAttributes.set(name, escape_html(value, true));
1624
}
1725
}

0 commit comments

Comments
 (0)