Element#matches() compiles the selector on every call
Mantenedores costumam responder em até 17 dias
Avaliação
- Dificuldade
- 2/5
- Tempo estimado
- 1-3 horas
- Facilidade para iniciantes
- 35/100
- Tipo de issue
- Refatoração
- Clareza
- Claramente especificada
- Status de atividade
- Estagnada
- Stack de tecnologia
- css, javascript
- Domínio
- backend, performance
Direção de pesquisa
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.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
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.
- Linguagem predominante
- HTML
- Estrelas
- 2.1k
- Forks
- 110
- Merge médio
- 11d 1h
- PRs com merge (30d)
- 1
Preparar o ambiente
Este projeto não oferece contêiner de desenvolvimento, Dockerfile nem guia de contribuição, então a configuração fica por sua conta: comece pelo README e veja nosso guia da primeira contribuição para os passos gerais.
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de WebReflection/linkedom
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 85/100
WebReflection/linkedom#344 ·
Mantenedores costumam responder em até 17 dias
-
`TextElement.toString()` puts the element text back at the first `><` of the start tag, so an attribute value containing `><` swallows the text of `<style>`, `<script>`, `<title>` and `<textarea>`Talvez já em andamento @vsolano9 assumiu hoje. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
WebReflection/linkedom#341 ·
Mantenedores costumam responder em até 17 dias
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
WebReflection/linkedom#340 ·
Mantenedores costumam responder em até 17 dias
-
Dificuldade 3/5 Meio dia Facilidade para iniciantes 50/100
WebReflection/linkedom#345 ·
Mantenedores costumam responder em até 17 dias
-
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 68/100
WebReflection/linkedom#342 ·
Mantenedores costumam responder em até 17 dias
Todas as issues de WebReflection/linkedom
Issues semelhantes
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 88/100
pop-os/cosmic-comp#2956 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 90/100
Mantenedores costumam responder em até 1 dia
-
GET rooms API returns 500 due to nonexistent Room.is_active attributeTalvez já em andamento @Mars-60 assumiu hoje. Abertabug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 84/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 76/100
Mantenedores costumam responder em até 3 dias
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 72/100
Chocobozzz/PeerTube#7832 ·
Mantenedores costumam responder em até 3 dias