npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@asamuzakjp/dom-selector

v4.4.5

Published

A CSS selector engine.

Downloads

1,306,331

Readme

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 - same functionality as Element.matches()

Parameters

  • selector string CSS selector
  • node object Element node
  • opt object? options
    • opt.event object? instance of MouseEvent, KeyboardEvent
    • 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 - same functionality as Element.closest()

Parameters

  • selector string CSS selector
  • node object Element node
  • opt object? options
    • opt.event object? instance of MouseEvent, KeyboardEvent
    • opt.noexcept boolean? no exception
    • opt.warn boolean? console warn e.g. unsupported pseudo-class

Returns object? matched node

querySelector(selector, node, opt)

querySelector - same functionality as Document.querySelector(), DocumentFragment.querySelector(), Element.querySelector()

Parameters

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

Returns object? matched node

querySelectorAll(selector, node, opt)

querySelectorAll - same functionality as Document.querySelectorAll(), DocumentFragment.querySelectorAll(), Element.querySelectorAll()
NOTE: returns Array, not NodeList

Parameters

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

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

Supported CSS selectors

|Pattern|Supported|Note| |:--------|:-------:|:--------| |*|✓| | |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:defined|Partially supported|Matching with MathML is not yet supported.| |E:dir(ltr)|✓| | |E:lang(en)|Partially supported|Comma-separated list of language codes, e.g. :lang(en, fr), is not yet supported.| |E:any‑link|✓| | |E:link|✓| | |E:visited|✓|Returns false or null to prevent fingerprinting.| |E:local‑link|✓| | |E:target|✓| | |E:target‑within|✓| | |E:scope|✓| | |E:current|Unsupported| | |E:current(s)|Unsupported| | |E:past|Unsupported| | |E:future|Unsupported| | |E:active|✓|Enabled if a mousedown / pointerdown event is passed as an option.| |E:hover|✓|Enabled if a mouseover / pointerover event is passed as an option.| |E:focus|✓| | |E:focus‑within|✓| | |E:focus‑visible|✓|Enabled if a keydown event is passed as an option.| |E:openE:closed|Partially supported|Matching with <select>, e.g. select:open, is not supported.| |E:enabledE:disabled|✓| | |E:read‑writeE:read‑only|✓| | |E:placeholder‑shown|✓| | |E:default|✓| | |E:checked|✓| | |E:indeterminate|✓| | |E:validE:invalid|✓| | |E:requiredE:optional|✓| | |E:blank|Unsupported| | |E:user‑validE:user‑invalid|Unsupported| | |E:root|✓| | |E:empty|✓| | |E:nth‑child(n [of S]?)|✓| | |E:nth‑last‑child(n [of S]?)|✓| | |E:first‑child|✓| | |E:last‑child|✓| | |E:only‑child|✓| | |E:nth‑of‑type(n)|✓| | |E:nth‑last‑of‑type(n)|✓| | |E:first‑of‑type|✓| | |E:last‑of‑type|✓| | |E:only‑of‑type|✓| | |E F|✓| | |E > F|✓| | |E + F|✓| | |E ~ F|✓| | |F || E|Unsupported| | |E:nth‑col(n)|Unsupported| | |E:nth‑last‑col(n)|Unsupported| | |E:popover-open|✓| | |E:host|✓| | |E:host(s)|✓| | |E:host‑context(s)|✓| |

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);
    };
  }
});

Performance

See benchmark for the latest results.

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

matches()

|Selector|jsdom v24.1.0 (nwsapi)|happy-dom|linkeDom|patched-jsdom (dom-selector)|Result| |:-----------|:-----------|:-----------|:-----------|:-----------|:-----------| |simple selector:matches('.content')|989,361 ops/sec ±0.27%|7,447 ops/sec ±1.10%|9,037 ops/sec ±1.74%|947,094 ops/sec ±0.24%|jsdom is the fastest and 1.0 times faster than patched-jsdom.| |compound selector:matches('p.content[id]:is(:last-child, :only-child)')|581,187 ops/sec ±0.21%|7,137 ops/sec ±0.82%|8,808 ops/sec ±1.24%|492,756 ops/sec ±0.25%|jsdom is the fastest and 1.2 times faster than patched-jsdom.| |compound selector:matches('p.content[id]:is(:invalid-nth-child, :only-child)')|F|7,144 ops/sec ±0.51%|F|145,203 ops/sec ±0.94%|patched-jsdom is the fastest.| |compound selector:matches('p.content[id]:not(:is(.foo, .bar))')|472,261 ops/sec ±1.64%|7,199 ops/sec ±0.58%|8,612 ops/sec ±0.51%|403,920 ops/sec ±0.29%|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 > .content')|152,011 ops/sec ±0.66%|F|5,737 ops/sec ±0.43%|135,540 ops/sec ±0.41%|jsdom is the fastest and 1.1 times faster than patched-jsdom.| |complex selector:matches('.box:first-child ~ .box:nth-of-type(4n+1) + .box .block.inner:has(> .content)')|F|F|5,767 ops/sec ±0.47%|39,996 ops/sec ±0.79%|patched-jsdom is the fastest.| |complex selector within logical pseudo-class:matches(':is(.box > .content, .block > .content)')|416,215 ops/sec ±0.49%|F|6,105 ops/sec ±0.29%|358,945 ops/sec ±0.29%|jsdom is the fastest and 1.2 times faster than patched-jsdom.|

