Conversation
fc-anjos
force-pushed
the
fix/node-cjs-packaging
branch
from
August 15, 2025 13:12
1ad4f6b to
1c7f6d3
Compare
This pull request changes the way the parser treats multiple control
flow elements in a single ERB tag.
Since we need to be able to group the ERB tags, we need to have the ERB
tags independently. This pull request therefore introduces a
`ERBMultipleBlocksInTagError` that gets added to a `ERBContentNode` that
contains multiple unclosed control flow elements.
The following is not valid:
```html+erb
<%
list1.each do |i|
list2.each do |j| %>
<% end %>
<% end %>
```
Which now gets parsed as:
```js
@ DocumentNode (location: (1:0)-(6:9))
└── children: (3 items)
├── @ ERBBlockNode (location: (1:0)-(5:11))
│ ├── errors: (1 item)
│ │ └── @ ERBMultipleBlocksInTagError (location: (1:0)-(3:24))
│ │ └── message: "Multiple unclosed control flow blocks in a single ERB tag. Split each block into its own ERB tag, or close all blocks within the same tag."
│ │
│ ├── tag_opening: "<%" (location: (1:0)-(1:2))
│ ├── content: "
│ list1.each do |i|
│ list2.each do |j| " (location: (1:2)-(3:22))
│ ├── tag_closing: "%>" (location: (3:22)-(3:24))
│ ├── body: (1 item)
│ │ └── @ HTMLTextNode (location: (3:24)-(5:2))
│ │ ├── errors: []
│ │ └── content: "\n \n "
│ │
│ └── end_node:
│ └── @ ERBEndNode (location: (5:2)-(5:11))
│ ├── tag_opening: "<%" (location: (5:2)-(5:4))
│ ├── content: " end " (location: (5:4)-(5:9))
│ └── tag_closing: "%>" (location: (5:9)-(5:11))
│
├── @ HTMLTextNode (location: (5:11)-(6:0))
│ └── content: "\n"
│
└── @ ERBContentNode (location: (6:0)-(6:9))
├── errors: (1 item)
│ └── @ ERBControlFlowScopeError (location: (6:0)-(6:9))
│ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)."
│ └── keyword: "`<% end %>`"
│
├── tag_opening: "<%" (location: (6:0)-(6:2))
├── content: " end " (location: (6:2)-(6:7))
├── tag_closing: "%>" (location: (6:7)-(6:9))
├── parsed: true
└── valid: false
```
Previously, this failed with:
```js
@ DocumentNode (location: (1:0)-(6:9))
└── children: (3 items)
├── @ ERBBlockNode (location: (1:0)-(5:11))
│ ├── tag_opening: "<%" (location: (1:0)-(1:2))
│ ├── content: "
│ list1.each do |i|
│ list2.each do |j| " (location: (1:2)-(3:22))
│ ├── tag_closing: "%>" (location: (3:22)-(3:24))
│ ├── body: (1 item)
│ │ └── @ HTMLTextNode (location: (3:24)-(5:2))
│ │ ├── errors: []
│ │ └── content: "\n \n "
│ │
│ └── end_node:
│ └── @ ERBEndNode (location: (5:2)-(5:11))
│ ├── tag_opening: "<%" (location: (5:2)-(5:4))
│ ├── content: " end " (location: (5:4)-(5:9))
│ └── tag_closing: "%>" (location: (5:9)-(5:11))
│
├── @ HTMLTextNode (location: (5:11)-(6:0))
│ └── content: "\n"
│
└── @ ERBContentNode (location: (6:0)-(6:9))
├── errors: (1 item)
│ └── @ ERBControlFlowScopeError (location: (6:0)-(6:9))
│ ├── message: "`<% end %>` appears outside its control flow block. Keep ERB control flow statements together within the same HTML scope (tag, attribute, or content)."
│ └── keyword: "`<% end %>`"
│
├── tag_opening: "<%" (location: (6:0)-(6:2))
├── content: " end " (location: (6:2)-(6:7))
├── tag_closing: "%>" (location: (6:7)-(6:9))
├── parsed: true
└── valid: false
```
At least, this makes the error more actionable.
The `ERBContentNode` can contain multiple control flow elements, if they
also get closed in the same tag. So this is still valid:
```html+erb
<%
if something?
if something_else?
call_something(argument)
end
end
%>
```
Resolves marcoroth#604
Resolves marcoroth#969
This pull request updates the lexer to support detecting `<%grapqhl` as a valid ERB tag opening. That way, we can treat them differently and don't their content as Ruby code and parse them using Prism. The [`graphql-client`](https://github.com/github-community-projects/graphql-client?tab=readme-ov-file#rails-erb-integration) gem exposes an ERB extension that allows to use custom `<%grapqhl %>` tags in ERB files. ```html+erb <%# app/views/humans/human.html.erb %> <%graphql fragment HumanFragment on Human { name homePlanet } %> <p><%= human.name %> lives on <%= human.home_planet %>.</p> ``` With this pull request, the example from marcoroth#972 now gets parsed with a `"<%graphql"` as the `tag_opening` and with no Ruby parse errors: ```js @ DocumentNode (location: (1:0)-(15:2)) └── children: (1 item) └── @ ERBContentNode (location: (1:0)-(15:2)) ├── tag_opening: "<%graphql" (location: (1:0)-(1:9)) ├── content: " fragment Comment on Comment { ... on PullRequestReviewComment { pullRequest { id repository { name owner { login } } } } } " (location: (1:9)-(15:0)) ├── tag_closing: "%>" (location: (15:0)-(15:2)) ├── parsed: false └── valid: true ``` At runtime, these tags are [getting converted to ERB comments](https://github.com/github-community-projects/graphql-client/blob/ba1328fe77a35c3558758db2cbe302f50f64e283/lib/graphql/client/erubi_enhancer.rb#L17). In the future, we might also want to update the `Herb::Engine` to skip these tags from being compiled. Resolves marcoroth#972
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.11 to 7.3.0. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…h#985) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.48.0 to 8.49.0. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [prettier](https://github.com/prettier/prettier) from 3.6.2 to 3.7.4. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ind-class-sorter (marcoroth#979) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.4 to 25.0.2. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…-lowercase` offense (marcoroth#976) This pull request fixes marcoroth#975 Updates autofix() so that after fixing the offending tag name, it walks up to the parent element and also fixes the related counterpart tag name (opening/closing) in the same element. Let me know if you see any improvements or have any suggestions! Thank you.
Bumps [rimraf](https://github.com/isaacs/rimraf) from 6.1.0 to 6.1.2. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…h#988) Follow up to: marcoroth#973 This pull request updates the `Herb::Engine` to not compile `<%graphql %>` tags when compiling HTML+ERB templates. This mirrors the behavior in `ErubiEnhancer` from the [`graphql-client`](https://github.com/github-community-projects/graphql-client/blob/ba1328fe77a35c3558758db2cbe302f50f64e283/lib/graphql/client/erubi_enhancer.rb#L17) gem.
…#974) This pull request fixes the parsing of `<` and `>` characters in text content where are a non-ambiguous. Previously, `<kbd>></kbd>` and similar patterns would raise an UnexpectedError because `>` wasn't recognized as valid text content. Similarly, `<` followed by non-tag characters (like spaces or digits) wasn't handled correctly. Now, the following is valid: `<kbd>></kbd>` And gets parsed as: ```js @ DocumentNode (location: (1:0)-(1:12)) └── children: (1 item) └── @ HTMLElementNode (location: (1:0)-(1:12)) ├── open_tag: │ └── @ HTMLOpenTagNode (location: (1:0)-(1:5)) │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ ├── tag_name: "kbd" (location: (1:1)-(1:4)) │ ├── tag_closing: ">" (location: (1:4)-(1:5)) │ ├── children: [] │ └── is_void: false │ ├── tag_name: "kbd" (location: (1:1)-(1:4)) ├── body: (1 item) │ └── @ HTMLTextNode (location: (1:5)-(1:6)) │ └── content: ">" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:6)-(1:12)) │ ├── tag_opening: "</" (location: (1:6)-(1:8)) │ ├── tag_name: "kbd" (location: (1:8)-(1:11)) │ ├── children: [] │ └── tag_closing: ">" (location: (1:11)-(1:12)) │ ├── is_void: false └── source: "HTML" ``` Resolves marcoroth#914
Bumps [vitepress](https://github.com/vuejs/vitepress) from 2.0.0-alpha.12 to 2.0.0-alpha.15. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The blog post’s mp4 demos weren’t rendering because they used image-style embeds, so I replaced them with video tags (autoplay/loop/muted/controls). Thank you! <img width="659" height="165" alt="image" src="https://github.com/user-attachments/assets/36c7c929-8b85-4d12-8d32-0b5bd0c121f0" /> ref. https://herb-tools.dev/blog/whats-new-in-herb-v0-8
Bumps [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) from 1.13.0 to 1.35.0. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@vscode/vsce](https://github.com/Microsoft/vsce) from 3.6.0 to 3.7.1. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@shikijs/vitepress-twoslash](https://github.com/shikijs/shiki/tree/HEAD/packages/vitepress-twoslash) from 3.15.0 to 3.20.0. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@mapbox/node-pre-gyp](https://github.com/mapbox/node-pre-gyp) from 2.0.0 to 2.0.3. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…verlay (marcoroth#999) When showing the Herb overlay: - For non-static positions (absolute, fixed, sticky, relative), the existing position value should be respected. - Non-static elements already serve as the positioning context for absolutely positioned children, so overriding them is unnecessary.
Bumps [nx](https://github.com/nrwl/nx/tree/HEAD/packages/nx) from 22.1.3 to 22.3.3. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
… element (marcoroth#1000) When attaching the Herb overlay to the `<html>` element, the label may be positioned outside of the viewport because the default CSS uses a negative top value. This adds a small conditional so the label remains visible when the overlay is attached to `<html>`. I wasn’t sure whether adding this kind of conditional styling in JavaScript is acceptable here, or if you’d prefer a CSS-based solution. Please let me know if you have a preferred direction. --------- Signed-off-by: Marco Roth <marco.roth@intergga.ch> Co-authored-by: Marco Roth <marco.roth@intergga.ch>
This PR adds a new version of the `hb_array` called `hb_narray` that implements an array that can actually hold the elements it stores. The existing `hb_array` only allows to store pointers to heap allocated elements, the new `hb_narray` acts as a container that also provides the backing memory for the elements it holds. It's important to note that you can still make the elements pointers if you want to. ## Why? - Improves locality of the elements - Reduces the number of individual heap allocations for elements ## Next steps 1. Get this merged 2. Refactor existing `hb_array` usages 3. Remove `hb_array` and rename `hb_narray` to `hb_array` ## Memory layout 
…marcoroth#1271) This pull request adds a matching `shouldLintFile()` check that respects `files.exclude`, `linter.exclude`, and default `exclude` patterns (like `vendor/**/*`, `node_modules/**/*`) before linting a document in the language server. Resolves marcoroth#1179
Bumps [@shikijs/vitepress-twoslash](https://github.com/shikijs/shiki/tree/HEAD/packages/vitepress-twoslash) from 3.22.0 to 4.0.1. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) from 1.50.0 to 1.51.0. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [yeoman-generator](https://github.com/yeoman/generator) from 7.5.1 to 8.0.0. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…uage-server (marcoroth#1272) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.19.13 to 25.3.3. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…h#1274) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.55.0 to 8.56.1. Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Closes marcoroth#1264 This PR refactors the linter code to use a static `ruleName` property instead of `name` on the instance.
…rcoroth#1280) This pull request updates the formatter to detect `white-space` significant elements by either looking at the `style` attribute and seeing if it includes the `white-space:` property with the relevant value, or if it includes one of the Tailwind whitespace-preserving classes. Additionally, it moves some of the AST utils related to attributes from the linter package to `core` so we can also use them in the formatter. Resolves marcoroth#1095
…oroth#1281) This pull request changes the native extension loading from `require_relative` to `require` to ensure compatibility with the latest RubyGems, which no longer installs native extensions into the gem's `lib/` directory by default. See ruby/rubygems#9240. Using `require` resolves the extension through `$LOAD_PATH`, which RubyGems populates with both the `lib/` and extension directories, so this works on both old and new versions of RubyGems. Resolves marcoroth#1244 Co-Authored-By: Daniel Colson <composerinteralia@github.com>
…arcoroth#1085) The `tailwind-class-sorter` was silently corrupting class names in attributes when ERB was used for interpolating class names. For example: **Input:** ```html <div class="foo-<%= bar %>-100"> ``` **Output:** ```html <div class="foo--100 <%= bar %>"> ``` This happened because the sorter separated all literal nodes from ERB nodes and joined them together, losing the ERB's position within the class name. This pull request adds detection for string interpolation patterns and skips sorting when they're found: - Case 1: `literal → ERB → literal` with no whitespace at boundaries e.g., `foo-<%= bar %>-100` - Case 2: ERB at start followed by literal without leading whitespace e.g., `<%= prefix %>-blue-500` - Case 3: Literal ending with hyphen followed by ERB at end e.g., `bg-<%= suffix %>` When interpolation is detected, sorting is skipped for that attribute value to prevent silent corruption. Classes inside nested conditionals are still sorted correctly. Resolves marcoroth#879
This pull request extracts a `hb_allocator_T` for providing an interface for allocating memory in the lexer and parser. In this pull request we implement both a `malloc`-based and a `hb_arena_T`-based allocator. Additionally, this updates all call-sites and functions to accept a new allocator that can later be swapped to use the `hb_arena_T`-based allocator in marcoroth#726.
Bumps the npm_and_yarn group with 1 update in the / directory: [underscore](https://github.com/jashkenas/underscore). Updates `underscore` from 1.13.7 to 1.13.8 Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This pull request introduces arena allocation for the lexer and parser, replacing "per-object" `malloc`/`free` calls with bulk allocation from a memory arena. All allocated AST nodes, tokens, and internal strings are placed into a single arena that is freed in one shot after the parse tree has been converted to the binding's native objects. The arena is accessed through `hb_allocator_T`, a vtable-based allocator abstraction introduced in marcoroth#1287. This pull request switches the default backend from `malloc` to `hb_arena_T` across all bindings and the CLI. The `malloc` backend remains available as a fallback. The intent is to validate the arena approach in production and then, once confident, simplify the abstraction away and use `hb_arena_T` directly. The `hb_allocator_T` interface was extended with a `destroy` function pointer and a high-level `hb_allocator_init(allocator, type)` constructor that takes an `hb_allocator_type_T` enum (`HB_ALLOCATOR_ARENA`, `HB_ALLOCATOR_MALLOC`). A separate `hb_allocator_init_with_size` variant accepts a custom initial arena size for edge cases. The default arena size is controlled by a compile-time `HB_ALLOCATOR_DEFAULT_ARENA_SIZE` flag. The extract API (`herb_extract`, `herb_extract_ruby_*`, `herb_extract_html_*`) was also updated to accept an `hb_allocator_T*`. Depends on marcoroth#1287
…#1293) ``` ❯ bundle exec exe/herb parse examples/test.html.erb --silent --arena-stats ╔════════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ARENA MEMORY LAYOUT ║ ╠════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ Statistics: ║ ║ • Pages: 1 ║ ║ • Default Page Size: 16 KB ║ ║ • Total Capacity: 16 KB ║ ║ • Total Used: 7 KB ║ ║ • Total Available: 8 KB ║ ║ • Usage: 44.6% ║ ║ • Allocations: 274 ║ ║ • Fragmentation: 0 B ║ ╠════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ Page 0 @ 0x104b2c000 ← CURRENT ║ ║ [████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 7 KB / 16 KB (45%) ║ ║ Unused: 8 KB (available for allocations) ║ ║ ║ ╚════════════════════════════════════════════════════════════════════════════════════════════════════╝ Success ``` ``` irb(main):001> Herb.parse("<div></div>", arena_stats: true) ╔════════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ARENA MEMORY LAYOUT ║ ╠════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ Statistics: ║ ║ • Pages: 1 ║ ║ • Default Page Size: 16 KB ║ ║ • Total Capacity: 16 KB ║ ║ • Total Used: 1 KB ║ ║ • Total Available: 14 KB ║ ║ • Usage: 8.0% ║ ║ • Allocations: 48 ║ ║ • Fragmentation: 0 B ║ ╠════════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ Page 0 @ 0x102f38000 ← CURRENT ║ ║ [█████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 1 KB / 16 KB (8%) ║ ║ Unused: 14 KB (available for allocations) ║ ║ ║ ╚════════════════════════════════════════════════════════════════════════════════════════════════════╝ => #<Herb::ParseResult:0x0000000121ad6ef8 ...> ```
…#1294) This pull request updates the `analyze` subcommand in the Ruby CLI to also accept the `--arena-stats` flag. **`herb analyze --arena-stats`** <img width="2718" height="1792" alt="CleanShot 2026-03-04 at 11 14 44@2x" src="https://github.com/user-attachments/assets/4be6bab4-2b10-4b93-b1ef-f3a21c2b17ab" /> ... <img width="2676" height="1842" alt="CleanShot 2026-03-04 at 11 14 04@2x" src="https://github.com/user-attachments/assets/d8c79175-f193-4125-829e-597b0f19375f" />
Context: https: //github.com/marcoroth/pull/726#pullrequestreview-3811782126 Co-Authored-By: Michael Kohl <me@citizen428.net>
Sadly the require change in marcoroth#1281 is now causing the `require "herb"` locally to favor the locally installed herb gem over the local herb version
This pull request implements the `textDocument/documentHighlight` request in the Language Server. **Before** https://github.com/user-attachments/assets/bde4508a-da1c-4c00-b6d8-1948fee07310 **After** https://github.com/user-attachments/assets/597b81bf-8106-4153-aa6c-138a1cfd8f59 Related marcoroth#1282
Based on the arena pages proposed in marcoroth#708 by @marcoroth, this PR builds on the concept and adds a more refined reset behavior that merges obsolete pages into one big page. This has the advantage that we won't have a bunch of small pages, and instead have more contiguous memory available. ## How it works ``` # Initial state | Page 1 (4kb) | - next > | Page 2 (4kb) | - next > | Page 1 (16kb) | # reset to 4kb | Page 1 (4kb) | - next > | Page 2 (24kb) | # reset to 0kb | Page 1 (4kb) | - next > | Page 2 (24kb) | ``` For now the first page is never deallocated and merged into a bigger page for two reasons: 1. It's an edge case that needs to be handled. 2. If the allocation of a new arena page fails at least you still have the first page. 🤷♂️ Related: marcoroth#1155 --------- Co-authored-by: Marco Roth <marco.roth@intergga.ch>
marcoroth#1295) Previously, the formatter would inline ERB children in block elements when the content fit within `maxLineLength`, even when the user intentionally placed them on separate lines. This pull request extends the existing mixed-content newline preservation (marcoroth#1279) to also cover block elements with ERB children. Resolves marcoroth#1181
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Node: Standardize dual-format packaging to match established codebase patterns
This Pull Request fixes the packaging for CJS consumers of the Node package.
Previously, the CommonJS entry failed due to a Rollup bundling configuration issue in
@herb-tools/node. The package used separate.cts/.mtssource files, but Rollup wasn't properly bundling local dependencies for the CJS build, leaving runtime requires that failed.This change adopts the established pattern: a single
src/index.tssource file that Rollup transforms into both ESM and CJS outputs. This matches the proven strategy used by@herb-tools/core,@herb-tools/formatter,@herb-tools/highlighter, and@herb-tools/linter.This change adopts the established pattern: a single
src/index.tssource file that Rollup transforms into both ESM and CJS outputs. This matches the strategy used by@herb-tools/core,@herb-tools/formatter,@herb-tools/highlighter, and@herb-tools/linter.No public API changes.
Expected vs Actual
@herb-tools/node, callawait Herb.load(), and use the API.Reproduction (before)
Using the built artifacts, with the previous split
.mts/.ctssources:Run:
node test-esm.mjs # ✓ Loads and runs successfullyRun:
node test-cjs.cjs # ✗ Error: Cannot find module './node-backend' (from dist/herb-node.cjs)Even with the path changed, there'd still be the error:
Diagnosis (before)
CJS build problem:
src/index-cjs.ctscontainedrequire("./node-backend.js")dist/herb-node.cjsexpected./node-backend.jsto exist at runtime, but it wasn't emittednew Promise(...)(Promise instance) instead of() => new Promise(...)(Promise factory)ESM build worked because:
node-backend.tsdependency() => new Promise(...)Fix
Adopt the standard, project-wide packaging pattern:
src/index.tsfor both formats..cts/.mtssources to avoid divergence and bundling issues.package.jsontype entries to the standard./dist/types/index.d.ts.This mirrors the approach in other packages and eliminates the non-working CJS path.
Files Changed
src/index-esm.mts→src/index.ts(renamed, now single source)src/index-cjs.cts(deleted, was non-working code path)rollup.config.mjs(updated to use standard pattern)package.json(updated type declarations)