Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c17c170
Testing no flushing html-mod
avigoldman Dec 26, 2025
8fd8c2c
clean up
avigoldman Dec 26, 2025
7944121
clean up
avigoldman Dec 26, 2025
215d55d
clean up
avigoldman Dec 26, 2025
3146569
clean up
avigoldman Dec 26, 2025
f9119ef
clean up
avigoldman Dec 26, 2025
563a2b2
clean up
avigoldman Dec 26, 2025
b08fd79
clean up
avigoldman Dec 26, 2025
e583562
clean up
avigoldman Dec 26, 2025
3f7b86b
clean up
avigoldman Dec 26, 2025
1c6cbfd
clean up
avigoldman Dec 26, 2025
18e9807
clean up
avigoldman Dec 26, 2025
224ebbe
clean up
avigoldman Dec 26, 2025
58b6851
clean up
avigoldman Dec 26, 2025
cbeef0d
clean up
avigoldman Dec 26, 2025
740c4ca
clean up
avigoldman Dec 26, 2025
47d8b70
clean up
avigoldman Dec 26, 2025
822380a
clean up
avigoldman Dec 26, 2025
7dbb07a
clean up
avigoldman Dec 26, 2025
2266f4c
clean up
avigoldman Dec 28, 2025
1cdcbab
clean up
avigoldman Dec 28, 2025
edd5e72
clean up
avigoldman Dec 28, 2025
cfb5209
clean up
avigoldman Dec 28, 2025
bc35abe
clean up
avigoldman Dec 28, 2025
acceb49
clean up
avigoldman Dec 28, 2025
6ff1cae
Remove .js extensions from imports and fix import ordering
avigoldman Dec 28, 2025
273de45
Fix ESLint/Prettier import ordering conflict
avigoldman Dec 28, 2025
3542894
clean up
avigoldman Dec 28, 2025
945b947
clean up
avigoldman Dec 28, 2025
a09d8c4
Simplify experimental README to be super focused
avigoldman Dec 29, 2025
edf07d4
Optimize AST updates with targeted subtree traversal
avigoldman Dec 29, 2025
024c1a4
Update benchmark numbers with latest measurements
avigoldman Dec 29, 2025
6933912
Fix TypeScript errors in targeted-update tests
avigoldman Dec 29, 2025
7474eec
Remove AI-generated code slop
avigoldman Dec 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .changeset/experimental-auto-flush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
'@ciolabs/html-mod': minor
---

Add experimental auto-flush implementation that eliminates manual `flush()` calls

## New Features

### Experimental Auto-Flush Implementation

- **Import path**: `@ciolabs/html-mod/experimental`
- Automatically synchronizes AST after every modification
- No manual `flush()` calls required
- Element references stay valid across modifications
- 2.16x faster for modify+query patterns (most common in visual editors)

### Dataset API (Both Versions)

- Added `dataset` property to `HtmlModElement`
- Full Proxy-based implementation with camelCase ↔ kebab-case conversion
- Compatible with standard DOM `DOMStringMap` interface
- Supports all dataset operations: get, set, delete, enumerate

## Performance

**Benchmarks (vs stable version):**

- Parse + setAttribute: 1.19x faster
- Modify + query pattern: 2.16x faster
- Real-world templates: 1.29x faster
- Batched modifications: 3.07x slower (rare pattern)

## Documentation

- See `EXPERIMENTAL.md` for complete feature comparison
- Migration guide included for switching from stable to experimental
- Comprehensive deployment recommendations

## Testing

- 624 tests passing (vs 196 in stable)
- Includes adversarial testing, stress testing, and real-world scenarios
- Zero drift over 10,000+ consecutive operations
- Handles malformed HTML gracefully

## Breaking Changes

None - fully backward compatible. The experimental version is available at a separate import path (`/experimental`).
14 changes: 14 additions & 0 deletions .changeset/prettier-eslint-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@ciolabs/config-prettier': patch
---

Remove Prettier import sorting plugin to resolve conflict with ESLint import/order rule

## Changes

- Removed `@trivago/prettier-plugin-sort-imports` plugin
- Removed `importOrder` and `importOrderSeparation` configuration
- ESLint's `import/order` rule now handles all import organization
- Fixes conflict where Prettier and ESLint were fighting over import formatting

