6.2.0 • Published 9 months ago

@asamuzakjp/dom-selector v6.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

DOM Selector

build CodeQL npm (scoped)

A CSS selector engine.

Install

npm i @asamuzakjp/dom-selector

Usage

import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';

const { window } = new JSDOM();
const {
  closest, matches, querySelector, querySelectorAll
} = new DOMSelector(window);

matches(selector, node, opt)

matches - equivalent to Element.matches()

Parameters

  • selector string CSS selector
  • node object Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns boolean true if matched, false otherwise

closest(selector, node, opt)

closest - equivalent to Element.closest()

Parameters

  • selector string CSS selector
  • node object Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns object? matched node

querySelector(selector, node, opt)

querySelector - equivalent to Document.querySelector(), DocumentFragment.querySelector() and Element.querySelector()

Parameters

  • selector string CSS selector
  • node object Document, DocumentFragment or Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns object? matched node

querySelectorAll(selector, node, opt)

querySelectorAll - equivalent to Document.querySelectorAll(), DocumentFragment.querySelectorAll() and Element.querySelectorAll()
NOTE: returns Array, not NodeList

Parameters

  • selector string CSS selector
  • node object Document, DocumentFragment or Element node
  • opt object? options
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns Array<(object | undefined)> array of matched nodes

Monkey patch jsdom

import { DOMSelector } from '@asamuzakjp/dom-selector';
import { JSDOM } from 'jsdom';

const dom = new JSDOM('', {
  runScripts: 'dangerously',
  url: 'http://localhost/',
  beforeParse: window => {
    const domSelector = new DOMSelector(window);

    const matches = domSelector.matches.bind(domSelector);
    window.Element.prototype.matches = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return matches(selector, this);
    };

    const closest = domSelector.closest.bind(domSelector);
    window.Element.prototype.closest = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return closest(selector, this);
    };

    const querySelector = domSelector.querySelector.bind(domSelector);
    window.Document.prototype.querySelector = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelector(selector, this);
    };
    window.DocumentFragment.prototype.querySelector = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelector(selector, this);
    };
    window.Element.prototype.querySelector = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelector(selector, this);
    };

    const querySelectorAll = domSelector.querySelectorAll.bind(domSelector);
    window.Document.prototype.querySelectorAll = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelectorAll(selector, this);
    };
    window.DocumentFragment.prototype.querySelectorAll = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelectorAll(selector, this);
    };
    window.Element.prototype.querySelectorAll = function (...args) {
      if (!args.length) {
        throw new window.TypeError('1 argument required, but only 0 present.');
      }
      const [selector] = args;
      return querySelectorAll(selector, this);
    };
  }
});

Supported CSS selectors

PatternSupportedNote
*
ns|E
*|E
|E
E
E:not(s1, s2, …)
E:is(s1, s2, …)
E:where(s1, s2, …)
E:has(rs1, rs2, …)
E.warning
E#myid
E[foo]
E[foo="bar"]
E[foo="bar" i]
E[foo="bar" s]
E[foo~="bar"]
E[foo^="bar"]
E[foo$="bar"]
E[foo*="bar"]
E[foo|="en"]
E:definedPartially supportedMatching with MathML is not yet supported.
E:dir(ltr)
E:lang(en)
E:anylink
E:link
E:visitedReturns false or null to prevent fingerprinting.
E:locallink
E:target
E:targetwithin
E:scope
E:currentUnsupported
E:current(s)Unsupported
E:pastUnsupported
E:futureUnsupported
E:active
E:hover
E:focus
E:focuswithin
E:focusvisible
E:openE:closedPartially supportedMatching with <select>, e.g. select:open, is not supported.
E:enabledE:disabled
E:readwriteE:readonly
E:placeholdershown
E:default
E:checked
E:indeterminate
E:validE:invalid
E:requiredE:optional
E:blankUnsupported
E:uservalidE:userinvalidUnsupported
E:root
E:empty
E:nthchild(n of S?)
E:nthlastchild(n of S?)
E:firstchild
E:lastchild
E:onlychild
E:nthoftype(n)
E:nthlastoftype(n)
E:firstoftype
E:lastoftype
E:onlyoftype
E F
E > F
E + F
E ~ F
F || EUnsupported
E:nthcol(n)Unsupported
E:nthlastcol(n)Unsupported
E:popover-open
E:state(v)*1
:host
:host(s)
:hostcontext(s)
:host(:state(v))*1
:host:has(rs1, rs2, ...)
:host(s):has(rs1, rs2, ...)
:hostcontext(s):has(rs1, rs2, ...)

