mrkeyoor.com_
Sun 09 Aug 06:53 UTC
npmWeb Frontendupdated 09 Aug 2026

dom-serialize

A CommonJS HTML serializer for browser-style DOM nodes. Unlike reading outerHTML, it also accepts text, attribute, comment, document, doctype, document-fragment, NodeList, and array inputs. Its unusual feature is an extensibility hook: it dispatches a bubbling, cancelable serialize event for each node, letting a listener replace that node with a string or another node, or omit it. That flexibility comes from a 2015 codebase and is the main reason to consider it over native DOM properties.

Verdict

The event-based override hook is genuinely distinctive, so retaining it in a tested legacy browser pipeline can be reasonable. For new code, native outerHTML or XMLSerializer, or a maintained serializer matched to your tree type, is safer than adopting this 2015 package.

API stability4/5The callable API and eight named serializer helpers have remained unchanged since 2.2.1 in November 2015. The bundled test suite covers elements, attributes, text, comments, documents, doctypes, fragments, arrays, NodeLists, event cancellation, replacement nodes, callback cleanup, and context propagation. That makes existing behavior predictable, but there is no published compatibility policy and stability is primarily the result of no releases rather than active regression control.
Docs2/5The README quickly explains supported node kinds and the serialize-event concept, then gives examples for text, elements, persistent listeners, and one-time callbacks. It omits all named helper exports, Node and jsdom setup, supported runtimes, namespace limitations, event-realm concerns, unsupported-node behavior, and the exact escaping rules. Its main callback sample misspells event.detail.serialize as serialze, so copying the documented ellipsis branch does not work as shown.
Maintenance1/5Version 2.2.1 was published on November 5, 2015, and the latest commit returned by the repository history is a license change from May 2017. GitHub reports a later push timestamp in August 2019, but there has been no corresponding npm release. The repository is not archived and npm does not mark the package deprecated, yet unresolved reports dating back to 2016 and 2017 plus a 2024 dependency warning show that users should not expect timely fixes.
Ecosystem2/5npm recorded 3,292,116 downloads for July 31 through August 6, 2026, but the GitHub project has 37 stars and 13 forks, and its four dependencies reflect an older browser-module stack. High downloads are likely driven by transitive use in established tooling rather than a current extension ecosystem. There are no adapters, TypeScript package, plugin catalog, maintained framework guides, or visible stream of new releases around the serializer.

Use it if

  • You must serialize a mixed input that may be an Element, Text, Attr, Document, DocumentFragment, NodeList, or array
  • You need per-node custom output through bubbling serialize events without cloning or rewriting the DOM first
  • You maintain a browser-oriented CommonJS project that already relies on this package's exact HTML output
  • You want text and attribute entity encoding handled while serializing a small, trusted DOM tree
Skip it if

Setup reality

npm install dom-serialize brings four runtime dependencies: custom-event, ent, extend, and void-elements. There are no peer dependencies, native builds, credentials, or config files. In a browser with real DOM nodes, require('dom-serialize') is enough. In Node, the package does not create a DOM, so you need jsdom, @xmldom/xmldom, or another implementation yourself. Its custom-event dependency chooses a constructor from the global environment, not from node.ownerDocument. When using an isolated jsdom window, make sure the CustomEvent used by dom-serialize belongs to that window before requiring the package; otherwise dispatchEvent can reject an event from the wrong realm or the polyfill can look for a missing global document. Serialization is synchronous and recursive, mutates no nodes, and returns an empty string for null, canceled nodes, and unsupported node types, which makes mistakes easy to miss. Each visited node receives a real bubbling serialize event. Existing listeners can therefore change output or cause side effects, and preventDefault removes a node unless detail.serialize is set. The serializer is HTML-shaped rather than a round-trip browser serializer: element names are lowercased, HTML void elements omit closing tags, attributes are enumerated in DOM order, text escapes less-than, greater-than, and ampersand, and comments are wrapped without validating forbidden comment sequences. The README's callback example contains a serialze typo; the working property is event.detail.serialize. There are no TypeScript declarations, ESM entry point, browser export map, or stated compatibility table. Pinning 2.2.1 and testing your target DOM implementation is the sensible approach for legacy use.

Patterns

Serialize an element and its childrenserialize-element

const serialize = require('dom-serialize');