closest()

|Selector|jsdom v24.1.0 (nwsapi)|happy-dom|linkeDom|patched-jsdom (dom-selector)|Result| |:-----------|:-----------|:-----------|:-----------|:-----------|:-----------| |simple selector:closest('.container')|367,877 ops/sec ±1.14%|7,218 ops/sec ±0.76%|9,181 ops/sec ±0.64%|356,762 ops/sec ±1.02%|jsdom is the fastest and 1.0 times faster than patched-jsdom.| |compound selector:closest('div.container[id]:not(.foo, .box)')|134,650 ops/sec ±0.32%|F|8,549 ops/sec ±0.62%|125,888 ops/sec ±3.16%|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')|134,654 ops/sec ±2.02%|F|5,751 ops/sec ±1.01%|119,709 ops/sec ±2.00%|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 .block.inner:has(> .content)')|F|F|5,596 ops/sec ±0.81%|28,732 ops/sec ±1.13%|patched-jsdom is the fastest.| |complex selector within logical pseudo-class:closest(':is(.container > .content, .container > .box)')|192,885 ops/sec ±1.15%|4,685 ops/sec ±1.30%|5,927 ops/sec ±0.64%|181,927 ops/sec ±1.39%|jsdom is the fastest and 1.1 times faster than patched-jsdom.|

querySelector()

|Selector|jsdom v24.1.0 (nwsapi)|happy-dom|linkeDom|patched-jsdom (dom-selector)|Result| |:-----------|:-----------|:-----------|:-----------|:-----------|:-----------| |simple selector:querySelector('.content')|27,400 ops/sec ±0.96%|9,042 ops/sec ±1.19%|10,550 ops/sec ±0.69%|25,885 ops/sec ±1.48%|jsdom is the fastest and 1.1 times faster than patched-jsdom.| |compound selector:querySelector('p.content[id]:is(:last-child, :only-child)')|8,685 ops/sec ±1.47%|8,723 ops/sec ±1.20%|9,836 ops/sec ±0.69%|8,418 ops/sec ±1.41%|linkedom is the fastest and 1.2 times faster than patched-jsdom. jsdom is 1.0 times faster than patched-jsdom.| |complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box[id] .block.inner > .content')|206 ops/sec ±1.48%|F|1,289 ops/sec ±0.42%|709 ops/sec ±1.75%|linkedom is the fastest and 1.8 times faster than patched-jsdom. patched-jsdom is 3.4 times faster than jsdom.| |complex selector:querySelector('.box:first-child ~ .box:nth-of-type(4n+1) + .box .block.inner:has(> .content)')|F|F|1,598 ops/sec ±0.35%|476 ops/sec ±1.94%|linkedom is the fastest and 3.4 times faster than patched-jsdom.| |complex selector within logical pseudo-class:querySelector(':is(.box > .content, .block > .content)')|2,921 ops/sec ±1.78%|F|9,782 ops/sec ±0.52%|97,513 ops/sec ±1.46%|patched-jsdom is the fastest. patched-jsdom is 33.4 times faster than jsdom.|

querySelectorAll()

|Selector|jsdom v24.1.0 (nwsapi)|happy-dom|linkeDom|patched-jsdom (dom-selector)|Result| |:-----------|:-----------|:-----------|:-----------|:-----------|:-----------| |simple selector:querySelectorAll('.content')|2,549 ops/sec ±0.55%|729 ops/sec ±0.48%|1,188 ops/sec ±1.57%|3,153 ops/sec ±0.95%|patched-jsdom is the fastest. patched-jsdom is 1.2 times faster than jsdom.| |compound selector:querySelectorAll('p.content[id]:is(:last-child, :only-child)')|880 ops/sec ±1.32%|706 ops/sec ±1.55%|1,156 ops/sec ±0.93%|938 ops/sec ±1.27%|linkedom is the fastest and 1.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')|196 ops/sec ±0.73%|F|414 ops/sec ±0.24%|797 ops/sec ±1.66%|patched-jsdom is the fastest. patched-jsdom is 4.1 times faster than jsdom.| |complex selector:querySelectorAll('.box:first-child ~ .box:nth-of-type(4n+1) + .box .block.inner:has(> .content)')|F|F|451 ops/sec ±0.36%|504 ops/sec ±1.88%|patched-jsdom is the fastest.| |complex selector within logical pseudo-class:querySelectorAll(':is(.box > .content, .block > .content)')|273 ops/sec ±0.89%|F|498 ops/sec ±1.38%|236 ops/sec ±2.36%|linkedom is the fastest and 2.1 times faster than patched-jsdom. jsdom is 1.2 times faster than patched-jsdom.|

Acknowledgments

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


Copyright (c) 2023 asamuzaK (Kazz)