+
+
+
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 1625858bd..dd4546066 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -37,13 +37,19 @@ export default defineConfig({
const pageTitle = pageData.frontmatter.title || pageData.title
- pageData.frontmatter.head.push([
- 'meta',
- {
- property: 'og:title',
- content: pageTitle ? `${pageTitle} | ${title}` : `${title} - ${description}`
- }
- ])
+ const hasOgTitle = pageData.frontmatter.head.some(
+ (item: any) => item[0] === 'meta' && item[1].property === 'og:title'
+ )
+
+ if (!hasOgTitle) {
+ pageData.frontmatter.head.push([
+ 'meta',
+ {
+ property: 'og:title',
+ content: pageTitle ? `${pageTitle} | ${title}` : `${title} - ${description}`
+ }
+ ])
+ }
},
async buildEnd() {
diff --git a/docs/.vitepress/config/markdown.mts b/docs/.vitepress/config/markdown.mts
index c9b58de17..0b3e0c089 100644
--- a/docs/.vitepress/config/markdown.mts
+++ b/docs/.vitepress/config/markdown.mts
@@ -10,6 +10,9 @@ export function createMarkdownConfig() {
herbLinterTransformer,
],
// Explicitly load these languages for types highlighting
- languages: ["js", "jsx", "ts", "tsx", "bash", "shell", "ruby", "html", "erb"],
+ languages: ["js", "ts", "bash", "shell", "ruby", "html", "erb", "java", "rust"],
+ image: {
+ lazyLoading: true
+ },
}
}
diff --git a/docs/.vitepress/config/theme.mts b/docs/.vitepress/config/theme.mts
index 329cc6bc7..d4fa0bf53 100644
--- a/docs/.vitepress/config/theme.mts
+++ b/docs/.vitepress/config/theme.mts
@@ -132,6 +132,7 @@ export function createThemeConfig() {
logo: "/herb.svg",
nav: [
{ text: "Home", link: "/" },
+ { text: "Blog", link: "/blog/whats-new-in-herb-v0-8" },
{ text: "Documentation", link: "/overview" },
{ text: "Playground", link: "/playground" },
],
@@ -180,6 +181,7 @@ export function createThemeConfig() {
}
],
'/projects/linter': linterSidebar,
+ '/blog': [],
'/': defaultSidebar
},
socialLinks: [
diff --git a/docs/.vitepress/config/vite.mts b/docs/.vitepress/config/vite.mts
index 8503f3723..b611760de 100644
--- a/docs/.vitepress/config/vite.mts
+++ b/docs/.vitepress/config/vite.mts
@@ -32,6 +32,8 @@ export function createViteConfig() {
browser: "vscode-icons:file-type-js",
"Node.js": "vscode-icons:file-type-js",
".js": "vscode-icons:file-type-js",
+ ".java": "vscode-icons:file-type-java",
+ ".rs": "vscode-icons:file-type-rust",
javascript: "vscode-icons:file-type-js",
shell: "vscode-icons:file-type-shell",
".erb": localIconLoader(import.meta.url, "../assets/herb.svg"),
diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css
index 9ab3acdff..0121cb4b0 100644
--- a/docs/.vitepress/theme/custom.css
+++ b/docs/.vitepress/theme/custom.css
@@ -262,3 +262,20 @@ pre .twoslash-error-level-error {
filter: brightness(0) invert(1);
}
+/* Medium Zoom Styles */
+.medium-zoom-overlay {
+ z-index: 100;
+}
+
+.medium-zoom-image--opened {
+ z-index: 101;
+ cursor: zoom-out;
+ max-width: 95vw !important;
+ max-height: 95vh !important;
+ object-fit: contain !important;
+}
+
+.vp-doc img {
+ cursor: zoom-in;
+ height: auto;
+}
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index 345bd32ee..b2bfbf90e 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -1,5 +1,9 @@
import type { EnhanceAppContext } from "vitepress"
import Theme from "vitepress/theme"
+import { h } from "vue"
+import { onMounted, watch, nextTick } from 'vue'
+import { useRoute } from 'vitepress'
+import mediumZoom from 'medium-zoom'
import TwoslashFloatingVue from "@shikijs/vitepress-twoslash/client"
@@ -16,6 +20,25 @@ export default {
app.component("GitHubContributors", GitHubContributors)
},
setup() {
+ const route = useRoute()
+
+ const initZoom = () => {
+ mediumZoom('.main img', {
+ background: 'var(--vp-c-bg)',
+ margin: 24,
+ scrollOffset: 0
+ })
+ }
+
+ onMounted(() => {
+ initZoom()
+ })
+
+ watch(
+ () => route.path,
+ () => nextTick(() => initZoom())
+ )
+
if (typeof window !== 'undefined') {
const updateThemeState = () => {
const isDark = document.documentElement.classList.contains('dark')
diff --git a/docs/docs/blog.md b/docs/docs/blog.md
new file mode 100644
index 000000000..322e3fbce
--- /dev/null
+++ b/docs/docs/blog.md
@@ -0,0 +1,13 @@
+---
+sidebar: false
+editLink: false
+outline: false
+---
+
+
+
+# Latest From the Herb Blog
+
+
diff --git a/docs/docs/blog/whats-new-in-herb-v0-8.md b/docs/docs/blog/whats-new-in-herb-v0-8.md
new file mode 100644
index 000000000..41be81e03
--- /dev/null
+++ b/docs/docs/blog/whats-new-in-herb-v0-8.md
@@ -0,0 +1,821 @@
+---
+title: What's new in Herb v0.8
+author:
+ name: Marco Roth
+date: 2025-11-12
+layout: doc
+sidebar: false
+outline:
+ level: [2, 2]
+ label: In this post
+head:
+ - - meta
+ - property: og:type
+ content: article
+
+ - - meta
+ - property: og:title
+ content: What's new in Herb v0.8
+
+ - - meta
+ - property: og:image
+ content: /blog/whats-new-in-herb-v0-8/hero.png
+
+ - - meta
+ - property: og:image:width
+ content: "1560"
+
+ - - meta
+ - property: og:image:height
+ content: "864"
+
+ - - meta
+ - property: og:url
+ content: /blog/whats-new-in-herb-v0-8
+
+ - - meta
+ - property: og:description
+ content: The most feature-packed Herb release yet with new Rust and Java bindings, linter autofix, configuration file support, custom rules, Tailwind class sorting, and major improvements across the ecosystem.
+
+ - - meta
+ - property: article:author
+ content: Marco Roth
+
+ - - meta
+ - name: twitter:card
+ content: summary_large_image
+---
+
+# What's new in Herb v0.8
+
+_November 12, 2025_ • Marco Roth
+
+
+
+Today, we are excited to announce **Herb v0.8**, the most feature-packed release so far!
+
+If you're not familiar with Herb yet: it's an ecosystem of tools specifically built for HTML+ERB files. If you haven't used Herb before, we suggest reading the [Overview](/overview) page first.
+
+Its main goal is to improve the developer experience while working with `.html.erb` files, but also to improve HTML rendering from Ruby and drive innovation in the Ruby and Rails view layers.
+
+**Quick links:**
+
+- [Herb v0.8.0 Changelog](https://github.com/marcoroth/herb/releases/tag/v0.8.0)
+- [Documentation](/overview)
+
+We would like to thank all contributors and everyone who reported issues to get this release out of the door. We encourage you to get involved and help us improve Herb for the entire ecosystem. Feel free to check out the [open issues](https://github.com/marcoroth/herb/issues) or get in touch.
+
+For the latest news about Herb, follow [@marcoroth](https://github.com/marcoroth) on any of the socials.
+
+
+## What's New in Herb v0.8
+
+This release brings significant improvements across the entire Herb ecosystem. It includes new language bindings, enhanced tooling, powerful new features for linting and formatting your HTML+ERB templates, plus numerous bug fixes and quality of life improvements.
+
+
+
+## New Language Bindings
+
+Herb was already available to be used from [C](/projects/parser), [Ruby](/bindings/ruby/), and the [JavaScript](/bindings/javascript/) bindings via WebAssembly (Emscripten) and Node.js (N-API).
+
+With Herb v0.8 we expand the language support with official bindings for two new ecosystems, [Rust](/bindings/rust/) and [Java](/bindings/java/).
+
+If you are looking to use these bindings and are missing functionality please feel free to open an issue.
+
+### Rust Bindings
+
+We've added comprehensive [Rust FFI bindings](/bindings/rust/), making Herb accessible to the Rust ecosystem. The Rust bindings provide full access to Herb's parsing capabilities with idiomatic Rust APIs, available on Crates.io as [`herb`](https://crates.io/crates/herb).
+
+```rust [main.rs]
+use herb::{lex, parse};
+
+fn main() {
+ let template = "
<%= @user.name %>
";
+
+ match lex(template) {
+ Ok(result) => { println!("{}", result); }
+ Err(error) => { eprintln!("Lex error: {}", error); }
+ }
+
+ match parse(template) {
+ Ok(result) => { println!("{}", result); }
+ Err(error) => { eprintln!("Parse error: {}", error); }
+ }
+}
+```
+
+You can find the full [installation and getting started guide in the documentation](/bindings/rust/).
+
+### Java Bindings
+
+Herb v0.8 now supports [Java through JNI bindings](/bindings/java/), bringing HTML+ERB parsing to the JVM ecosystem. This should open up Herb's capabilities to Java, Kotlin, Scala, and other JVM languages.
+
+```java [Main.java]
+import org.herb.*;
+
+public class Main {
+ public static void main(String[] args) {
+ String template = "
<%= @user.name %>
";
+
+ LexResult lexResult = Herb.lex(template);
+ System.out.println(lexResult.inspect());
+
+ ParseResult parseResult = Herb.parse(template);
+ System.out.println(parseResult.inspect());
+ }
+}
+```
+
+You can find the full [installation and getting started guide in the documentation](/bindings/java/).
+
+## Configuration File Support
+
+Herb v0.8 introduces a new `.herb.yml` configuration file for customizing Herb tools. You can configure the Herb Linter and Herb Formatter behavior across your project and share the configuration with your team by checking the `.herb.yml` file into git.
+
+```yaml [.herb.yml]
+files:
+ exclude:
+ - "node_modules/**/*"
+ - "vendor/**/*"
+
+linter:
+ enabled: true
+ rules:
+ html-no-self-closing:
+ enabled: true
+ exclude:
+ - "!app/views/*_mailer/**/*"
+
+ erb-comment-syntax:
+ enabled: false
+
+formatter:
+ enabled: true
+ maxLineLength: 120
+
+ rewriter:
+ pre:
+ - tailwind-class-sorter
+```
+
+The config options are all handled by the new [`@herb-tools/config` package](/projects/config).
+
+You can check out the whole [configuration reference in the documentation](/configuration).
+
+## Linter Improvements
+
+### Autofix
+
+The Herb Linter supports a new `--fix` option to automatically fix correctable offenses.
+
+
+
+The `--fix` option is not dependent on the Herb Formatter. It uses the [Herb Printer](/projects/printer) architecture, so the Linter can fix offenses without touching or formatting anything else in the document. This means you can use the Herb Linter without the Herb Formatter, if you prefer.
+
+The CLI will show you which offenses are autocorrectable by annotating them with `[Correctable]`, and displays a summary at the end of the linter run showing how many offenses can be fixed.
+
+
+### Disable Comments
+
+You can now disable specific linter offenses inline using special `<%# herb:disable %>` comments. Given the following document:
+
+```erb
+
+
+
+```
+
+You can now disable these offenses by explicitly specifying the rule name(s) or by using the `all` directive:
+
+```erb
+ <%# herb:disable html-no-self-closing %>
+
+ <%# herb:disable html-no-self-closing, html-tag-name-lowercase %>
+
+ <%# herb:disable all %>
+```
+
+There is also a new set of `herb-disable-*` linter rules to guide proper usage of `<%# herb:disable %>` comments, so you don't have to run the linter just to realize you had a typo in the rule name.
+
+```erb
+ <%# herb:disable %>
+
+ <%# herb:disable all %>
+
+ <%# herb:disable html-no-self-close %>
+
+ <%# herb:disable all, html-no-self-closing %>
+```
+
+You can also run the linter while ignoring all disable comments:
+
+```bash
+herb-lint --ignore-disable-comments
+```
+
+### New Linter Rules
+
+This release brings a total of 14 new linter rules to help you write better HTML+ERB templates.
+
+- [`erb-no-case-node-children`](/linter/rules/erb-no-case-node-children.md)
+ Don't use `children` for `case/when` and `case/in` nodes
+
+- [`erb-no-extra-newline`](/linter/rules/erb-no-extra-newline.md)
+ Disallow extra newlines.
+
+- [`erb-no-extra-whitespace-inside-tags`](/linter/rules/erb-no-extra-whitespace-inside-tags.md)
+ Disallow multiple consecutive spaces inside ERB tags
+
+- [`herb-disable-comment-malformed`](/linter/rules/herb-disable-comment-malformed.md)
+ Detect malformed `herb:disable` comments.
+
+- [`herb-disable-comment-missing-rules`](/linter/rules/herb-disable-comment-missing-rules.md)
+ Require rule names in `herb:disable` comments.
+
+- [`herb-disable-comment-no-duplicate-rules`](/linter/rules/herb-disable-comment-no-duplicate-rules.md)
+ Disallow duplicate rule names in `herb:disable` comments.
+
+- [`herb-disable-comment-no-redundant-all`](/linter/rules/herb-disable-comment-no-redundant-all.md)
+ Disallow redundant use of `all` in `herb:disable` comments.
+
+- [`herb-disable-comment-unnecessary`](/linter/rules/herb-disable-comment-unnecessary.md)
+ Detect unnecessary `herb:disable` comments.
+
+- [`herb-disable-comment-valid-rule-name`](/linter/rules/herb-disable-comment-valid-rule-name.md)
+ Validate rule names in `herb:disable` comments.
+
+- [`html-body-only-elements`](/linter/rules/html-body-only-elements.md)
+ Require content elements inside ``.
+
+- [`html-head-only-elements`](/linter/rules/html-head-only-elements.md)
+ Require head-scoped elements inside ``.
+
+- [`html-input-require-autocomplete`](/linter/rules/html-input-require-autocomplete.md)
+ Require `autocomplete` attributes on `` tags.
+
+- [`html-no-duplicate-meta-names`](/linter/rules/html-no-duplicate-meta-names.md)
+ Duplicate `` name attributes are not allowed.
+
+- [`html-no-space-in-tag`](/linter/rules/html-no-space-in-tag.md)
+ Disallow spaces in HTML tags
+
+
+If you have ideas for good linter rules that would fit in the Herb Linter, please feel free to [open an issue with your proposal](https://github.com/marcoroth/herb/issues/new?template=linter-rule-proposal.md) to discuss it. There are currently ideas for around [60 more linter rules](https://github.com/marcoroth/herb/issues?q=is%3Aopen%20is%3Aissue%20label%3Alinter-rule), some of which are also a good starting point for contributing to Herb directly!
+
+You can check all available linter rules in [the documentation](/linter/rules/).
+
+### Custom Linter Rules
+
+Herb v0.8 allows you to define your own linter rules now. You can create custom linter rules for project-specific requirements by placing JavaScript files in the `.herb/rules/` directory.
+
+```js [.herb/rules/no-inline-styles.mjs]
+import { BaseRuleVisitor } from "@herb-tools/linter"
+
+class NoInlineStylesVisitor extends BaseRuleVisitor {
+ // ...
+}
+
+export default class NoInlineStylesRule {
+ name = "no-inline-styles"
+
+ check(document, context) {
+ // ...
+ }
+}
+```
+
+Learn more about how to write your [custom linter rules in the documentation](/projects/linter).
+
+
+### Multiple Files in CLI
+
+The `herb-lint` CLI now accepts multiple files/directories or globs as positional arguments.
+
+```bash
+herb-lint path/to/file1.html.erb path/to/file2.html.erb
+```
+
+or multiple directory/globs:
+
+```bash
+herb-lint app/views vendor/views/**/*.html
+```
+
+## Herb Rewriters
+
+Herb v0.8 ships with a new `@herb-tools/rewriter` package that introduces the Herb Rewriter system. Rewriters provide a way to strategically traverse, modify, and rewrite the Herb Syntax Tree. This powerful system will be essential for writing Linter Rule Autocorrectors, Refactoring tools, and Code Actions.
+
+The `@herb-tools/rewriter` package introduces a powerful rewriting API:
+
+```typescript
+import { rewrite, ASTRewriter } from "@herb-tools/rewriter"
+
+class MyRewriter extends ASTRewriter {
+ // ....
+}
+
+const template = ``
+const result = rewrite(template, [new MyRewriter()])
+```
+
+The `@herb-tools/rewriter` package also ships with a few built-in rewriters. The first one is the Tailwind Class Sorter Rewriter, more on that in the formatter section below.
+
+### Custom Rewriters
+
+You can create custom rewriters for project-specific requirements by placing JavaScript files in the `.herb/rewriters/` directory.
+
+```js [.herb/rewriters/my-rewriter.mjs]
+import { ASTRewriter } from "@herb-tools/rewriter"
+
+export default class MyRewriter extends ASTRewriter {
+ async initialize(context) {
+ // ...
+ }
+
+ rewrite(node, context) {
+ // ...
+ }
+}
+```
+
+This rewriter is now discovered by Herb using the `my-rewriter` name, based on the file name. You can read more about the [rewriter system in the documentation](/projects/rewriter).
+
+
+## Formatter Improvements
+
+The formatter has received several improvements, including a lot of bug fixes regarding text content and whitespace formatting.
+
+We are getting closer to removing the experimental label. Please give it a shot on your view files and report any weird behavior you might encounter using the [issue template](https://github.com/marcoroth/herb/issues/new?template=formatting-issue.md).
+
+### Miscellaneous
+
+- Improved root-level whitespace formatting
+- Preservation of the `<%# herb:disable %>` comment placements during formatting
+- Better handling of `case` statement children
+- The formatter now respects and preserves frontmatter content
+- The formatter now skips formatting Rails scaffold templates
+- More consistent formatting of whitespace and text content handling
+- The CLI now also accepts multiple files, directories, or globs
+
+### Tailwind CSS class sorter integration
+
+Earlier this year we published the [`@herb-tools/tailwind-class-sorter`](https://github.com/marcoroth/herb/tree/main/javascript/packages/tailwind-class-sorter#readme) package, which allows you to use the [official recommended Tailwind class order](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier), without being dependent on Prettier.
+
+
+
+
+
+The problem was that the algorithm was locked into the Prettier plugin itself, preventing independent use. The `@herb-tools/tailwind-class-sorter` extracts that algorithm into a standalone package that can be used from Node.js independently of Prettier or the Prettier Plugin:
+
+```js
+import { sortTailwindClasses } from "@herb-tools/tailwind-class-sorter"
+
+const result = await sortTailwindClasses("px-4 bg-blue-500 text-white rounded py-2")
+// "rounded bg-blue-500 px-4 py-2 text-white"
+```
+
+
+The new `@herb-tools/rewriter` package now ships with a built-in rewriter for `@herb-tools/tailwind-class-sorter`.
+
+**TL;DR**: This built-in rewriter is fully integrated into the Herb Formatter, ensuring your Tailwind classes are always consistently ordered when you format documents.
+
+
+
+
+## Language Server Improvements
+
+### Fix-on-Save
+
+The Herb Language Server now supports automatic fix-on-save for lint offenses. Enable this feature in your editor settings to automatically apply fixes when you save files.
+
+
+
+For Visual Studio Code, you can set your preference in the settings pane, or add the following to your settings:
+
+```json
+{
+ "languageServerHerb.linter.fixOnSave": true
+}
+```
+
+Also make sure your editor is set to automatically format on save. In Visual Studio Code the setting is:
+
+```json
+{
+ "editor.formatOnSave": true
+}
+```
+
+You can also choose to simultaneously use Fix-on-save and Format-on-save.
+
+### Code Actions
+
+The language server now provides code actions to disable linter rules at the current location, making it easy to suppress specific warnings when needed.
+
+
+
+We also offer code actions to directly update the `.herb.yml`, to make it easier and less cumbersome to work with the linter and to configure it:
+
+
+
+
+## Herb CLI Improvements
+
+### Colored CLI Output
+
+The Ruby CLI now features colored output for the `parse` and `lex` commands, making it easier to inspect parser output and debug templates.
+
+```bash
+herb parse template.html.erb
+```
+
+
+
+```bash
+herb lex template.html.erb
+```
+
+
+
+### Parse Error Summary
+
+When you parse an HTML+ERB file with errors, it now shows these errors as part of the pretty print output. However, for real-life templates the parse output can get quite long and verbose. To address this, we now show an error summary at the end of the `parse` output.
+
+
+
+
+### Improved `analyze` command
+
+The `herb analyze` command got some upgrades as well. It now parses and compiles all HTML+ERB templates it finds using the `Herb::Engine`, warning you early about potential issues and syntax errors in your templates.
+
+
+
+## Visual Studio Code Improvements
+
+Alongside all the improvements from the language server, there are also some more Visual Studio Code-specific improvements in the [Visual Studio Code Extension](/integrations/editors/vscode).
+
+### `.herb.yml` Integration
+
+The [Visual Studio Code Extension](/integrations/editors/vscode) fully supports and respects the new `.herb.yml` configuration and helps you to visualize the current settings.
+
+### Status Bar
+
+The Herb Status Bar shows you which settings are currently being applied in the current window/project you are working on.
+
+
+
+
+It will show `".herb.yml (Project Settings)"` when it's using the setting from the shared `.herb.yml` file, or it will show `"Herb (Personal Settings)"` when it's using the settings you have defined yourself in your editor.
+
+The `.herb.yml` settings always have higher priority over your personal editor settings to make it easier to follow the project settings.
+
+
+### Configuration
+
+Clicking on the status bar will open the Herb Configuration Menu. You can also see the configuration source and the current status of the configured tools.
+
+
+
+
+### Sidebar
+
+Additionally, you can also see the status of the configured tools in the sidebar.
+
+
+
+
+### Create and Manage Configuration
+
+The Visual Studio Code Extension also helps you create and manage/update the `.herb.yml` configuration file:
+
+
+
+
+### Rails Dev Container
+
+The [Herb LSP Extension](https://marketplace.visualstudio.com/items?itemName=marcoroth.herb-lsp) is now installed by default as part of the [Rails Devcontainer Setup](https://github.com/rails/devcontainer). If you have a Devcontainer configured in your Rails application, you'll get these Language Tools for HTML+ERB files automatically out of the box.
+
+
+## Dev Tools Improvements
+
+### Editor Selector
+
+The Herb Dev Tools (used in ReActionView) now feature a dropdown to select your preferred editor.
+
+
+
+Tools like ReActionView can set a new `` meta tag that will be picked up by the client-side dev tools. This value is being used for the `"Auto (from server)"` option.
+
+In Rails, it will use the new `ActiveSupport::Editor` in Rails 8.1 or fallback to the value of the `RAILS_EDITOR` or `EDITOR` environment variables from your shell environment.
+
+
+## Engine Improvements
+
+The engine gets a few updates as well, including an important fix that caused HTML attribute values to be double escaped.
+
+### Proper ERB Trimming Support
+
+The Engine now properly supports ERB trimming, meaning the `<%-` and `-%>` ERB tags are now respected when compiling and evaluating a template using `Herb::Engine`.
+
+### Customizable Escape Functions
+
+The Engine now supports customizing HTML, JavaScript, and CSS escape functions. Previously, only the `escapefunc` was configurable, and it defaulted to `Herb::Engine.h`. Since Herb has a better understanding of HTML+ERB documents, we can now offer more accurate escaping functions depending on the context.
+
+In addition to the `escapefunc` option, you can now pass these new options to `Herb::Engine`:
+
+* `attrfunc`: Escape HTML Attribute Values (defaults to `Herb::Engine.attr`)
+* `jsfunc`: Escape `