*1: ElementInternals.states, i.e. CustomStateSet, is not implemented in jsdom, so you need to apply a patch in the custom element constructor.

class LabeledCheckbox extends window.HTMLElement {
  #internals;
  constructor() {
    super();
    this.#internals = this.attachInternals();
    // patch CustomStateSet
    if (!this.#internals.states) {
      this.#internals.states = new Set();
    }
    this.addEventListener('click', this._onClick.bind(this));
  }
  get checked() {
    return this.#internals.states.has('checked');
  }
  set checked(flag) {
    if (flag) {
      this.#internals.states.add('checked');
    } else {
      this.#internals.states.delete('checked');
    }
  }
  _onClick(event) {
    this.checked = !this.checked;
  }
}

Performance

See benchmark for the latest results.

F: Failed because the selector is not supported or the result was incorrect.

matches()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:matches('.content')970,195 ops/sec ±1.09%6,434,629 ops/sec ±0.72%8,542 ops/sec ±0.55%759,550 ops/sec ±0.36%happydom is the fastest and 8.5 times faster than patched-jsdom. jsdom is 1.3 times faster than patched-jsdom.
compound selector:matches('p.content[id]:is(:last-child, :only-child)')577,536 ops/sec ±1.66%5,548,367 ops/sec ±1.02%8,290 ops/sec ±0.73%418,490 ops/sec ±0.46%happydom is the fastest and 13.3 times faster than patched-jsdom. jsdom is 1.4 times faster than patched-jsdom.
compound selector:matches('p.content[id]:is(:invalid-nth-child, :only-child)')F4,987,842 ops/sec ±1.99%F88,901 ops/sec ±1.31%happydom is the fastest and 56.1 times faster than patched-jsdom.
compound selector:matches('p.content[id]:not(:is(.foo, .bar))')463,747 ops/sec ±1.81%5,014,172 ops/sec ±0.53%8,200 ops/sec ±0.91%334,734 ops/sec ±1.63%happydom is the fastest and 15.0 times faster than patched-jsdom. jsdom is 1.4 times faster than patched-jsdom.
complex selector:matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')145,755 ops/sec ±2.56%F5,309 ops/sec ±1.61%122,806 ops/sec ±0.33%jsdom is the fastest and 1.2 times faster than patched-jsdom.
complex selector:matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')FF5,372 ops/sec ±0.85%18,921 ops/sec ±1.80%patched-jsdom is the fastest.
complex selector within logical pseudo-class:matches(':is(.box > .content, .block > .content)')394,756 ops/sec ±1.62%F5,688 ops/sec ±0.65%316,784 ops/sec ±1.25%jsdom is the fastest and 1.2 times faster than patched-jsdom.

closest()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:closest('.container')364,468 ops/sec ±0.84%2,002,932 ops/sec ±0.94%8,666 ops/sec ±0.76%328,519 ops/sec ±1.61%happydom is the fastest and 6.1 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom.
compound selector:closest('div.container[id]:not(.foo, .box)')130,479 ops/sec ±1.60%F8,186 ops/sec ±0.71%118,992 ops/sec ±1.59%jsdom is the fastest and 1.1 times faster than patched-jsdom.
complex selector:closest('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')139,006 ops/sec ±1.51%F5,315 ops/sec ±0.71%114,474 ops/sec ±1.29%jsdom is the fastest and 1.2 times faster than patched-jsdom.
complex selector:closest('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')FF5,228 ops/sec ±0.40%15,264 ops/sec ±1.96%patched-jsdom is the fastest.
complex selector within logical pseudo-class:closest(':is(.container > .content, .container > .box)')186,594 ops/sec ±1.66%1,735,355 ops/sec ±1.03%5,608 ops/sec ±0.72%166,431 ops/sec ±0.24%happydom is the fastest and 10.4 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom.

querySelector()

