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

onlytag

v0.1.0

Published

onlytag is a tag-based notation with three kinds of tags, JSON attributes, and decorations.

Downloads

11

Readme

onlytag

A tag-based notation parser for TypeScript. No raw text, no rendering — just structured parsing into a typed AST.

Concepts

Tags in onlytag support the following concepts:

  • Attribute — attach data to a tag with key=value syntax. Values can be JSON values, placeholders, or groups
  • Decoration — flag-like modifier on a tag. Written as .name, carries no value
  • Placeholder — symbolic reference written as $name. Parsed only, no substitution
  • Group — parenthesized list of values with (v1, v2, ...) syntax
  • Custom Delimiter — define your own delimiter pairs instead of the default <>

Quick Start

npm add onlytag
import { onlytag } from "onlytag"

const ot = new onlytag()

const tokens = ot.tokenize(`<Button .primary text="Click me" />`)
const doc = ot.parse(tokens)

console.log(ot.dump(doc))
// {
//  "type": "document",
//  "children": [
//    {
//      "type": "element",
//      "name": "Button",
//      "decorations": ["primary"],
//      "attributes": { "text": "Click me" },
//      "children": [],
//      "selfClosing": true
//    }
//  ]
// }

Tag

A tag is the fundamental unit of structure. There are three forms:

| Form | Syntax | Description | |---|---|---| | Opening | <TagName> | Begins an element that contains children | | Closing | </TagName> | Ends the matching opening tag | | Self-closing | <TagName /> | A complete element with no children |

All identifiers (tag names, attribute keys, decoration names, placeholder names) follow the same naming rule: they must start with a letter and may contain letters, digits, underscores, and hyphens ([a-zA-Z][a-zA-Z0-9_-]*). Tag names are case-sensitive.

A document can have multiple top-level tags:

<A />
<B />
<C />

This produces a DocumentNode with three children.

Attribute

Attributes attach data to a tag using key=value syntax. The key follows the same naming rule as tag names ([a-zA-Z][a-zA-Z0-9_-]*). The value must be one of the following:

JSON values — any valid JSON literal:

| Type | Example | |---|---| | String | name="Lumina" | | Number | level=42 | | Boolean | active=true | | Null | slot=null | | Object | config={"key": "value"} | | Array | items=[1, 2, 3] |

Placeholder — a symbolic reference written as $name:

<Button action=$submitHandler />

In the AST, this becomes { type: "placeholder", name: "submitHandler" }. The library parses placeholders but does not perform any substitution or replacement. Consumers are responsible for resolving placeholders in application code.

Group value — a parenthesized, comma-separated list of JSON values and/or placeholders:

<Filter eq=($mode, "dark", 1) />

In the AST, this becomes { type: "group", values: [{ type: "placeholder", name: "mode" }, "dark", 1] }.

An empty group is also valid:

<Tag key=() />

Decoration

Decorations are flag-like annotations written as .name inside a tag. They carry no value — their presence is the signal.

<Card .elevated .rounded_lg />

Decoration names follow the same naming rule as tag names ([a-zA-Z][a-zA-Z0-9_-]*). Decorations and attributes can be freely interleaved:

<Widget .d1 count=1 .d2 enabled=true />

Custom Delimiters

By default, tags use < and > as delimiters. You can configure alternative delimiter pairs through the tags option:

const ot = new onlytag({ tags: [{ opening: "|", closing: "|" }] })
const doc = ot.parse(ot.tokenize(`|Button .primary text="Click me" /|`))

Symmetric pairs (same character for opening and closing) are supported. You can also define multiple delimiter pairs simultaneously:

const ot = new onlytag({
  tags: [
    { opening: "<", closing: ">" },
    { opening: "|", closing: "|" },
  ],
})
const doc = ot.parse(ot.tokenize(`<Parent>|Child /|</Parent>`))

Allowed delimiter characters: !, #, %, &, *, +, ;, <, >, ?, @, ^, |, ~

