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 🙏

© 2026 – Pkg Stats / Ryan Hefner

virty

v0.3.1

Published

A class-based virtual DOM structure allowing for DOM manipulation in nodejs

Readme

Virty

⚠️ This library is in its early stages of development. Expect bugs, and expect them to be plentiful. Until a v1.0.0 release, this message will persist.

⚠️ Recently many methods and getters were added to the main Node class, but time is short and I haven't documented them in the README yet. Everything is JSDoc documented, so you're encouraged to install and review the source code for documentation until I get around to the README.

A class-based virtual dom structure intended to be as brain-dead as possible to use.

❔ Looking for a document parser for html/xml? Take a look at flex-parse. Flex-parse uses virty under the hood as its document model of choice and offers extensively flexible* parsing strategies.*Currently a work in progress, but basic element, text, and comment parsing is available

Installation

The release on Github prior to v1.0.0 will likely be the best place to install from. Development may be sporadic and/or rapid at times, and I won't always be pushing the latest updates to NPM.

# Latest release
pnpm i "https://github.com/jacoblockett/virty"
# Release hosted on NPM
pnpm i virty

Quickstart

Say we wanted to emulate this html structure:

<body>
	<div id="main">
		<h1>Welcome back, Username!</h1>
		<div class="form-block">
			<div class="message">Not Username? Type your name below!</div>
			<input placeholder="Type your name here" />
			<!-- QA: should we put a submit button here? -->
		</div>
	</div>
</body>

At the most basic level, this would be the equivalent virty code:

// COMMENT, ELEMENT, and TEXT are simple string constants exported for convenience's sake
import Node, { COMMENT, ELEMENT, TEXT } from "virty"

// element nodes
const body = new Node({ type: ELEMENT, tagName: "body" })
const mainDiv = new Node({ type: ELEMENT, tagName: "div", attributes: { id: "main" } })
const h1 = new Node({ type: ELEMENT, tagName: "h1" })
const formBlock = new Node({ type: ELEMENT, tagName: "div", attributes: { class: "form-block" } })
const message = new Node({ type: ELEMENT, tagName: "div", attributes: { class: "message" } })
const input = new Node({ type: ELEMENT, tagName: "input", attributes: { placeholder: "Type your name here" } })

// text nodes
const h1Text = new Node({ type: TEXT, value: "Welcome back, Username!" })
const msgText = new Node({ type: TEXT, value: "Not Username? Type your name below!" })

// comment nodes
const qaComment = new Node({ type: COMMENT, value: "<!-- QA: should we put a submit button here? -->" })

// putting it all together
body.appendChild(mainDiv)
mainDiv.appendChild(h1, formBlock)
h1.appendChild(h1Text)
formBlock.appendChild(message, input, qaComment)
message.appendChild(msgText)

Another way to write the same thing would be to make use of the children option in the Node classes' constructors:

import Node, { COMMENT, ELEMENT, TEXT } from "virty"

const doc = new Node({
	type: ELEMENT,
	tagName: "body",
	children: [
		new Node({
			type: ELEMENT,
			tagName: "div",
			attributes: { id: "main" },
			children: [
				new Node({
					type: ELEMENT,
					tagName: "h1",
					children: [new Node({ type: TEXT, value: "Welcome back, Username!" })]
				}),
				new Node({
					type: ELEMENT,
					tagName: "div",
					attributes: { class: "form-block" },
					children: [
						new Node({
							type: ELEMENT,
							tagName: "div",
							attributes: { class: "message" },
							children: [new Node({ type: TEXT, value: "Not Username? Type your name below!" })]
						}),
						new Node({
							type: ELEMENT,
							tagName: "input",
							attributes: { placeholder: "Type your name here" }
						}),
						new Node({
							type: COMMENT,
							value: "<!-- QA: should we put a submit button here? -->"
						})
					]
				})
			]
		})
	]
})

And finally, if you prefer a more custom functional approach:

import Node, { COMMENT, ELEMENT, TEXT } from "virty"

const createComment = value => new Node({ type: COMMENT, value })
const createElement = (tagName, attributes, children) => new Node({ type: ELEMENT, tagName, attributes, children })
const createText = value => new Node({ type: TEXT, value })

const doc = createElement("body", {}, [
	createElement("div", { id: "main" }, [
		createElement("h1", {}, [createText("Welcome back, Username!")]),
		createElement("div", { class: "form-block" }, [
			createElement("div", { class: "message" }, [createText("Not Username? Type your name below!")]),
			createElement("input", { placeholder: "Type your name here" }),
			createComment("<!-- QA: should we put a submit button here? -->")
		])
	])
])

Generally speaking, this library isn't designed for you to be manually creating document structures like this. If you want to, by all means, but you should think of this library as a lower-level driver to produce something higher level, such as results for a parser, a dynamic structure generator based on user input, etc.

API

Signature:

class Node(init: {
    type: "comment"|"element"|"text",
    tagName?: string,
    attributes?: { [name: string]: string|number|boolean },
    children?: Node[],
    value?: string,
}): Node

Init options:

| Name | Type | Required | Default | Description | | ---------- | --------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------ | ---------------------------------------------------------- | | type | Enum("comment"\|"element"\|"text") | Yes | | The case-insensitive type of the node | | tagName | string | If the element includes attributes, yes, otherwise, no | "element": """comment"\|"text": undefined | Case-sensitive tag name to use with "element" nodes | | attributes | { [name: string]: string\|number\|boolean } | No | "element": {}"comment"\|"text": undefined | Attribute values to use with "element" nodes | | children | Node[] | No | "element": []"comment"\|"text": undefined | Child nodes to append to "element" nodes | | value | string | No | "element": undefined"comment"\|"text": "" | The text content to use with "comment" or "text" nodes |

💡 "element" nodes will ignore the value option, and "comment"|"text" nodes will ignore the tagName, attributes, and children options.


Methods, Getters, Setters:

Node.isComment 🔝

static Node.isComment(value: unknown): boolean

Checks if the given value is a comment node.

Example:

Node.isComment("<!-- comment -->") // false
Node.isComment(new Node({ type: COMMENT, value: "<!-- comment -->" })) // true

Node.isElement 🔝

static Node.isElement(value: unknown): boolean

Checks if the given value is an element node.

Example:

Node.isElement("div") // false
Node.isElement(new Node({ type: ELEMENT, tagName: "div" })) // true

Node.isNode 🔝

static Node.isNode(value: unknown): boolean

Checks if the given value is a node.

Example:

Node.isNode("text") // false
Node.isNode(new Node({ type: COMMENT, value: "<!-- comment -->" })) // true
Node.isNode(new Node({ type: ELEMENT, tagName: "div" })) // true
Node.isNode(new Node({ type: TEXT, value: "text" })) // true

Node.isText 🔝

static Node.isText(value: unknown): boolean

Checks if the given value is a text node.

Example:

Node.isText("text") // false
Node.isText(new Node({ type: TEXT, value: "text" })) // true

Node.prototype.attributes 🔝

get attributes(): { [name: string]: string|number|boolean } | undefined

The attributes of the node.

Example:

const n = new Node({ type: ELEMENT, tagName: "div", attributes: { class: "a b c" } })

n.attributes.class // "a b c"

Node.prototype.children 🔝

get children(): Node[] | undefined

The children of the node.

Example:

const n = new Node({ type: ELEMENT, children: [new Node({ type: ELEMENT }), new Node({ type: TEXT })] })

n.children // [ Node {}, Node {} ], i.e. [ Node { ELEMENT }, Node { TEXT } ]

Node.prototype.next 🔝

get next(): Node | undefined

The next sibling node.

Example:

const n = new Node({ type: ELEMENT, children: [new Node({ type: ELEMENT }), new Node({ type: TEXT })] })

n.next // undefined
n.children[0].next // Node {}, i.e. Node { TEXT }

Node.prototype.parent 🔝

get parent(): Node | undefined

The parent node.

Example:

const n = new Node({ type: ELEMENT, children: [new Node({ type: ELEMENT }), new Node({ type: TEXT })] })

n.parent // undefined
n.children[0].parent // Node {}, i.e. Node { ELEMENT (n) }

Node.prototype.previous 🔝

get previous(): Node | undefined

The previous sibling node.

Example:

const n = new Node({ type: ELEMENT, children: [new Node({ type: ELEMENT }), new Node({ type: TEXT })] })

n.previous // undefined
n.children[1].previous // Node {}, i.e. Node { ELEMENT }

Node.prototype.root 🔝

get root(): Node

The root node.

Example:

const n = new Node({
	type: ELEMENT,
	children: [new Node({ type: ELEMENT, children: [new Node({ type: ELEMENT })] }), new Node({ type: TEXT })]
})

n.children[0].children[0].root // Node {}, i.e. Node { ELEMENT (n) }

Node.prototype.tagName 🔝

get tagName(): string | undefined

The tag name of the node.

Example:

const n = new Node({ type: ELEMENT, tagName: "div" })

n.tagName // "div"

Node.prototype.type 🔝

get type(): "comment" | "element" | "text"

The type of the node.

Example:

const n = new Node({ type: ELEMENT })

n.type // "element"

Node.prototype.value 🔝

get value(): string | undefined

The value of the node.

⚠️ This is not the value attribute of an element node, such as an <input> element's value, for example. To access that, use Node.prototype.attributes.value instead.

Example:

const n = new Node({ type: TEXT, value: "Lorem ipsum..." })

n.value // "Lorem ipsum..."

Node.prototype.appendChild 🔝

function appendChild(...nodes: (Node | Node[])[]): Node

Appends the given child or children to the node. You can pass multiple nodes as arguments or an array of nodes. Appended children will lose any and all parent and sibling references should they already exist. "comment" and "text" nodes will do nothing. Returns itself for chaining.

Example:

const parent = new Node({ type: ELEMENT })
const child1 = new Node({ type: TEXT })
const child2 = new Node({ type: TEXT })

parent.appendChild(child1, child2)
// or parent.appendChild([child1, child2])
// or parent.appendChild(child1).appendChild(child2)

parent.children // [ Node {}, Node {} ], i.e. [ Node { TEXT (child1) }, Node { TEXT (child2) } ]

Node.prototype.appendSibling 🔝

function appendSibling(...nodes: (Node | Node[])[]): Node

Appends the given sibling or siblings to the node. You can pass multiple nodes as arguments or an array of nodes. Appended siblings will lose any and all parent and sibling references should they already exist. Returns itself for chaining.

Example:

const parent = new Node({ type: ELEMENT })
const child1 = new Node({ type: TEXT })
const child2 = new Node({ type: TEXT })

parent.appendChild(child1)
child1.appendSibling(child2)

parent.children // [ Node {}, Node {} ], i.e. [ Node { TEXT (child1) }, Node { TEXT (child2) } ]

Node.prototype.removeChild 🔝

function removeChild(...nodes: (Node | Node[])[]): Node

Removes the given child or children from the node. You can pass multiple nodes as arguments or an array of nodes. Nodes of type "comment" and "text", and nodes with no children, will do nothing. Returns itself for chaining.

Example:

const parent = new Node({ type: ELEMENT })
const child1 = new Node({ type: TEXT })
const child2 = new Node({ type: TEXT })

parent.appendChild(child1, child2).removeChild(child1)

parent.children // [ Node {} ], i.e. [ Node { TEXT (child2) } ]