Selectorjsdom v25.0.0 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:querySelector('.content')29,510 ops/sec ±0.88%3,177,172 ops/sec ±1.25%10,026 ops/sec ±0.83%29,689 ops/sec ±1.43%happydom is the fastest and 107.0 times faster than patched-jsdom. patched-jsdom is 1.0 times faster than jsdom.
compound selector:querySelector('p.content[id]:is(:last-child, :only-child)')9,909 ops/sec ±1.42%2,754,183 ops/sec ±1.03%9,476 ops/sec ±0.59%9,355 ops/sec ±1.40%happydom is the fastest and 294.4 times faster than patched-jsdom. jsdom is 1.1 times faster than patched-jsdom.
complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')217 ops/sec ±1.98%F1,402 ops/sec ±2.03%444 ops/sec ±1.64%linkedom is the fastest and 3.2 times faster than patched-jsdom. patched-jsdom is 2.0 times faster than jsdom.
complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')FF1,619 ops/sec ±0.62%320 ops/sec ±1.49%linkedom is the fastest and 5.1 times faster than patched-jsdom.
complex selector within logical pseudo-class:querySelector(':is(.box > .content, .block > .content)')3,180 ops/sec ±1.84%F9,415 ops/sec ±0.69%208,961 ops/sec ±2.48%patched-jsdom is the fastest. patched-jsdom is 65.7 times faster than jsdom.

querySelectorAll()

Selectorjsdom v25.0.1 (nwsapi)happy-domlinkeDompatched-jsdom (dom-selector)Result
simple selector:querySelectorAll('.content')2,892 ops/sec ±1.11%5,500,951 ops/sec ±0.94%1,218 ops/sec ±0.25%3,244 ops/sec ±1.25%happydom is the fastest and 1695.8 times faster than patched-jsdom. patched-jsdom is 1.1 times faster than jsdom.
compound selector:querySelectorAll('p.content[id]:is(:last-child, :only-child)')964 ops/sec ±1.18%4,385,931 ops/sec ±0.80%1,193 ops/sec ±1.17%,020 ops/sec ±0.14%happydom is the fastest and 4299.2 times faster than patched-jsdom. patched-jsdom is 1.1 times faster than jsdom.
complex selector:querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')218 ops/sec ±1.53%F429 ops/sec ±1.71%479 ops/sec ±1.20%patched-jsdom is the fastest. patched-jsdom is 2.2 times faster than jsdom.
complex selector:querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner:has(> .content)')FF465 ops/sec ±0.18%332 ops/sec ±1.21%linkedom is the fastest and 1.4 times faster than patched-jsdom.
complex selector within logical pseudo-class:querySelectorAll(':is(.box > .content, .block > .content)')302 ops/sec ±1.50%F542 ops/sec ±0.33%795 ops/sec ±3.24%patched-jsdom is the fastest. patched-jsdom is 2.6 times faster than jsdom.

Acknowledgments

The following resources have been of great help in the development of the DOM Selector.


Copyright (c) 2023 asamuzaK (Kazz)

6.2.0

9 months ago

5.3.3

11 months ago

5.3.2

11 months ago

5.3.1

11 months ago

5.3.0

11 months ago

6.1.0

10 months ago

6.1.1

10 months ago

5.4.0

11 months ago

6.0.1

10 months ago

6.0.0

10 months ago

6.0.3

10 months ago

6.0.2

10 months ago

6.0.5

10 months ago

6.0.4

10 months ago

5.3.4

11 months ago

4.4.10

1 year ago

4.4.1

1 year ago

4.4.0

1 year ago

4.4.3

1 year ago

4.4.2

1 year ago

4.4.13

1 year ago

4.4.12

1 year ago

4.4.11

1 year ago

5.0.9

12 months ago

5.0.8

12 months ago

5.0.7

12 months ago

5.0.5

12 months ago

5.0.4

12 months ago

5.0.3

12 months ago

5.0.2

12 months ago

5.0.1

12 months ago

5.0.0

12 months ago

4.4.9

1 year ago

4.4.8

1 year ago

4.4.5

1 year ago

4.4.4

1 year ago

4.4.7

1 year ago

4.4.6

1 year ago

4.5.0-b.7

1 year ago

4.5.0-b.6