const article = document.createElement('article');
article.setAttribute('data-id', '42');
article.textContent = 'Fish & chips';

console.log(serialize(article));
// <article data-id="42">Fish &amp; chips</article>

The package produces the element's outer markup and encodes ampersands in text nodes.

Escape a standalone text nodeserialize-text

const text = document.createTextNode('<strong>A & B</strong>');
console.log(serialize(text));
// &lt;strong&gt;A &amp; B&lt;/strong&gt;

Text serialization escapes less-than, greater-than, and ampersand, but deliberately leaves quote characters alone.

Serialize a DocumentFragment without a wrapperserialize-fragment

const fragment = document.createDocumentFragment();
fragment.append(document.createElement('b'), document.createTextNode('hello'));

console.log(serialize(fragment));
// <b></b>hello

A fragment serializes only its child nodes; no synthetic fragment tag is added.

Serialize a NodeListserialize-node-list

const list = document.querySelectorAll('.result');
const html = serialize(list);

Any object with a numeric length and no nodeType is treated as a NodeList or array and concatenated in index order.

Serialize selected nodes in a chosen orderserialize-array

const nodes = [
  document.createTextNode('before'),
  document.createElement('hr'),
  document.createTextNode('after'),
];

console.log(serialize(nodes));
// before<hr>after

HTML void elements such as hr and br are emitted without closing tags.

Override one node with a serialize listeneroverride-node-output

const secret = document.querySelector('[data-secret]');
secret.addEventListener('serialize', (event) => {
  event.detail.serialize = '<span>[redacted]</span>';
});

const html = serialize(secret.parentNode);

Replacement strings are inserted verbatim, so never build them from untrusted text without escaping it first.

Omit a node with preventDefaultomit-node

const root = document.querySelector('main');
root.addEventListener('serialize', (event) => {
  if (event.serializeTarget.matches?.('[data-private]')) {
    event.preventDefault();
  }
});

const html = serialize(root);

The event bubbles only through an actual DOM ancestry. A detached root may not provide the event-delegation behavior you expect for descendants.

Customize one serialization calluse-one-time-callback

const html = serialize(root, (event) => {
  if (event.serializeTarget.matches?.('[data-summary]')) {
    event.detail.serialize = '…';
  }
});

When the second argument is a function, it is attached for this call and removed afterward. The correct property is serialize, not the serialze typo in the README.

Pass context to bubbling listenerspass-context

root.addEventListener('serialize', (event) => {
  if (event.detail.context === 'email' && event.serializeTarget.nodeName === 'VIDEO') {
    event.detail.serialize = '<a href="/watch">Watch video</a>';
  }
});

const emailHtml = serialize(root, 'email');

The context is arbitrary data passed unchanged as event.detail.context; the implementation does not interpret it.

Replace a node with another DOM nodereplace-with-node

const html = serialize(root, (event) => {
  if (event.serializeTarget.nodeName === 'TIME') {
    const text = document.createTextNode(event.serializeTarget.dateTime);
    event.detail.serialize = text;
  }
});

A replacement carrying a numeric nodeType is serialized recursively and receives its own serialize event.

Serialize one Attr node with numeric entitiesserialize-attribute

const link = document.createElement('a');
link.setAttribute('title', 'A & "B"');

const output = serialize.serializeAttribute(link.attributes[0], { named: false });
console.log(output);

serializeAttribute and serializeText accept ent encoding options, but these named helpers are documented only by source and tests.

Serialize a complete HTML documentserialize-doctype

const type = document.implementation.createDocumentType('html', '', '');
const doc = document.implementation.createDocument(
  'http://www.w3.org/1999/xhtml',
  'html',
  type,
);

console.log(serialize(doc));
// <!DOCTYPE html><html></html>

The doctype serializer handles publicId and systemId, but the surrounding element serializer still follows its HTML-oriented lowercase and void-element rules.

Alternatives

PackageRegistryPick it when
w3c-xmlserializernpmYou need a maintained, specification-oriented XML serializer for DOM nodes
dom-serializernpmYour tree comes from htmlparser2, domhandler, or Cheerio rather than the browser DOM
parse5npmYou need standards-focused HTML parsing and serialization around a parse5 syntax tree
jsdomnpmNode code needs a complete web DOM plus document serialization, and the larger dependency is acceptable