@ciolabs/html-mod

中文文档
1.3.0发布于 2026/07/15Apache-2.0 WITH Commons-ClauseTypeScriptESM + CJS构建来源已验证近 30 天下载 3万

Manipulate HTML strings with a Browser-like API

原始使用文档
文档目录18 节
  1. Why?
  2. The problems
  3. The solution
  4. Install
  5. Usage
  6. AST Synchronization
  7. Migrating from older versions
  8. HtmlMod
  9. constructor(html: string, options?)
  10. Methods
  11. HtmlModElement
  12. Properties
  13. Methods
  14. HtmlModText
  15. Properties
  16. Methods
  17. Types
  18. HtmlModOptions

@ciolabs/html-mod

Manipulate HTML strings with a Browser-like API

Why?

The problems

Why is this necessary? Why not just use a tried and true tool like cheerio? There is a whole group of tools made for this.

That class of tools are parsing. They parse the HTML into an Abstract Syntax Tree (AST) and then you can manipulate the AST. Then when you're ready to output the HTML, you serialize the AST back into a string.

This is great if the HTML is totally valid and has no quirks.

But we are building for two use cases:

  1. Email code
  2. User source files

Email code

Email code will have plenty of quirks. And it will contain personalization languages like Liquid and Handlebars. We need to be able to parse the HTML, but we can't serialize it into an AST because it might break the personalization languages.

For example, running this HTML through cheerio:

<a href={{ url }}>Click me</a>

will result in this:

<a href="{{" url }}>Click me</a>

As you can see that broke the Liquid syntax.

User source files

User source files might have similar quirks. But more importantly, we want to edit their file without changing the whole file. Just the one part we are trying to edit.

An example is if we are trying to add a class to this div:

<main class='main'>
  <div class="button">Click me</div>
</main>

If we use cheerio it will reserialize all of the HTML with double quotes:

<main class="main">
  <div class="button added-class">Click me</div>
</main>

This is fine programmatically, but it's not what the user expects. We just changed potentially hundreds of lines of code, but the user only changed one single attribute.

And if the user had anything not 100% correct (i.e. a missing tag) we just "fixed" it - a totally unexpected edit!

The solution

We need a tool that can parse the HTML, but not serialize it. We need a tool that can edit the HTML without changing the whole file.

This is where html-mod comes in. It's a tool that can parse the HTML so we get the AST-style manipulation. But instead of manipulating the AST we take the HTML and manipulate the string directly. That way we only ever change the part of the HTML we want to change.

Install

npm install @ciolabs/html-mod

Usage

import { HtmlMod } from '@ciolabs/html-mod';

const h = new HtmlMod('<div>hello</div>');

h.querySelector('div')!.innerHTML = 'world';

console.log(h.toString());
//=> <div>world</div>

AST Synchronization

The AST is automatically kept in sync with string modifications. You can freely modify and query without any manual steps:

import { HtmlMod } from '@ciolabs/html-mod';

const h = new HtmlMod('<div>hello</div>');

h.querySelector('div')!.append('<div>world</div>');

// Queries always reflect the latest modifications
console.log(h.querySelectorAll('div').length); //=> 2

Element references stay valid across modifications — no need to re-query after making changes:

const h = new HtmlMod('<div><p>Hello</p></div>');
const div = h.querySelector('div')!;

div.setAttribute('class', 'active');

// Element reference is still valid
div.setAttribute('data-id', '123'); // ✅ Works perfectly

// Queries reflect all modifications
const p = h.querySelector('p'); // ✅ Finds the element

Migrating from older versions

If you're upgrading from a version that required manual flush() calls, you can safely remove them. The @ciolabs/html-mod/experimental import path still works but is deprecated — import from @ciolabs/html-mod directly instead.

HtmlMod

The HtmlMod class is the main class for this package. It's the class that you use to query for HtmlModElement elements and manipulate the HTML string.

constructor(html: string, options?)

options

HtmlModOptions | optional

The full options for htmlparser2. This is the underlying parser used to parse the HTML.

options.HtmlModElement

Class | optional

The class to use for the HtmlModElement class. This is the class that is used to manipulate the HTML. By default, it uses the HtmlModElement class in this package.

Methods

trim() => this

Removes all whitespace from the beginning and end of the HTML string.

trimStart() => this

Removes all whitespace from the beginning of the HTML string.

trimEnd() => this

Removes all whitespace from the end of the HTML string.

trimLines() => this

Removes empty lines from the start and end.

isEmpty() => boolean

Returns true if the resulting HTML is empty.

toString() => string

Returns the manipulated HTML string.

clone() => HtmlMod

Returns a new HtmlMod instance with the same HTML string.

querySelector(selector: string) => HtmlModElement | null

Returns the first HtmlModElement that matches the selector.

querySelectorAll(selector: string) => HtmlModElement[]

Returns an array of HtmlModElement that match the selector.

batch(callback: () => T) => T

Runs the callback with writes batched, then applies them all at once. Use it around loops that make many small writes — every unbatched mutation pays a full string splice plus an AST position update over all following nodes, so write-heavy loops scale quadratically with document size. Batching applies the queued edits in one string rebuild and one position pass, which stays flat as documents grow (a 3,600-write loop on a 232KB document drops from ~500ms to ~7ms).

Batched operations: setAttribute, removeAttribute (and everything built on them — dataset, id/className setters, toggleAttribute), plus the structural inserts before, after, prepend, and append. Everything else applies immediately, flushing any queued edits first.

