Skip to content

Commit 7e584e4

Browse files
authored
chore: optimise attributes (#10916)
* avoid getAttribute outside hydration * tidy up * simplify * dom -> element
1 parent 4f24eae commit 7e584e4

File tree

2 files changed

+44
-47
lines changed

2 files changed

+44
-47
lines changed

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

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,37 @@ export function attr_effect(dom, attribute, value) {
3131
}
3232

3333
/**
34-
* @param {Element} dom
34+
* @param {Element} element
3535
* @param {string} attribute
3636
* @param {string | null} value
3737
*/
38-
export function attr(dom, attribute, value) {
38+
export function attr(element, attribute, value) {
3939
value = value == null ? null : value + '';
4040

41-
if (DEV) {
42-
check_src_in_dev_hydration(dom, attribute, value);
43-
}
41+
// @ts-expect-error
42+
var attributes = (element.__attributes ??= {});
43+
44+
if (hydrating) {
45+
attributes[attribute] = element.getAttribute(attribute);
46+
47+
if (attribute === 'src' || attribute === 'href' || attribute === 'srcset') {
48+
check_src_in_dev_hydration(element, attribute, value);
4449

45-
if (
46-
!hydrating ||
47-
(dom.getAttribute(attribute) !== value &&
48-
// If we reset those, they would result in another network request, which we want to avoid.
50+
// If we reset these attributes, they would result in another network request, which we want to avoid.
4951
// We assume they are the same between client and server as checking if they are equal is expensive
5052
// (we can't just compare the strings as they can be different between client and server but result in the
5153
// same url, so we would need to create hidden anchor elements to compare them)
52-
attribute !== 'src' &&
53-
attribute !== 'href' &&
54-
attribute !== 'srcset')
55-
) {
56-
if (value === null) {
57-
dom.removeAttribute(attribute);
58-
} else {
59-
dom.setAttribute(attribute, value);
54+
return;
6055
}
6156
}
57+
58+
if (attributes[attribute] === (attributes[attribute] = value)) return;
59+
60+
if (value === null) {
61+
element.removeAttribute(attribute);
62+
} else {
63+
element.setAttribute(attribute, value);
64+
}
6265
}
6366

6467
/**
@@ -123,14 +126,14 @@ export function spread_attributes_effect(dom, attrs, lowercase_attributes, css_h
123126

124127
/**
125128
* Spreads attributes onto a DOM element, taking into account the currently set attributes
126-
* @param {Element & ElementCSSInlineStyle} dom
129+
* @param {Element & ElementCSSInlineStyle} element
127130
* @param {Record<string, unknown> | undefined} prev
128131
* @param {Record<string, unknown>[]} attrs
129132
* @param {boolean} lowercase_attributes
130133
* @param {string} css_hash
131134
* @returns {Record<string, unknown>}
132135
*/
133-
export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_hash) {
136+
export function spread_attributes(element, prev, attrs, lowercase_attributes, css_hash) {
134137
var next = object_assign({}, ...attrs);
135138
var has_hash = css_hash.length !== 0;
136139

@@ -144,8 +147,8 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
144147
next.class = '';
145148
}
146149

147-
var setters = map_get(setters_cache, dom.nodeName);
148-
if (!setters) map_set(setters_cache, dom.nodeName, (setters = get_setters(dom)));
150+
var setters = map_get(setters_cache, element.nodeName);
151+
if (!setters) map_set(setters_cache, element.nodeName, (setters = get_setters(element)));
149152

150153
for (key in next) {
151154
var value = next[key];
@@ -170,27 +173,27 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
170173
}
171174

172175
if (!delegated && prev?.[key]) {
173-
dom.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts);
176+
element.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts);
174177
}
175178

176179
if (value != null) {
177180
if (!delegated) {
178-
dom.addEventListener(event_name, value, opts);
181+
element.addEventListener(event_name, value, opts);
179182
} else {
180183
// @ts-ignore
181-
dom[`__${event_name}`] = value;
184+
element[`__${event_name}`] = value;
182185
delegate([event_name]);
183186
}
184187
}
185188
} else if (value == null) {
186-
dom.removeAttribute(key);
189+
element.removeAttribute(key);
187190
} else if (key === 'style') {
188-
dom.style.cssText = value + '';
191+
element.style.cssText = value + '';
189192
} else if (key === 'autofocus') {
190-
autofocus(/** @type {HTMLElement} */ (dom), Boolean(value));
193+
autofocus(/** @type {HTMLElement} */ (element), Boolean(value));
191194
} else if (key === '__value' || key === 'value') {
192195
// @ts-ignore
193-
dom.value = dom[key] = dom.__value = value;
196+
element.value = element[key] = element.__value = value;
194197
} else {
195198
var name = key;
196199
if (lowercase_attributes) {
@@ -199,25 +202,19 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
199202
}
200203

201204
if (setters.includes(name)) {
202-
if (DEV) {
203-
check_src_in_dev_hydration(dom, name, value);
204-
}
205-
206-
if (
207-
!hydrating ||
208-
// @ts-ignore see attr method for an explanation of src/srcset
209-
(dom[name] !== value && name !== 'src' && name !== 'href' && name !== 'srcset')
210-
) {
205+
if (hydrating && (name === 'src' || name === 'href' || name === 'srcset')) {
206+
check_src_in_dev_hydration(element, name, value);
207+
} else {
211208
// @ts-ignore
212-
dom[name] = value;
209+
element[name] = value;
213210
}
214211
} else if (typeof value !== 'function') {
215212
if (has_hash && name === 'class') {
216213
if (value) value += ' ';
217214
value += css_hash;
218215
}
219216

220-
attr(dom, name, value);
217+
attr(element, name, value);
221218
}
222219
}
223220
}
@@ -301,22 +298,20 @@ function get_setters(element) {
301298
}
302299

303300
/**
304-
* @param {any} dom
301+
* @param {any} element
305302
* @param {string} attribute
306303
* @param {string | null} value
307304
*/
308-
function check_src_in_dev_hydration(dom, attribute, value) {
309-
if (!hydrating) return;
310-
if (attribute !== 'src' && attribute !== 'href' && attribute !== 'srcset') return;
311-
312-
if (attribute === 'srcset' && srcset_url_equal(dom, value)) return;
313-
if (src_url_equal(dom.getAttribute(attribute) ?? '', value ?? '')) return;
305+
function check_src_in_dev_hydration(element, attribute, value) {
306+
if (!DEV) return;
307+
if (attribute === 'srcset' && srcset_url_equal(element, value)) return;
308+
if (src_url_equal(element.getAttribute(attribute) ?? '', value ?? '')) return;
314309

315310
// eslint-disable-next-line no-console
316311
console.error(
317312
`Detected a ${attribute} attribute value change during hydration. This will not be repaired during hydration, ` +
318313
`the ${attribute} value that came from the server will be used. Related element:`,
319-
dom,
314+
element,
320315
' Differing value:',
321316
value
322317
);

packages/svelte/src/internal/client/dom/operations.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export function init_operations() {
6767
text_prototype.__nodeValue = ' ';
6868
// @ts-expect-error
6969
element_prototype.__className = '';
70+
// @ts-expect-error
71+
element_prototype.__attributes = null;
7072

7173
first_child_get = /** @type {(this: Node) => ChildNode | null} */ (
7274
// @ts-ignore

0 commit comments

Comments
 (0)