Security News
38% of CISOs Fear Theyβre Not Moving Fast Enough on AI
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
Linkedom is a lightweight, fast, and efficient library for working with DOM and HTML in Node.js environments. It provides a comprehensive set of APIs to manipulate and traverse the DOM, similar to what you would find in a browser environment.
DOM Manipulation
This feature allows you to parse HTML strings and manipulate the DOM elements. In this example, we parse an HTML string, select a div element by its ID, change its text content, and then output the modified HTML.
const { parseHTML } = require('linkedom');
const { document } = parseHTML('<html><body><div id="app"></div></body></html>');
const appDiv = document.getElementById('app');
appDiv.textContent = 'Hello, World!';
console.log(document.toString());
Event Handling
Linkedom supports event handling similar to browser environments. This example demonstrates how to add an event listener to a button element and dispatch a click event programmatically.
const { parseHTML } = require('linkedom');
const { document, Event } = parseHTML('<html><body><button id="btn">Click me</button></body></html>');
const button = document.getElementById('btn');
button.addEventListener('click', () => console.log('Button clicked!'));
const event = new Event('click');
button.dispatchEvent(event);
CSS Selector Queries
You can use CSS selectors to query elements in the DOM. This example shows how to select all list items with a specific class and log their text content.
const { parseHTML } = require('linkedom');
const { document } = parseHTML('<html><body><ul><li class="item">Item 1</li><li class="item">Item 2</li></ul></body></html>');
const items = document.querySelectorAll('.item');
items.forEach(item => console.log(item.textContent));
jsdom is a popular library that simulates a browser environment in Node.js. It provides a full-featured DOM and HTML parser, making it suitable for testing and server-side rendering. Compared to linkedom, jsdom is more comprehensive but also heavier and slower.
Cheerio is a fast, flexible, and lean implementation of core jQuery designed specifically for the server. It parses HTML and XML documents and provides a jQuery-like API for DOM manipulation. Cheerio is lighter and faster than jsdom but does not support a full DOM environment like linkedom.
node-html-parser is a fast HTML parser that can parse and manipulate HTML documents. It is lightweight and efficient but does not provide a full DOM API like linkedom. It is suitable for simple HTML parsing and manipulation tasks.
Social Media Photo by JJ Ying on Unsplash
LinkeDOM is a triple-linked list based DOM-like namespace, for DOM-less environments, with the following goals:
import {DOMParser, parseHTML} from 'linkedom';
// Standard way: text/html, text/xml, image/svg+xml, etc...
// const document = (new DOMParser).parseFromString(html, 'text/html');
// Simplified way for HTML
const {
// note, these are *not* globals
window, document, customElements,
HTMLElement,
Event, CustomEvent
// other exports ..
} = parseHTML(`
<!doctype html>
<html lang="en">
<head>
<title>Hello SSR</title>
</head>
<body>
<form>
<input name="user">
<button>
Submit
</button>
</form>
</body>
</html>
`);
// builtin extends compatible too π
customElements.define('custom-element', class extends HTMLElement {
connectedCallback() {
console.log('it works π₯³');
}
});
document.body.appendChild(
document.createElement('custom-element')
);
document.toString();
// the SSR ready document
document.querySelectorAll('form, input[name], button');
// the NodeList of elements
// CSS Selector via CSSselect
v0.11
a new linkedom/worker
export has been added. This works with deno, Web, and Service Workers, and it's not strictly coupled with NodeJS. Please note, this export does not include canvas
module, and the performance
is retrieved from the globalThis
context.LinkeDOM uses a blazing fast JSDON serializer, and nodes, as well as whole documents, can be retrieved back via parseJSON(value)
.
// any node can be serialized
const array = toJSON(document);
// somewhere else ...
import {parseJSON} from 'linkedom';
const document = parseJSON(array);
Please note that Custom Elements won't be upgraded, unless the resulting nodes are imported via document.importNode(nodeOrFragment, true)
.
Alternatively, JSDON.fromJSON(array, document)
is able to initialize right away Custom Elements associated with the passed document
.
This module is based on DOMParser API, hence it creates a new document
each time new DOMParser().parseFromString(...)
is invoked.
As there's no global pollution whatsoever, to retrieve classes and features associated to the document
returned by parseFromString
, you need to access its defaultView
property, which is a special proxy that lets you get pseudo-global-but-not-global properties and classes.
Alternatively, you can use the parseHTML
utility which returns a pseudo window object with all the public references you need.
// facade to a generic JSDOM bootstrap
import {parseHTML} from 'linkedom';
function JSDOM(html) { return parseHTML(html); }
// now you can do the same as you would with JSDOM
const {document, window} = new JSDOM('<h1>Hello LinkeDOM π</h1>');
The triple-linked list data structure is explained below in How does it work?, the Deep Dive, and the presentation on Speakeasy JS.
LinkeDOM has zero intention to:
That's it, the rule of thumb is: do I want to be able to render anything, and as fast as possible, in a DOM-less env? LinkeDOM is great!
Do I need a 100% spec compliant env that simulate a browser? I rather use cypress or JSDOM then, as LinkeDOM is not meant to be a replacement for neither projects.
The TL;DR answer is no. Live collections are considered legacy, are slower, have side effects, and it's not intention of LinkeDOM to support these, including:
getElementsByTagName
does not update when nodes are added or removedgetElementsByClassName
does not update when nodes are added or removedchildNodes
, if trapped once, does not update when nodes are added or removedchildren
, if trapped once, does not update when nodes are added or removedattributes
, if trapped once, does not update when attributes are added or removeddocument.all
, if trapped once, does not update when attributes are added or removedIf any code you are dealing with does something like this:
const {children} = element;
while (children.length)
target.appendChild(children[0]);
it will cause an infinite loop, as the children
reference won't side-effect when nodes are moved.
You can solve this in various ways though:
// the modern approach (suggested)
target.append(...element.children);
// the check for firstElement/Child approach (good enough)
while (element.firstChild)
target.appendChild(element.firstChild);
// the convert to array approach (slow but OK)
const list = [].slice.call(element.children);
while (list.length)
target.appendChild(list.shift());
// the zero trap approach (inefficient)
while (element.childNodes.length)
target.appendChild(element.childNodes[0]);
Nope, these are discovered each time, so when heavy usage of these lists is needed, but no mutation is meant, just trap these once and use these like a frozen array.
function eachChildNode({childNodes}, callback) {
for (const child of childNodes) {
callback(child);
if (child.nodeType === child.ELEMENT_NODE)
eachChildNode(child, callback);
}
}
eachChildNode(document, console.log);
All nodes are linked on both sides, and all elements consist of 2 nodes, also linked in between.
Attributes are always at the beginning of an element, while zero or more extra nodes can be found before the end.
A fragment is a special element without boundaries, or parent node.
Node: β node β
Attr<Node>: β attr β β ownerElement?
Text<Node>: β text β β parentNode?
Comment<Node>: β comment β β parentNode?
Element<Node>: β start β end β β parentNode?
Fragment<Element>: start β end
Element example:
parentNode? (as shortcut for a linked list of previous nodes)
β
ββββββββββββββββββββββββββββββββββββββββββββββ
β β
node? β start β attr* β text* β comment* β element* β end β node?
β β
ββββββββββββββββββββββββββββββββββββββββββββββ
Fragment example:
ββββββββββββββββββββββββββββββββββββββββββββββ
β β
start β attr* β text* β comment* β element* β end
β β
ββββββββββββββββββββββββββββββββββββββββββββββ
If this is not clear, feel free to read more in the deep dive page.
Moving N nodes from a container, being it either an Element or a Fragment, requires the following steps:
As result, there are no array operations, and no memory operations, and everything is kept in sync by updating a few properties, so that removing 3714
sparse <div>
elements in a 12M document, as example, takes as little as 3ms, while appending a whole fragment takes close to 0ms.
Try npm run benchmark:html
to see it yourself.
This structure also allows programs to avoid issues such as "Maximum call stack size exceeded" (basicHTML), or "JavaScript heap out of memory" crashes (JSDOM), thanks to its reduced usage of memory and zero stacks involved, hence scaling better from small to very big documents.
As everything is a while(...)
loop away, by default this module does not cache anything, specially because caching requires state invalidation for each container, returned queries, and so on. However, you can import linkedom/cached
instead, as long as you understand its constraints.
This module parses, and works, only with the following nodeType
:
ELEMENT_NODE
ATTRIBUTE_NODE
TEXT_NODE
COMMENT_NODE
DOCUMENT_NODE
DOCUMENT_FRAGMENT_NODE
DOCUMENT_TYPE_NODE
Everything else, at least for the time being, is considered YAGNI, and it won't likely ever land in this project, as there's no goal to replicate deprecated features of this aged Web.
This module exports both linkedom
and linkedom/cached
, which are basically the exact same thing, except the cached version outperforms linkedom
in these scenarios:
On the other hand, the basic, non-cached, module, grants the following:
importNode
or cloneNode
(i.e. template literals based libraries)To run the benchmark locally, please follow these commands:
git clone https://github.com/WebReflection/linkedom.git
cd linkedom/test
npm i
cd ..
npm i
npm run benchmark
FAQs
A triple-linked lists based DOM implementation
The npm package linkedom receives a total of 23,480 weekly downloads. As such, linkedom popularity was classified as popular.
We found that linkedom demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
CISOs are racing to adopt AI for cybersecurity, but hurdles in budgets and governance may leave some falling behind in the fight against cyber threats.
Research
Security News
Socket researchers uncovered a backdoored typosquat of BoltDB in the Go ecosystem, exploiting Go Module Proxy caching to persist undetected for years.
Security News
Company News
Socket is joining TC54 to help develop standards for software supply chain security, contributing to the evolution of SBOMs, CycloneDX, and Package URL specifications.