1 year ago

4.5.0-b.5

1 year ago

4.5.0-b.4

1 year ago

4.5.0-b.3

1 year ago

4.5.0-b.2

1 year ago

4.5.0-b.1

1 year ago

5.1.0

12 months ago

4.6.1

1 year ago

4.6.0

1 year ago

5.2.2

11 months ago

5.2.1

11 months ago

5.2.0

11 months ago

4.6.3

12 months ago

4.6.5

12 months ago

4.6.4

12 months ago

4.6.0-b.1

1 year ago

4.5.0

1 year ago

4.3.0

1 year ago

4.2.2

1 year ago

4.2.1

1 year ago

4.2.0

1 year ago

4.1.7

1 year ago

4.1.4

1 year ago

4.1.6

1 year ago

4.1.5

1 year ago

4.1.3

1 year ago

4.1.2

1 year ago

4.1.1

1 year ago

4.1.0

1 year ago

4.0.1

1 year ago

4.0.0

1 year ago

3.0.5

1 year ago

3.0.4

1 year ago

3.0.3

1 year ago

3.0.2

1 year ago

2.1.0-b.3

1 year ago

2.1.0-b.2

1 year ago

2.1.0-b.4

1 year ago

3.0.1

1 year ago

2.1.0-b.1

1 year ago

2.0.3-a.6

1 year ago

2.0.3-a.5

1 year ago

2.0.3-a.8

1 year ago

2.0.3-a.7

1 year ago

2.0.3-a.4

1 year ago

2.0.3-a.2

1 year ago

2.0.3-a.3

1 year ago

2.0.2

2 years ago

2.0.2-a.3

2 years ago

2.0.3-a.1

2 years ago

2.0.2-a.1

2 years ago

2.0.2-a.2

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.2.8

2 years ago

1.2.7

2 years ago

1.2.6

2 years ago

1.2.5

2 years ago

1.2.4

2 years ago

1.2.3

2 years ago

1.2.2

2 years ago

1.2.1

2 years ago

1.2.0

2 years ago

1.1.14

2 years ago

1.1.13

2 years ago

1.1.9

2 years ago

1.1.8

2 years ago

1.1.6

2 years ago

1.1.5

2 years ago

1.1.4

2 years ago

1.1.3

2 years ago

1.1.12

2 years ago

1.1.11

2 years ago

1.1.10

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.1.2

2 years ago

0.24.0

2 years ago

0.23.2

2 years ago

0.21.2

2 years ago

0.21.1

2 years ago

0.21.0

2 years ago

0.22.0

2 years ago

0.20.1

2 years ago

0.20.0

2 years ago

0.19.4

2 years ago

0.19.5

2 years ago

0.19.6

2 years ago

0.20.2

2 years ago

0.15.4

2 years ago

0.19.1

2 years ago

0.15.5

2 years ago

0.19.2

2 years ago

0.15.6

2 years ago

0.19.3

2 years ago

0.15.7

2 years ago

0.15.8

2 years ago

0.15.9

2 years ago

0.17.0

2 years ago

0.15.3

2 years ago

0.18.1

2 years ago

0.16.3

2 years ago

0.16.4

2 years ago

0.16.0

2 years ago

0.16.1

2 years ago

0.18.0

2 years ago

0.16.2

2 years ago

0.15.10

2 years ago

0.15.13

2 years ago

0.15.12

2 years ago

0.15.1

2 years ago

0.14.0

2 years ago

0.13.5

2 years ago

0.13.2

2 years ago

0.13.1

2 years ago

0.12.8

2 years ago

0.12.7

2 years ago

0.12.6

2 years ago

0.12.5

2 years ago

0.12.3

2 years ago

0.12.2

2 years ago

0.12.1

2 years ago

0.12.0

2 years ago

0.11.1

2 years ago

0.10.0

2 years ago

0.9.2

2 years ago

0.8.1

2 years ago

0.8.0

2 years ago

0.7.0

2 years ago

0.6.2

2 years ago

0.6.1

2 years ago

0.6.0

2 years ago

0.5.0

2 years ago

0.4.2

2 years ago

0.4.1

2 years ago

0.3.0

2 years ago

0.2.2

2 years ago

0.2.1

2 years ago