This allows both tools to work together harmoniously without conflicting changes.
6 changes: 2 additions & 4 deletions packages/config-prettier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@
"clean": "rm -rf dist"
},
"peerDependencies": {
"prettier": "^3.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.0.0"
"prettier": "^3.0.0"
},
"devDependencies": {
"prettier": "^3.3.3",
"tsup": "^8.0.0",
"typescript": "^5.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0"
"typescript": "^5.0.0"
}
}
3 changes: 0 additions & 3 deletions packages/config-prettier/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ const config: Config = {
bracketSameLine: false,
arrowParens: 'avoid',
endOfLine: 'lf',
plugins: ['@trivago/prettier-plugin-sort-imports'],
importOrder: ['^[./]'],
importOrderSeparation: true,
overrides: [
{
files: ['*.{js,ts,gts,jsx,tsx}', '.*.{js,ts,gts,jsx,tsx}'],
Expand Down
51 changes: 38 additions & 13 deletions packages/html-mod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,9 @@ console.log(h.toString());
//=> <div>world</div>
```

## Flushing the manipulations
## Flushing and AST Synchronization

When you are manipulating the HTML, you are really manipulating the underlying string, not the AST. So when you run queries you are only ever querying against the initial HTML string. **You are not querying against the manipulated HTML string**.

If you need to query against the manipulated HTML string, you need to "flush" the manipulations. This will take the manipulated HTML string and reparse it. This is not a cheap operation, so you should only do it when you absolutely need to.
When you modify the HTML, the AST (Abstract Syntax Tree) used for queries becomes out of sync with the string. You need to call `flush()` to reparse and synchronize the AST before querying:

```typescript
import { HtmlMod } from '@ciolabs/html-mod';
Expand All @@ -97,25 +95,40 @@ const h = new HtmlMod('<div>hello</div>');

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

console.log(h.querySelectorAll('div').length); //=> 1

// Must flush before querying to see the changes
h.flush();

console.log(h.querySelectorAll('div').length); //=> 2
```

You can check if the manipulations have been flushed by calling `isFlushed()`:
You can check if the AST needs to be flushed:

```typescript
import { HtmlMod } from '@ciolabs/html-mod';
console.log(h.isFlushed()); //=> false after modifications, true after flush()
```

const h = new HtmlMod('<div>hello</div>');
### Experimental: Auto-Flush Version

An experimental version is available that automatically keeps the AST synchronized without manual `flush()` calls. This provides better ergonomics and performance for interactive use cases:

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

const h = new HtmlMod('<div>hello</div>');
h.querySelector('div')!.append('<div>world</div>');

console.log(h.isFlushed()); //=> false
// No flush needed - queries work immediately!
console.log(h.querySelectorAll('div').length); //=> 2
```

**Benefits:**

- βœ… No manual flush() calls needed
- βœ… 2.23x faster for modify+query patterns
- βœ… Zero drift guarantee over 10,000+ operations
- βœ… Perfect for visual editors and interactive UIs

See [src/experimental/README.md](./src/experimental/README.md) for complete documentation, benchmarks, and migration guide.

## 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.
Expand Down Expand Up @@ -162,7 +175,7 @@ Returns `true` if the resulting HTML is empty.

#### isFlushed() => boolean

Returns `true` if the source string is in sync with the AST.
Returns `true` if the AST positions are in sync with the source string. Returns `false` after modifications until `flush()` is called.

#### generateDecodedMap()

Expand All @@ -182,10 +195,12 @@ Returns a new `HtmlMod` instance with the same HTML string.

#### flush() => this

Flushes the manipulations. This will take the manipulated HTML string and reparse it. This is not a cheap operation, so you should only do it when you need to.
Reparses the HTML to synchronize the AST with string modifications. Required after any modifications before querying.

Returns `this`.

**Note:** The experimental version (see above) automatically maintains synchronization, making manual flush calls unnecessary.

#### querySelector(selector: string) => HtmlModElement | null

Returns the first `HtmlModElement` that matches the selector.
Expand Down Expand Up @@ -216,6 +231,16 @@ An array of the classes on the element.

The class attribute value of the element.

#### dataset: DOMStringMap

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

```typescript
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.
Expand Down
8 changes: 7 additions & 1 deletion packages/html-mod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./experimental": {
"types": "./dist/experimental/index.d.ts",
"import": "./dist/experimental/index.mjs",
"require": "./dist/experimental/index.js"
}
},
"files": [
Expand All @@ -30,7 +35,8 @@
"build": "tsup",
"dev": "tsup --watch",
"clean": "rm -rf dist",
"test": "vitest run"
"test": "vitest run",
"benchmark": "npm run build && node dist/benchmark.mjs"
},
"dependencies": {
"@ciolabs/htmlparser2-source": "workspace:*",
Expand Down
Loading