Each character can only appear in one pair. Within a single pair, the opening and closing characters may be the same.

Whitespace

All whitespace (spaces, tabs, newlines) is ignored outside of JSON string literals. These two are equivalent:

<A level=1 />
<A
  level=1
/>

Whitespace inside JSON string attribute values (e.g., name="hello world") is preserved as part of the JSON literal.

Options

The onlytag constructor accepts an optional Options object to control which features are allowed:

| Option | Type | Default | Description | |---|---|---|---| | allowDecorations | boolean | true | Allow .name decoration syntax | | allowAttributes | boolean | true | Allow key=value attribute syntax | | allowPlaceholders | boolean | true | Allow $name placeholder values | | allowGroup | boolean | true | Allow (v1, v2, ...) group values | | allowMixedDelimiters | boolean | true | Allow the same tag name to use different delimiter pairs | | allowInconsistentClosures | boolean | true | Allow a tag name to be used as both self-closing and open/close | | tags | TagDelimiterPair[] | [{ opening: "<", closing: ">" }] | Active delimiter pairs | | maxNestingDepth | number | 1024 | Maximum nesting depth for elements |

When an option is set to false, using the corresponding feature throws an error.

// Tags only — no decorations, no attributes, no placeholders
const strict = new onlytag({
  allowDecorations: false,
  allowAttributes: false,
  allowPlaceholders: false,
})

Usage Examples

Nested Elements with Decorations and Attributes

<Character .hero .rare>
  <Identity name="Lumina" level=42 />
  <Stats base={"hp": 1500, "mp": 800} modifiers=[1.2, 0.9] />
  <Inventory>
    <Weapon .enchanted id="w-99" durability=0.85 />
  </Inventory>
</Character>

Custom Delimiters with Group Values for Conditional-Like Patterns

By combining custom delimiters, attributes, and group values, you can express conditional-like structures. Note that onlytag only parses — interpretation is up to the consumer.

const ot = new onlytag({
  tags: [
    { opening: "<", closing: ">" },
    { opening: "|", closing: "|" },
  ],
})

const input = `
<Layout>
  <Header title="My App" />
  | if equal=($theme, "dark") /|
  <Body>
    <Content text="Hello" />
  </Body>
</Layout>
`

const doc = ot.parse(ot.tokenize(input))

The | if equal=($theme, "dark") /| tag uses pipe delimiters with a group value containing a placeholder and a string. The library parses it into an AST node — it does not evaluate the condition.

Strict Options for Structured Logging

With restrictive options, onlytag can be used to define structured log entries. Multiple top-level tags are supported:

const ot = new onlytag({
  allowDecorations: true,
  allowAttributes: true,
  allowPlaceholders: false,
  allowInconsistentClosures: false,
})

const input = `
<Event .info timestamp=1710000000>
  <Payload type="login" success=true />
</Event>

<Event .warn timestamp=1710000123>
  <Payload type="disk_usage" percent=85.5 />
</Event>

<Event .error .critical timestamp=1710000500>
  <Details code=500 message="Internal Server Error" />
</Event>
`

const doc = ot.parse(ot.tokenize(input))
// doc.children.length === 3

By disabling placeholders and inconsistent closures, you ensure a stricter, more predictable document format suitable for structured records.

Serialization

The onlytag class provides dump for JSON serialization of the AST:

const ot = new onlytag()

// Parse
const doc = ot.parse(ot.tokenize(`<Button .primary text="Click me" />`))

// Serialize to JSON string
const json = ot.dump(doc)

dump calls JSON.stringify on the DocumentNode.

Limitations

  • No raw text between tags — all content must be expressed as tags, attributes, or decorations
  • No placeholder substitution — $name placeholders are parsed into the AST but not replaced or resolved
  • No rendering or execution — onlytag is a parser only; interpretation of the AST is the consumer's responsibility
  • No template engine features — no loops, conditionals, or includes