Element#matches() compiles the selector on every call

Offen
#348 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 5 Tagen

@jonasporto arbeitet bereits daran.

Seit 16.10.2026.

  • #349 von @jonasporto — offen

Bewertung

Schwierigkeit
2/5
Geschätzter Aufwand
1-3 Stunden
Anfängerfreundlichkeit
35/100
Issue-Typ
Refactoring
Klarheit
Klar beschrieben
Aktivitätsstatus
Veraltet
Tech-Stack
css, javascript
Bereich
backend, performance

Rechercherichtung

The hot path is esm/shared/matches.js near line 109, where Element#matches() passes the selector string to CSSselect.is(), which recompiles it on every call; prepareMatch already compiles once per call for querySelector* and closest(), so look there for the pattern to reuse. Pull request #349 is already open against this, so check it before starting. Done means a compiled selector is cached per selector string and document kind in a small bounded Map, :scope selectors still compile per call, and the existing matches tests pass with coverage unchanged.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

Element#matches(selectors) parses and compiles the selector again on every call. Code that tests many elements against the same few selectors (resolving a <style> sheet over an SVG chart, for example) spends most of its time in css-what's parser and css-select's compiler rather than in matching.

// npm i linkedom && node matches.mjs
import { parseHTML } from 'linkedom';

let svg = '<svg id="chart">';
for (let i = 0; i < 400; i++)
  svg += `<g class="node n${i % 7}"><rect class="box"></rect><g class="label"><text class="title"><tspan>${i}</tspan></text></g></g>`;
const { document } = parseHTML(`<!doctype html><html><body>${svg}</svg></body></html>`);

const selectors = [];
for (const a of ['#chart', '#chart .node', '#chart g.label', 'svg'])
  for (const b of ['rect', 'text', 'tspan', '.label', '.title', '.box', '.n3', 'g', '.cluster rect', '.edgeLabel', 'g.node', '.missing'])
    selectors.push(`${a} ${b}`);

const elements = [...document.querySelectorAll('svg, svg *')];
const t = performance.now();
for (const el of elements)
  for (const sel of selectors)
    el.matches(sel);
console.log(`${elements.length * selectors.length} calls: ${(performance.now() - t).toFixed(0)} ms`);

Node 22.17.0, macOS, median of runs:

96,048 matches() calls
linkedom 0.18.13 ~112 ms
linkedom main (3fcf145) ~108 ms
same, with the compiled selector kept per selector string ~20 ms

A larger run (2,001 elements × 50 selectors, 300k calls), in ms per 100k calls: linkedom 0.18.13 158, main 111, with the compiled selector kept 17; happy-dom 21.0.3 10, jsdom 30.1.3 209.

A CPU profile of main shows css-what parseSelector/unescapeCSS, css-select compileToken/escapeRegex and GC on top; the matching itself is a small share.

Where: esm/shared/matches.js#L109 passes the selector string to CSSselect.is(), which calls compile(query, options) each time (css-select 7.0.0, dist/index.js, is). querySelector* and closest() compile once per call through prepareMatch, so it is matches() that pays per element.

Possible fix: keep the compiled function per selector string and document kind (xmlMode), in a small bounded Map; selectors with :scope keep compiling per call since their context is the element. I have this with a test (coverage unchanged) and can open a PR.

Vorherrschende Sprache
HTML
Sterne
2.1k
Forks
117
Ø Merge
4 T. 13 Std.
Gemergte PRs (30 T.)
2

Entwicklungsumgebung

Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus WebReflection/linkedom

Alle Issues in WebReflection/linkedom

Ähnliche Issues

Weitere Issues zu Backend & API Design

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.