chore: optimise attributes (#10916)

* avoid getAttribute outside hydration

* tidy up

* simplify

* dom -> element
pull/10917/head
Rich Harris 9 months ago committed by GitHub
parent 4f24eae9c3
commit 7e584e40d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -31,34 +31,37 @@ export function attr_effect(dom, attribute, value) {
} }
/** /**
* @param {Element} dom * @param {Element} element
* @param {string} attribute * @param {string} attribute
* @param {string | null} value * @param {string | null} value
*/ */
export function attr(dom, attribute, value) { export function attr(element, attribute, value) {
value = value == null ? null : value + ''; value = value == null ? null : value + '';
if (DEV) { // @ts-expect-error
check_src_in_dev_hydration(dom, attribute, value); var attributes = (element.__attributes ??= {});
}
if (hydrating) {
attributes[attribute] = element.getAttribute(attribute);
if (attribute === 'src' || attribute === 'href' || attribute === 'srcset') {
check_src_in_dev_hydration(element, attribute, value);
if ( // If we reset these attributes, they would result in another network request, which we want to avoid.
!hydrating ||
(dom.getAttribute(attribute) !== value &&
// If we reset those, they would result in another network request, which we want to avoid.
// We assume they are the same between client and server as checking if they are equal is expensive // We assume they are the same between client and server as checking if they are equal is expensive
// (we can't just compare the strings as they can be different between client and server but result in the // (we can't just compare the strings as they can be different between client and server but result in the
// same url, so we would need to create hidden anchor elements to compare them) // same url, so we would need to create hidden anchor elements to compare them)
attribute !== 'src' && return;
attribute !== 'href' &&
attribute !== 'srcset')
) {
if (value === null) {
dom.removeAttribute(attribute);
} else {
dom.setAttribute(attribute, value);
} }
} }
if (attributes[attribute] === (attributes[attribute] = value)) return;
if (value === null) {
element.removeAttribute(attribute);
} else {
element.setAttribute(attribute, value);
}
} }
/** /**
@ -123,14 +126,14 @@ export function spread_attributes_effect(dom, attrs, lowercase_attributes, css_h
/** /**
* Spreads attributes onto a DOM element, taking into account the currently set attributes * Spreads attributes onto a DOM element, taking into account the currently set attributes
* @param {Element & ElementCSSInlineStyle} dom * @param {Element & ElementCSSInlineStyle} element
* @param {Record<string, unknown> | undefined} prev * @param {Record<string, unknown> | undefined} prev
* @param {Record<string, unknown>[]} attrs * @param {Record<string, unknown>[]} attrs
* @param {boolean} lowercase_attributes * @param {boolean} lowercase_attributes
* @param {string} css_hash * @param {string} css_hash
* @returns {Record<string, unknown>} * @returns {Record<string, unknown>}
*/ */
export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_hash) { export function spread_attributes(element, prev, attrs, lowercase_attributes, css_hash) {
var next = object_assign({}, ...attrs); var next = object_assign({}, ...attrs);
var has_hash = css_hash.length !== 0; var has_hash = css_hash.length !== 0;
@ -144,8 +147,8 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
next.class = ''; next.class = '';
} }
var setters = map_get(setters_cache, dom.nodeName); var setters = map_get(setters_cache, element.nodeName);
if (!setters) map_set(setters_cache, dom.nodeName, (setters = get_setters(dom))); if (!setters) map_set(setters_cache, element.nodeName, (setters = get_setters(element)));
for (key in next) { for (key in next) {
var value = next[key]; var value = next[key];
@ -170,27 +173,27 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
} }
if (!delegated && prev?.[key]) { if (!delegated && prev?.[key]) {
dom.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts); element.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts);
} }
if (value != null) { if (value != null) {
if (!delegated) { if (!delegated) {
dom.addEventListener(event_name, value, opts); element.addEventListener(event_name, value, opts);
} else { } else {
// @ts-ignore // @ts-ignore
dom[`__${event_name}`] = value; element[`__${event_name}`] = value;
delegate([event_name]); delegate([event_name]);
} }
} }
} else if (value == null) { } else if (value == null) {
dom.removeAttribute(key); element.removeAttribute(key);
} else if (key === 'style') { } else if (key === 'style') {
dom.style.cssText = value + ''; element.style.cssText = value + '';
} else if (key === 'autofocus') { } else if (key === 'autofocus') {
autofocus(/** @type {HTMLElement} */ (dom), Boolean(value)); autofocus(/** @type {HTMLElement} */ (element), Boolean(value));
} else if (key === '__value' || key === 'value') { } else if (key === '__value' || key === 'value') {
// @ts-ignore // @ts-ignore
dom.value = dom[key] = dom.__value = value; element.value = element[key] = element.__value = value;
} else { } else {
var name = key; var name = key;
if (lowercase_attributes) { if (lowercase_attributes) {
@ -199,17 +202,11 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
} }
if (setters.includes(name)) { if (setters.includes(name)) {
if (DEV) { if (hydrating && (name === 'src' || name === 'href' || name === 'srcset')) {
check_src_in_dev_hydration(dom, name, value); check_src_in_dev_hydration(element, name, value);
} } else {
if (
!hydrating ||
// @ts-ignore see attr method for an explanation of src/srcset
(dom[name] !== value && name !== 'src' && name !== 'href' && name !== 'srcset')
) {
// @ts-ignore // @ts-ignore
dom[name] = value; element[name] = value;
} }
} else if (typeof value !== 'function') { } else if (typeof value !== 'function') {
if (has_hash && name === 'class') { if (has_hash && name === 'class') {
@ -217,7 +214,7 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
value += css_hash; value += css_hash;
} }
attr(dom, name, value); attr(element, name, value);
} }
} }
} }
@ -301,22 +298,20 @@ function get_setters(element) {
} }
/** /**
* @param {any} dom * @param {any} element
* @param {string} attribute * @param {string} attribute
* @param {string | null} value * @param {string | null} value
*/ */
function check_src_in_dev_hydration(dom, attribute, value) { function check_src_in_dev_hydration(element, attribute, value) {
if (!hydrating) return; if (!DEV) return;
if (attribute !== 'src' && attribute !== 'href' && attribute !== 'srcset') return; if (attribute === 'srcset' && srcset_url_equal(element, value)) return;
if (src_url_equal(element.getAttribute(attribute) ?? '', value ?? '')) return;
if (attribute === 'srcset' && srcset_url_equal(dom, value)) return;
if (src_url_equal(dom.getAttribute(attribute) ?? '', value ?? '')) return;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error( console.error(
`Detected a ${attribute} attribute value change during hydration. This will not be repaired during hydration, ` + `Detected a ${attribute} attribute value change during hydration. This will not be repaired during hydration, ` +
`the ${attribute} value that came from the server will be used. Related element:`, `the ${attribute} value that came from the server will be used. Related element:`,
dom, element,
' Differing value:', ' Differing value:',
value value
); );

@ -67,6 +67,8 @@ export function init_operations() {
text_prototype.__nodeValue = ' '; text_prototype.__nodeValue = ' ';
// @ts-expect-error // @ts-expect-error
element_prototype.__className = ''; element_prototype.__className = '';
// @ts-expect-error
element_prototype.__attributes = null;
first_child_get = /** @type {(this: Node) => ChildNode | null} */ ( first_child_get = /** @type {(this: Node) => ChildNode | null} */ (
// @ts-ignore // @ts-ignore

Loading…
Cancel
Save