NWSAPI CSS Selector Engine
NWSAPI is a high-performance CSS selector engine, serving as a continuation and significant rework of the earlier `nwmatcher` project, currently at version 2.2.23. Its primary goal is to provide comprehensive and fast support for the latest CSS Level 4 Selectors, while also meticulously emulating native browser APIs such as `querySelector()`, `querySelectorAll()`, `matches()`, and `closest()`. The library is actively maintained, with a focus on continuous bug fixes and feature enhancements, and is intended to replace `nwmatcher` in environments like `jsdom`. NWSAPI differentiates itself through a unique architecture that employs regular expressions to parse CSS selector strings and metaprogramming to transform these into memoized JavaScript function resolvers, a process executed only once per selector for 'unmatched performances'. It ships with no external dependencies and supports both browser environments (via a global `NW.Dom` object) and headless environments like Node.js (as a CommonJS module).
Common errors
-
TypeError: document.querySelector is not a function
cause In Node.js or browser environments where NWSAPI is not directly replacing native DOM APIs, the `document.querySelector` and similar methods are not automatically polyfilled or overridden by `nwsapi` upon import.fixManually bind NWSAPI's functions to the `document` object or `Element.prototype` as demonstrated in the quickstart, or use the NWSAPI functions directly with a context: `engine.first('selector', document.body)`. -
ReferenceError: NW is not defined
cause The global `NW.Dom` object is only available when NWSAPI is loaded directly into a browser environment via a `<script>` tag. This error occurs when trying to access `NW.Dom` in Node.js or before the script has executed in a browser.fixFor Node.js, use `require('nwsapi')` or `import { ... } from 'nwsapi'` to access the module exports. For browsers, ensure `nwsapi.js` is loaded via a `<script>` tag before attempting to access `NW.Dom`.
Warnings
- breaking NWSAPI (v2.0.0 and later) is a complete rewrite and continuation of the `nwmatcher` project. While aiming for similar functionality, significant internal changes mean it is not a drop-in replacement for `nwmatcher` and requires careful migration.
- gotcha The `configure` option `IDS_DUPES` defaults to `true`, which means NWSAPI will allow multiple elements to share the same ID. This deviates from strict HTML/DOM specifications where IDs must be unique and might lead to unexpected behavior if not accounted for.
- gotcha When using NWSAPI in Node.js with a virtual DOM library like JSDOM, its selector methods (`first`, `select`, `match`, `ancestor`) are not automatically bound to `document` or `Element.prototype`. Developers must manually bind these methods to achieve a native-like API (`document.querySelector`, `Element.prototype.matches`).
Install
-
npm install nwsapi -
yarn add nwsapi -
pnpm add nwsapi
Imports
- select
import select from 'nwsapi';
import { select } from 'nwsapi'; - configure
import nwsapi from 'nwsapi'; nwsapi.configure();
import { configure } from 'nwsapi'; - NW.Dom.install
import { install } from 'nwsapi';<script type="text/javascript" src="nwsapi.js" onload="NW.Dom.install()"></script>
Quickstart
const { JSDOM } = require('jsdom');
const nwsapi = require('nwsapi');
// Create a JSDOM instance to simulate a browser environment
const dom = new JSDOM(`
<!DOCTYPE html>
<html>
<body>
<div id="app">
<header>
<h1>My Title</h1>
<nav>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#about" class="active">About</a></li>
</ul>
</nav>
</header>
<main>
<section class="content">
<p>Some text here.</p>
<button id="myButton">Click Me</button>
</section>
<section class="footer-content">
<p>More text.</p>
</section>
</main>
</div>
</body>
</html>
`);
// Get the document object from the JSDOM instance
const document = dom.window.document;
// Configure NWSAPI to work with the JSDOM document context
// The library's functions need to be bound to a context that behaves like a DOM element/document.
const engine = nwsapi.configure({
// Example configuration: allow duplicate IDs (default is true)
IDS_DUPES: true
});
// Manually bind NWSAPI methods to the JSDOM document and Element prototype
// This makes it behave like a polyfilled native API.
document.querySelector = (selector) => engine.first(selector, document);
document.querySelectorAll = (selector) => engine.select(selector, document);
dom.window.Element.prototype.matches = function(selector) { return engine.match(selector, this); };
dom.window.Element.prototype.closest = function(selector) { return engine.ancestor(selector, this); };
// --- Using the NWSAPI-enhanced JSDOM document ---
// Find the first element matching a selector
const mainTitle = document.querySelector('h1');
console.log('Main title:', mainTitle ? mainTitle.textContent : 'Not found');
// Find all elements matching a selector
const allParagraphs = document.querySelectorAll('p');
console.log('Number of paragraphs:', allParagraphs.length);
allParagraphs.forEach((p, i) => console.log(`Paragraph ${i + 1}:`, p.textContent));
// Check if an element matches a selector
const aboutLink = document.querySelector('a.active');
if (aboutLink) {
console.log('Is "About" link active?', aboutLink.matches('.active'));
console.log('Is "About" link a button?', aboutLink.matches('button'));
}
// Find the closest ancestor
const myButton = document.getElementById('myButton');
if (myButton) {
const closestSection = myButton.closest('section');
console.log('Closest section to button:', closestSection ? closestSection.className : 'Not found');
}