mrkeyoor.com_
Sat 08 Aug 22:49 UTC
npmUtilsupdated 08 Aug 2026

posthtml-parser

posthtml-parser synchronously converts an HTML or XML string into PostHTML's compact tree format. The root is an array; text, comments, and recognized directives are strings, while elements are objects with tag, optional attrs, optional content, and optional location. It wraps htmlparser2 and preserves tag case, attribute case, and entities by default. This is the parser layer for PostHTML transforms, not a DOM, selector engine, validator, sanitizer, template compiler, or renderer.

Verdict

posthtml-parser is the right narrow tool when PostHTML tree compatibility is the requirement. For general HTML inspection use a queryable DOM parser, and for standards-focused parsing plus serialization use parse5.

API stability3/5The current surface is one named parser function plus exported TypeScript types, and its array, string, and tag-object tree shape is intentionally simple. However, the package remains below 1.0. Its changelog records a switch from default to named export in 0.10.0 and a Node support floor increase in 0.12.0, both meaningful migration events. htmlparser2 options also form part of the accepted option type, so dependency-major changes can affect parsing behavior even when this wrapper stays small.
Docs4/5The README gives an install and parse example, shows the exact resulting tree, explains all three element fields, and documents directives, XML mode, entity decoding, case normalization, CDATA, self-closing recognition, source locations, and valueless attributes with defaults. It does not show traversal, mutation, rendering, CommonJS usage, Buffer conversion, or the limited location coverage. The long changelog and typed declarations provide useful extra evidence, but users still need source or tests for edge cases.
Maintenance3/5Version 0.12.1 was released in September 2024 after the project moved tests to Vitest, raised the Node floor, refreshed CI, and modernized development tools. The repository is not archived and showed pushes in January 2026, with dependency-maintenance pull requests still open. At the same time, the latest release contains mostly tool and documentation updates, and older feature requests such as parent pointers, Buffer handling, and a title parsing edge case remain unresolved, so activity is steady but not fast.
Ecosystem4/5The package recorded 2,983,259 downloads for the measured week and produces the standard tree consumed by PostHTML plugins and posthtml-render. Its single htmlparser2 dependency brings mature HTML and XML tokenization, while shipped types make plugin code easier to check. The direct repository is modest at 116 stars and the tree format intentionally omits DOM conveniences, but compatibility with the broader PostHTML processing stack gives it more practical reach than its standalone profile suggests.

Use it if

  • You are writing a PostHTML plugin or another transform that already consumes the PostHTML tree shape
  • You want a small synchronous parser with TypeScript types and direct access to htmlparser2 parsing options
  • Your markup includes custom tags, PHP-like processing directives, XML, CDATA, or valueless attributes that need explicit parser controls
  • You need optional line and column locations on element nodes for diagnostics or source-aware transforms
Skip it if

Setup reality

Install with npm install posthtml-parser. Version 0.12.1 requires Node 16 or newer and has one runtime dependency, htmlparser2 9.x. There are no peers, native extensions, credentials, config files, or background services. The published build is CommonJS even though the README uses ESM syntax. Both import { parser } from 'posthtml-parser' through Node interop and const { parser } = require('posthtml-parser') are valid; there is no default export, a change recorded in the 0.10.0 changelog. TypeScript declarations ship through dist/index.d.ts and include the parser options and tree node types. Input must be a string, so convert fs.readFile buffers with toString('utf8'). Parsing is synchronous and returns a complete array, which is simple in build tools but can block the process on large files. Defaults are deliberately conservative: tag and attribute names keep their case, entities stay encoded, XML mode is off, and valueless attributes become empty strings. Comments and recognized processing directives are stored as literal strings, not distinct typed nodes. Empty attrs and content keys may be omitted, so walkers must check whether a node is a string or object and whether content is an array. Enabling sourceLocations adds one-based line and column data to element nodes, but implied HTML closures and absent locations on text mean it is not a token map. xmlMode changes void-tag, script/style, CDATA, and self-closing behavior, so it is not just a formatting switch. Custom server-template syntax may need directives with exact start and end markers. Parsing alone does not turn a changed tree back into markup; install posthtml-render or use the complete posthtml processor for that half. Finally, decodeEntities changes actual text and attribute values, and lowerCaseAttributeNames has a documented speed cost, so make those choices based on the consumer rather than enabling every option at once.

