perf: use Set instead of Array for constant lookups in utils.js (#18250)

Several constant lookup tables in `utils.js` were arrays searched with
`Array.prototype.includes`, which is O(n). They're queried often — per
attribute during attribute setup and SSR, per event during event
delegation, and per identifier during compilation. Switching them to
`Set` makes each lookup O(1) without changing any public behaviour.
pull/18290/head
Mathias Picker 3 months ago committed by GitHub
parent fe9ab936b6
commit e6110d31c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
perf: use `Set` for static attribute and event lookups

@ -13,7 +13,7 @@ export function hash(str) {
return (hash >>> 0).toString(36); return (hash >>> 0).toString(36);
} }
const VOID_ELEMENT_NAMES = [ const VOID_ELEMENT_NAMES = new Set([
'area', 'area',
'base', 'base',
'br', 'br',
@ -30,17 +30,17 @@ const VOID_ELEMENT_NAMES = [
'source', 'source',
'track', 'track',
'wbr' 'wbr'
]; ]);
/** /**
* Returns `true` if `name` is of a void element * Returns `true` if `name` is of a void element
* @param {string} name * @param {string} name
*/ */
export function is_void(name) { export function is_void(name) {
return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype'; return VOID_ELEMENT_NAMES.has(name) || name.toLowerCase() === '!doctype';
} }
const RESERVED_WORDS = [ const RESERVED_WORDS = new Set([
'arguments', 'arguments',
'await', 'await',
'break', 'break',
@ -89,14 +89,14 @@ const RESERVED_WORDS = [
'while', 'while',
'with', 'with',
'yield' 'yield'
]; ]);
/** /**
* Returns `true` if `word` is a reserved JavaScript keyword * Returns `true` if `word` is a reserved JavaScript keyword
* @param {string} word * @param {string} word
*/ */
export function is_reserved(word) { export function is_reserved(word) {
return RESERVED_WORDS.includes(word); return RESERVED_WORDS.has(word);
} }
/** /**
@ -106,8 +106,8 @@ export function is_capture_event(name) {
return name.endsWith('capture') && name !== 'gotpointercapture' && name !== 'lostpointercapture'; return name.endsWith('capture') && name !== 'gotpointercapture' && name !== 'lostpointercapture';
} }
/** List of Element events that will be delegated */ /** Set of Element events that will be delegated */
const DELEGATED_EVENTS = [ const DELEGATED_EVENTS = new Set([
'beforeinput', 'beforeinput',
'click', 'click',
'change', 'change',
@ -131,20 +131,20 @@ const DELEGATED_EVENTS = [
'touchend', 'touchend',
'touchmove', 'touchmove',
'touchstart' 'touchstart'
]; ]);
/** /**
* Returns `true` if `event_name` is a delegated event * Returns `true` if `event_name` is a delegated event
* @param {string} event_name * @param {string} event_name
*/ */
export function can_delegate_event(event_name) { export function can_delegate_event(event_name) {
return DELEGATED_EVENTS.includes(event_name); return DELEGATED_EVENTS.has(event_name);
} }
/** /**
* Attributes that are boolean, i.e. they are present or not present. * Attributes that are boolean, i.e. they are present or not present.
*/ */
const DOM_BOOLEAN_ATTRIBUTES = [ const DOM_BOOLEAN_ATTRIBUTES = new Set([
'allowfullscreen', 'allowfullscreen',
'async', 'async',
'autofocus', 'autofocus',
@ -173,14 +173,14 @@ const DOM_BOOLEAN_ATTRIBUTES = [
'defer', 'defer',
'disablepictureinpicture', 'disablepictureinpicture',
'disableremoteplayback' 'disableremoteplayback'
]; ]);
/** /**
* Returns `true` if `name` is a boolean attribute * Returns `true` if `name` is a boolean attribute
* @param {string} name * @param {string} name
*/ */
export function is_boolean_attribute(name) { export function is_boolean_attribute(name) {
return DOM_BOOLEAN_ATTRIBUTES.includes(name); return DOM_BOOLEAN_ATTRIBUTES.has(name);
} }
/** /**
@ -213,7 +213,7 @@ export function normalize_attribute(name) {
return ATTRIBUTE_ALIASES[name] ?? name; return ATTRIBUTE_ALIASES[name] ?? name;
} }
const DOM_PROPERTIES = [ const DOM_PROPERTIES = new Set([
...DOM_BOOLEAN_ATTRIBUTES, ...DOM_BOOLEAN_ATTRIBUTES,
'formNoValidate', 'formNoValidate',
'isMap', 'isMap',
@ -229,16 +229,16 @@ const DOM_PROPERTIES = [
'allowFullscreen', 'allowFullscreen',
'disablePictureInPicture', 'disablePictureInPicture',
'disableRemotePlayback' 'disableRemotePlayback'
]; ]);
/** /**
* @param {string} name * @param {string} name
*/ */
export function is_dom_property(name) { export function is_dom_property(name) {
return DOM_PROPERTIES.includes(name); return DOM_PROPERTIES.has(name);
} }
const NON_STATIC_PROPERTIES = ['autofocus', 'muted', 'defaultValue', 'defaultChecked']; const NON_STATIC_PROPERTIES = new Set(['autofocus', 'muted', 'defaultValue', 'defaultChecked']);
/** /**
* Returns `true` if the given attribute cannot be set through the template * Returns `true` if the given attribute cannot be set through the template
@ -246,7 +246,7 @@ const NON_STATIC_PROPERTIES = ['autofocus', 'muted', 'defaultValue', 'defaultChe
* @param {string} name * @param {string} name
*/ */
export function cannot_be_set_statically(name) { export function cannot_be_set_statically(name) {
return NON_STATIC_PROPERTIES.includes(name); return NON_STATIC_PROPERTIES.has(name);
} }
/** /**
@ -258,24 +258,24 @@ export function cannot_be_set_statically(name) {
* - they apply to mobile which is generally less performant * - they apply to mobile which is generally less performant
* we're marking them as passive by default for other elements, too. * we're marking them as passive by default for other elements, too.
*/ */
const PASSIVE_EVENTS = ['touchstart', 'touchmove']; const PASSIVE_EVENTS = new Set(['touchstart', 'touchmove']);
/** /**
* Returns `true` if `name` is a passive event * Returns `true` if `name` is a passive event
* @param {string} name * @param {string} name
*/ */
export function is_passive_event(name) { export function is_passive_event(name) {
return PASSIVE_EVENTS.includes(name); return PASSIVE_EVENTS.has(name);
} }
const CONTENT_EDITABLE_BINDINGS = ['textContent', 'innerHTML', 'innerText']; const CONTENT_EDITABLE_BINDINGS = new Set(['textContent', 'innerHTML', 'innerText']);
/** @param {string} name */ /** @param {string} name */
export function is_content_editable_binding(name) { export function is_content_editable_binding(name) {
return CONTENT_EDITABLE_BINDINGS.includes(name); return CONTENT_EDITABLE_BINDINGS.has(name);
} }
const LOAD_ERROR_ELEMENTS = [ const LOAD_ERROR_ELEMENTS = new Set([
'body', 'body',
'embed', 'embed',
'iframe', 'iframe',
@ -285,17 +285,17 @@ const LOAD_ERROR_ELEMENTS = [
'script', 'script',
'style', 'style',
'track' 'track'
]; ]);
/** /**
* Returns `true` if the element emits `load` and `error` events * Returns `true` if the element emits `load` and `error` events
* @param {string} name * @param {string} name
*/ */
export function is_load_error_element(name) { export function is_load_error_element(name) {
return LOAD_ERROR_ELEMENTS.includes(name); return LOAD_ERROR_ELEMENTS.has(name);
} }
const SVG_ELEMENTS = [ const SVG_ELEMENTS = new Set([
'altGlyph', 'altGlyph',
'altGlyphDef', 'altGlyphDef',
'altGlyphItem', 'altGlyphItem',
@ -382,14 +382,14 @@ const SVG_ELEMENTS = [
'use', 'use',
'view', 'view',
'vkern' 'vkern'
]; ]);
/** @param {string} name */ /** @param {string} name */
export function is_svg(name) { export function is_svg(name) {
return SVG_ELEMENTS.includes(name); return SVG_ELEMENTS.has(name);
} }
const MATHML_ELEMENTS = [ const MATHML_ELEMENTS = new Set([
'annotation', 'annotation',
'annotation-xml', 'annotation-xml',
'maction', 'maction',
@ -420,64 +420,64 @@ const MATHML_ELEMENTS = [
'munder', 'munder',
'munderover', 'munderover',
'semantics' 'semantics'
]; ]);
/** @param {string} name */ /** @param {string} name */
export function is_mathml(name) { export function is_mathml(name) {
return MATHML_ELEMENTS.includes(name); return MATHML_ELEMENTS.has(name);
} }
const STATE_CREATION_RUNES = /** @type {const} */ ([ const STATE_CREATION_RUNES = new Set(
'$state', /** @type {const} */ (['$state', '$state.raw', '$derived', '$derived.by'])
'$state.raw', );
'$derived',
'$derived.by' const RUNES = new Set(
]); /** @type {const} */ ([
...STATE_CREATION_RUNES,
const RUNES = /** @type {const} */ ([ '$state.eager',
...STATE_CREATION_RUNES, '$state.snapshot',
'$state.eager', '$props',
'$state.snapshot', '$props.id',
'$props', '$bindable',
'$props.id', '$effect',
'$bindable', '$effect.pre',
'$effect', '$effect.tracking',
'$effect.pre', '$effect.root',
'$effect.tracking', '$effect.pending',
'$effect.root', '$inspect',
'$effect.pending', '$inspect().with',
'$inspect', '$inspect.trace',
'$inspect().with', '$host'
'$inspect.trace', ])
'$host' );
]);
/** @typedef {typeof RUNES extends Set<infer T> ? T : never} RuneName */
/** @typedef {typeof RUNES[number]} RuneName */ /** @typedef {typeof STATE_CREATION_RUNES extends Set<infer T> ? T : never} StateCreationRuneName */
/** /**
* @param {string} name * @param {string} name
* @returns {name is RuneName} * @returns {name is RuneName}
*/ */
export function is_rune(name) { export function is_rune(name) {
return RUNES.includes(/** @type {RuneName} */ (name)); return RUNES.has(/** @type {RuneName} */ (name));
} }
/** @typedef {typeof STATE_CREATION_RUNES[number]} StateCreationRuneName */
/** /**
* @param {string} name * @param {string} name
* @returns {name is StateCreationRuneName} * @returns {name is StateCreationRuneName}
*/ */
export function is_state_creation_rune(name) { export function is_state_creation_rune(name) {
return STATE_CREATION_RUNES.includes(/** @type {StateCreationRuneName} */ (name)); return STATE_CREATION_RUNES.has(/** @type {StateCreationRuneName} */ (name));
} }
/** List of elements that require raw contents and should not have SSR comments put in them */ /** Elements that require raw contents and should not have SSR comments put in them */
const RAW_TEXT_ELEMENTS = /** @type {const} */ (['textarea', 'script', 'style', 'title']); const RAW_TEXT_ELEMENTS = new Set(/** @type {const} */ (['textarea', 'script', 'style', 'title']));
/** @typedef {typeof RAW_TEXT_ELEMENTS extends Set<infer T> ? T : never} RawTextElement */
/** @param {string} name */ /** @param {string} name */
export function is_raw_text_element(name) { export function is_raw_text_element(name) {
return RAW_TEXT_ELEMENTS.includes(/** @type {typeof RAW_TEXT_ELEMENTS[number]} */ (name)); return RAW_TEXT_ELEMENTS.has(/** @type {RawTextElement} */ (name));
} }
// Matches valid HTML/SVG/MathML element names and custom element names. // Matches valid HTML/SVG/MathML element names and custom element names.

Loading…
Cancel
Save