hastscript

A hyperscript-style helper for building hast (HTML/SVG) syntax trees in JavaScript.

Library
npm
v9.0.1
199stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
40/100Fair
Development Activity4
Maintenance20
Community56
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
81/100Excellent
Architecture85
Code Quality90
Innovation65
Learning Curve85

hastscript is the hyperscript interface for the hast ecosystem — the same style of API as React’s createElement or Vue’s h, but purpose-built for producing hast (HTML abstract syntax tree) nodes instead of DOM elements or virtual DOM nodes. It exports two functions, h and s, that turn a CSS-like selector, a properties object, and a list of children into a fully-formed hast Element or Root node, normalizing attribute names and values (via property-information) along the way.

It is a foundational utility in the unified/remark/rehype content-processing ecosystem: any plugin or tool that needs to programmatically construct HTML or SVG markup as a syntax tree (rather than string-concatenating HTML) reaches for h/s instead of hand-building node objects. Because it outputs plain hast trees, its output composes directly with the rest of the unified toolchain — hast-util-to-html, hast-util-to-dom, rehype plugins, and MDX/JSX compilers.

The package also ships an automatic JSX runtime (hastscript/jsx-runtime and hastscript/svg/jsx-runtime), so .jsx/.tsx files can be compiled directly into hast trees using standard JSX syntax, with no custom pragma required in modern toolchains.

What You Get

  • h(selector, properties, ...children) for building HTML hast Element/Root nodes from a CSS-like selector string
  • s(selector, properties, ...children) for building SVG hast nodes, with SVG-specific case-sensitive tag name handling
  • Automatic normalization of property names/values (class lists, space/comma-separated attributes, boolean and numeric attributes) via property-information
  • An automatic JSX runtime (hastscript/jsx-runtime, hastscript/svg/jsx-runtime) so JSX/TSX files compile straight to hast trees
  • Full TypeScript types for Child, Properties, and Result, with the package itself written in typed JavaScript (JSDoc + .d.ts generation)
  • Special-case handling for <template> elements, mapping their children into a content root as the HTML spec requires

Common Use Cases

  • Writing a rehype plugin that needs to inject or replace HTML markup in a syntax tree
  • Generating SVG icon or diagram trees programmatically inside a unified/remark/rehype pipeline
  • Authoring MDX or custom JSX components that need to emit raw hast nodes instead of React elements
  • Building static-site or markdown-processing tooling that assembles HTML fragments as data rather than strings, avoiding manual escaping bugs
  • Testing or fixture generation for other hast-ecosystem packages, where trees need to be constructed by hand quickly

Under The Hood

Architecture The package is intentionally small and layered: lib/create-h.js exports a single createH(schema, defaultTagName, caseSensitive) factory that returns a closure-bound h-style function; lib/index.js then instantiates this twice — once with the HTML property-information schema and div default (exported as h), once with the SVG schema, g default, and an SVG case-sensitive tag-name adjustment map (exported as s). All selector parsing is delegated to the separate hast-util-parse-selector package, and all attribute name/value normalization to property-information, so create-h.js itself only has to decide, per call, whether the second argument is a properties object or the first child (isChild()), then fold properties and children into the parsed node. A small lib/create-automatic-runtime.js module adapts this same h/s closure into the shape required by the JSX automatic runtime contract (jsx, jsxs, jsxDEV, Fragment), which lib/automatic-runtime-html.js and lib/automatic-runtime-svg.js simply wire up and re-export. Nothing in the package holds mutable module-level state beyond the two schema-bound closures, so the whole design is a thin, composable adapter over hast-util-parse-selector and property-information rather than an independent tree-building engine.

Tech Stack Plain modern JavaScript (ESM-only, type: module) with JSDoc-based typing compiled to .d.ts via tsc; runtime dependencies are @types/hast, comma-separated-tokens, space-separated-tokens, hast-util-parse-selector, and property-information — all from the same syntax-tree/unified authorship, giving the whole dependency chain a single consistent style. Tooling includes xo (ESLint preset) for linting, prettier for formatting, remark-cli with remark-preset-wooorm for linting the README itself, tsd plus type-coverage (configured for 100% strict type coverage) for type-level testing, and c8 for code-coverage reporting. CI runs on GitHub Actions across two Node.js versions (lts/hydrogen and current node) with Codecov upload; there is no bundler or build step for consumers since the package ships hand-written ESM directly.

Code Quality Tests live in test/core.js (over 1,100 lines) plus dedicated JSX build tests (test/jsx-build-jsx-classic.js, test/jsx-build-jsx-automatic.js, test/jsx-build-jsx-automatic-development.js) run through Node’s built-in test runner, with c8 --100 enforcing full statement coverage and a separate test-d/ directory of .tsx fixtures asserting compile-time type behavior via tsd. Error handling is minimal and explicit — invalid children throw a plain Error with a descriptive message rather than failing silently. Naming and style are enforced mechanically (xo/prettier/remark all run as part of npm test), and the 100% type-coverage gate (with one narrowly justified ignoreFiles exception) means the JSDoc types are treated as a hard contract, not documentation-only.

What Makes It Unique Unlike general hyperscript libraries built for virtual-DOM diffing (e.g. Vue’s h, Snabbdom), hastscript targets the hast AST specification directly, so its output is inert, serializable, and consumable by any hast-compatible tool — not tied to a rendering runtime. Its selector-to-property normalization (turning .foo.bar classes, comma/space-separated attribute strings, and dash-cased overloaded booleans into hast’s canonical property shapes) is schema-driven through property-information rather than hard-coded per attribute, which is what lets the same createH factory serve both the full HTML attribute set and the SVG attribute/case-sensitivity rules from one small implementation.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search