const htmlMod = new HtmlMod(html);

htmlMod.batch(() => {
  for (const element of htmlMod.querySelectorAll('*')) {
    element.dataset.key = generateKey();
  }
});

htmlMod.toString(); // all edits applied

The result is always identical to running the same operations unbatched. Reads that could observe pending state flush the batch automatically first: toString(), innerHTML/outerHTML, selector queries, attribute reads on an element with queued edits, children reads when inserts are pending, and any non-batched mutation. Conflicting writes (the same attribute twice, a repeated before/after on one element, prepend/append mixes) also flush first, so batching is safe to wrap around any loop — worst case it degrades to unbatched behavior.

One caveat: raw AST position fields (the startIndex/endIndex on nodes reached via children) read inside an open batch reflect pre-batch coordinates. They are mutually consistent with each other and become final when the batch ends. Position getters that combine AST offsets with the source string (e.g. sourceRange) flush the batch first and always return final coordinates.

Batches nest; edits apply when the outermost batch exits (or on the first flushing read). The callback's return value is passed through.

HtmlModElement

The HtmlModElement class is the class that is used to manipulate the HTML. It's the class that is returned when you run a query.

Properties

tagName: string

The tag name of the element. This can be set.

id: string

The id of the element. This can be set.

classList: string[]

An array of the classes on the element.

className: string

The class attribute value of the element.

dataset: DOMStringMap

An object containing all data-* attributes. Can be read and modified like a plain object:

const el = h.querySelector('div')!;
el.dataset.userId = '123'; // Sets data-user-id="123"
console.log(el.dataset.userId); // "123"

attributes: Attribute[]

An array of Attribute objects.

innerHTML: string

The inner HTML of the element. This can be set.

outerHTML: string

The outer HTML of the element.

textContent: string

The text content of the element. This can be set.

children: SourceChildNode[]

An array of the children of the element.

parent: HtmlModElement | null

The parent element.

sourceRange: object

Position information for the element in the source:

{
  startLineNumber: number;
  startColumn: number;
  endLineNumber: number;
  endColumn: number;
}

Methods

before(html: string) => this

Inserts the HTML before the element.

after(html: string) => this

Inserts the HTML after the element.

prepend(html: string) => this

Prepends the HTML to the element.

append(html: string) => this

Appends the HTML to the element.

remove() => this

Removes the element.

replaceWith(html: string) => this

Replaces the element with the HTML.

hasAttribute(name: string) => boolean

Returns true if the element has the attribute.

hasAttributes() => boolean

Returns true if the element has any attributes.

getAttribute(name: string) => string | null

Returns the value of the attribute.

getAttributeNames() => string[]

Returns an array of the attribute names.

setAttribute(name: string, value: string) => this

Sets the attribute.

toggleAttribute(name: string, force?: boolean) => this

Toggles the attribute.

force: If true, the attribute will be added. If false, the attribute will be removed. If not specified, the attribute will be toggled.

removeAttribute(name: string) => this

Removes the attribute.

querySelector(selector: string) => HtmlModElement | null

Returns the first HtmlModElement that matches the selector.

querySelectorAll(selector: string) => HtmlModElement[]

Returns an array of HtmlModElement that match the selector.

toString() => string

Returns the HTML string. Same as outerHTML.

clone() => HtmlModElement

Returns a new HtmlModElement instance with the same HTML string.

Useful if you want to manipulate the HTML without affecting the original HtmlMod instance.

HtmlModText

The HtmlModText class represents a text node in the HTML. It's returned as part of an element's children array.

Properties

textContent: string

The decoded text content of the node. This can be set (HTML entities will be escaped).

innerHTML: string

The raw HTML content of the text node. This can be set.

Methods

toString() => string

Returns the raw text node content.

Types

HtmlModOptions

type HtmlModOptions = Options & {
  HtmlModElement?: typeof HtmlModElement;
};

Where Options inherits all htmlparser2 ParserOptions and DomHandlerOptions.

版本与发布记录

@ciolabs/html-mod 共发布 15 个版本,首次发布于 2025/09/19;过去 12 个月发布了 10 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.3.0latest2026/07/153 个月前
1.2.02026/07/153 个月前
1.1.22026/06/224 个月前
1.1.12026/06/224 个月前
1.1.02026/05/295 个月前
1.0.12026/02/228 个月前

关于 @ciolabs/html-mod 的常见问题

@ciolabs/html-mod 是什么?

Manipulate HTML strings with a Browser-like API

如何安装 @ciolabs/html-mod?

在项目目录运行 npm install @ciolabs/html-mod,或使用 pnpm add / yarn add / bun add。

@ciolabs/html-mod 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ciolabs/html-mod 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@ciolabs/html-mod 可以商用吗?

@ciolabs/html-mod 使用 Apache-2.0 WITH Commons-Clause 许可。声明的许可为 Apache-2.0 WITH Commons-Clause,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@ciolabs/html-mod 的下载量有多少?

根据 npm 官方下载统计,@ciolabs/html-mod 在 2026/10/10 – 2026/11/08 这 30 天内被下载 30,159 次,日均约 1,005 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ciolabs/html-mod 还在维护吗?

最新版本 1.3.0 发布于 2026/07/15(3 个月前);至今共发布 15 个版本,其中过去 12 个月发布了 10 个;首次发布于 2025/09/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。