Patterns

Parse HTML into a PostHTML treeparse-html-string

import { parser } from 'posthtml-parser';

const tree = parser('<main><h1>Hello</h1></main>');
console.dir(tree, { depth: null });

parser is synchronous and returns an array whose entries are text strings or element objects.

Load the named export from CommonJSrequire-commonjs

const { parser } = require('posthtml-parser');
const tree = parser('<p>CommonJS</p>');

Version 0.10.0 removed the default parser export; destructure the named parser export.

Parse an HTML fileparse-file

import { readFile } from 'node:fs/promises';
import { parser } from 'posthtml-parser';

const html = await readFile('input.html', 'utf8');
const tree = parser(html);

Pass an encoding to readFile or call buffer.toString('utf8'); the declared parser input is a string, not a Buffer.

Walk every element recursivelywalk-tree

function walk(nodes, visit) {
  for (const node of nodes) {
    if (typeof node !== 'object' || node === null) continue;
    visit(node);
    if (Array.isArray(node.content)) walk(node.content, visit);
  }
}

walk(tree, (node) => console.log(node.tag));

Text, comments, and directives are strings, and content may be absent; the tree has no built-in walk method or parent pointer.

Collect link targetscollect-links

const links = [];
walk(tree, (node) => {
  if (node.tag === 'a' && typeof node.attrs?.href === 'string') {
    links.push(node.attrs.href);
  }
});

Attribute values can also be numbers or booleans in the exported type, so narrow the value before string operations.

Change attributes in the parsed treeedit-attributes

walk(tree, (node) => {
  if (node.tag === 'a') {
    node.attrs = { ...node.attrs, rel: 'noopener noreferrer' };
  }
});

Mutation changes the in-memory tree only; use posthtml-render or the full posthtml package to produce HTML afterward.

Decode character entitiesdecode-entities

const tree = parser('<p title="Tom &amp; Ada">A &lt; B</p>', {
  decodeEntities: true,
});

decodeEntities defaults to false, so enabling it changes the stored text and attribute values rather than only parser metadata.

Lowercase tag and attribute namesnormalize-name-case

const tree = parser('<My-Card DATA-ID="7"></My-Card>', {
  lowerCaseTags: true,
  lowerCaseAttributeNames: true,
});

Both options default to false, lowerCaseTags does not apply in XML mode, and the README warns that lowercasing attribute names has a noticeable speed cost.

Parse XML-style markupparse-xml

const tree = parser('<feed><entry id="1" /></feed>', {
  xmlMode: true,
});

XML mode affects case, self-closing elements, CDATA, void elements, and script/style handling; it is a parsing-semantics choice.

Attach line and column locationstrack-source-locations

const tree = parser('<section>
  <h2>Title</h2>
</section>', {
  sourceLocations: true,
});

console.log(tree[0].location);

Locations are one-based and exist on element objects only; text, comment, and directive strings receive no span.

Preserve PHP processing directivesrecognize-directives

const tree = parser('<?php echo "Hello"; ?><p>Hi</p>', {
  directives: [
    { name: '?php', start: '<', end: '>' },
  ],
});

A recognized directive is stored as its literal source string; use a RegExp name when several processing-instruction forms share markers.

Distinguish a valueless attributepreserve-valueless-attrs

const tree = parser('<input disabled value="">', {
  recognizeNoValueAttribute: true,
});

// attrs.disabled is true; attrs.value is ''

Without this option, a valueless attribute is stored as an empty string, making it indistinguishable from an explicitly empty value.

Alternatives

PackageRegistryPick it when
htmlparser2npmChoose it for lower-level callbacks, streaming input, or its DOM handler without converting to PostHTML's tree shape
parse5npmChoose it when HTML-standard parsing, error locations, and a paired serializer matter more than PostHTML compatibility
node-html-parsernpmChoose it when you want a simplified DOM with query selectors and element methods for direct scraping or edits
posthtmlnpmChoose the full processor when you want parsing, plugin execution, tree helpers, and rendering in one pipeline