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

picotags

v0.0.3

Published

Tiny XML-like tag tokenizer and transformer for inline markup.

Readme

picotags

English

用于内联标记的轻量 XML-like 标签 tokenizer 和 transformer。

当你想在字符串里使用轻量标签时,可以使用 picotags,例如终端颜色里的 <red>failed</red>、CLI 输出里的 <dim>hint</dim>,或者日志、消息和富文本管线里的自定义内联标记。它负责解析标签结构,具体如何渲染或替换由你的代码决定。

特性

  • 运行时很小,无依赖。
  • tokenize() 返回可流式消费的 iterator。
  • 支持开始标签、结束标签、文本和自闭合标签。
  • 支持 boolean、双引号、单引号和裸值属性。
  • token 包含 start / end 源码位置。
  • 对已识别标签做严格嵌套校验。
  • transform() 返回可接入 source map 工具的输出片段。
  • 不合法的 < 会保留为文本,不会抛错。

安装

pnpm add picotags
import { replace, tokenize, transform } from "picotags";

Tokenize

tokenize(input) 返回一个 iterator,可以用 for...ofArray.from() 消费。

import { tokenize } from "picotags";

const tokens = Array.from(tokenize('a <dim level=1 flag>b<br /></dim>'));

console.log(tokens);

输出:

[
  { type: "text", text: "a ", start: 0, end: 2 },
  {
    type: "opentag",
    name: "dim",
    attrs: [
      { name: "level", value: "1", raw: "level=1", start: 7, end: 14 },
      { name: "flag", value: true, raw: "flag", start: 15, end: 19 },
    ],
    raw: "<dim level=1 flag>",
    start: 2,
    end: 20,
  },
  { type: "text", text: "b", start: 20, end: 21 },
  { type: "selfclosetag", name: "br", attrs: [], raw: "<br />", start: 21, end: 27 },
  { type: "closetag", name: "dim", raw: "</dim>", start: 27, end: 33 },
]

已识别的标签必须正确嵌套并关闭。结构不合法时会抛出 PicotagsSyntaxError

语法错误

tokenize()replace()transform() 在遇到不合法标签结构时会抛出 PicotagsSyntaxError

import { PicotagsSyntaxError, tokenize } from "picotags";

try {
  Array.from(tokenize("<a><b></a></b>"));
} catch (error) {
  if (error instanceof PicotagsSyntaxError) {
    console.log(error.code);
    // mismatched-close-tag
  }
}

错误码:

type SyntaxErrorCode =
  | "unexpected-close-tag"
  | "mismatched-close-tag"
  | "unclosed-tag";

Replace

replace(input, handlers) 按源码顺序替换 token。

handler 返回字符串时,会用返回值替换当前 token。返回 undefined 时,会保留当前 token 的原始文本。

import { replace } from "picotags";

const output = replace("<dim>Hello <br /></dim>", {
  onopentag(token) {
    return `[${token.name}]`;
  },
  onselfclosetag(token) {
    return `[${token.name}/]`;
  },
  onclosetag(token) {
    return `[/${token.name}]`;
  },
});

console.log(output);
// [dim]Hello [br/][/dim]

文本也可以替换:

replace("<dim>Hello</dim>", {
  ontext(token) {
    return token.text.toUpperCase();
  },
});
// <dim>HELLO</dim>

Transform

transform(input, handlers) 使用和 replace() 相同的替换回调,但额外返回源码位置片段。

它返回生成后的代码和 chunk 映射信息:

import { transform } from "picotags";

const result = transform("<dim>Hello<br /></dim>", {
  onopentag(token) {
    return `[${token.name}]`;
  },
  onselfclosetag(token) {
    return `[${token.name}/]`;
  },
  onclosetag(token) {
    return `[/${token.name}]`;
  },
});

console.log(result.code);
// [dim]Hello[br/][/dim]

console.log(result.chunks);

每个 chunk 都包含原始范围和生成范围:

type TransformChunk = {
  value: string;
  original: string;
  token: Token;
  generatedStart: number;
  generatedEnd: number;
  originalStart: number;
  originalEnd: number;
};

这不是 source map 实现。它只提供后续接入 source map 库所需的原始范围数据。

属性

支持的属性形式:

<tag disabled>
<tag count="1">
<tag count='1'>
<tag count=1>

解析结果:

[
  { name: "disabled", value: true, raw: "disabled", start: 1, end: 9 },
  { name: "count", value: "1", raw: 'count="1"', start: 10, end: 19 },
]

属性会保留源码顺序。每个属性都带自己的 rawstartend,便于保留精确位置信息。

属性值始终是字符串;没有显式值的 boolean 属性为 true

Token 类型

type Token =
  | TextToken
  | OpenTagToken
  | SelfCloseTagToken
  | CloseTagToken;

文本 token:

type TextToken = {
  type: "text";
  text: string;
  start: number;
  end: number;
};

开始标签:

type OpenTagToken = {
  type: "opentag";
  name: string;
  attrs: AttrToken[];
  raw: string;
  start: number;
  end: number;
};

自闭合标签:

type SelfCloseTagToken = {
  type: "selfclosetag";
  name: string;
  attrs: AttrToken[];
  raw: string;
  start: number;
  end: number;
};

属性 token:

type AttrToken = {
  name: string;
  value: string | true;
  raw: string;
  start: number;
  end: number;
};

结束标签:

type CloseTagToken = {
  type: "closetag";
  name: string;
  raw: string;
  start: number;
  end: number;
};

语法

标签名和属性名必须匹配:

[A-Za-z][A-Za-z0-9:_-]*

示例:

<dim>
<x-foo:bar_1>

不合法标签会保留为文本:

Array.from(tokenize("a < b"));
// [{ type: "text", text: "a < b", start: 0, end: 5 }]