@poliklot/prettier-plugin-handlebars
v0.2.12
Published
Prettier plugin for classic Handlebars (.hbs)
Maintainers
Readme
@poliklot/prettier-plugin-handlebars
Prettier plugin for classic Handlebars templates with mixed HTML markup.
It formats .hbs / .handlebars files used in HTML-heavy Handlebars projects,
with a focus on stable, idempotent output and preserving classic Handlebars
semantics.
Install
npm install --save-dev prettier @poliklot/prettier-plugin-handlebarsQuick Start
Recommended config:
/** @type {import("prettier").Config} */
module.exports = {
plugins: ["@poliklot/prettier-plugin-handlebars"],
overrides: [
{
files: ["*.hbs", "*.handlebars"],
options: {
parser: "handlebars",
},
},
],
};The explicit override keeps .hbs files on this plugin even in Prettier versions that also know about other Handlebars-like parsers.
Configuration Patterns
1. Minimal plugin setup
Use this only when you have verified that your Prettier version and editor resolve .hbs files to this plugin. The explicit override below is safer for shared projects.
/** @type {import("prettier").Config} */
module.exports = {
plugins: ["@poliklot/prettier-plugin-handlebars"],
};2. Explicit Handlebars override
Use this when you want stable format-on-save behavior in editors, or when your project mixes multiple template types.
/** @type {import("prettier").Config} */
module.exports = {
plugins: ["@poliklot/prettier-plugin-handlebars"],
overrides: [
{
files: ["*.hbs", "*.handlebars"],
options: {
parser: "handlebars",
},
},
],
};3. Explicit Handlebars override with project style
Normal Prettier options still work and often matter a lot for .hbs.
/** @type {import("prettier").Config} */
module.exports = {
plugins: ["@poliklot/prettier-plugin-handlebars"],
overrides: [
{
files: ["*.hbs", "*.handlebars"],
options: {
parser: "handlebars",
printWidth: 120,
useTabs: true,
tabWidth: 4,
singleQuote: true,
htmlWhitespaceSensitivity: "ignore",
},
},
],
};4. Local plugin path during dogfooding
Useful when you are testing the plugin from a neighboring repository before publishing a new npm version.
/** @type {import("prettier").Config} */
module.exports = {
plugins: ["../prettier-plugin-handlebars/dist/plugin.js"],
overrides: [
{
files: ["*.hbs", "*.handlebars"],
options: {
parser: "handlebars",
},
},
],
};5. JSON config
{
"plugins": ["@poliklot/prettier-plugin-handlebars"],
"overrides": [
{
"files": ["*.hbs", "*.handlebars"],
"options": {
"parser": "handlebars",
"printWidth": 120,
"useTabs": true,
"tabWidth": 4
}
}
]
}CLI
Published package:
npx prettier --write "src/**/*.{hbs,handlebars}" --plugin @poliklot/prettier-plugin-handlebars --parser handlebarsLocal plugin build:
npx prettier --write "src/**/*.{hbs,handlebars}" --plugin ../prettier-plugin-handlebars/dist/plugin.js --parser handlebarsProject Setup Audit
The package also ships a small setup helper:
npx @poliklot/prettier-plugin-handlebars initBy default this is a dry run. It reports whether the project has:
- local
prettierand@poliklot/prettier-plugin-handlebarsdependencies; - a Prettier config that loads the plugin;
- an explicit
*.hbs/*.handlebarsoverride withparser: "handlebars"; .prettierignorepatterns that skip Handlebars files.
Apply supported JSON config changes with:
npx @poliklot/prettier-plugin-handlebars init --writeUse it in CI or local checks with:
npx @poliklot/prettier-plugin-handlebars init --checkThe helper can create or update .prettierrc, .prettierrc.json, and the prettier object in package.json. JavaScript, JSON5, YAML, and TOML configs are detected but not rewritten automatically; the command prints the manual action instead.
API
const prettier = require("prettier");
const plugin = require("@poliklot/prettier-plugin-handlebars");
async function run(source) {
return prettier.format(source, {
filepath: "template.hbs",
parser: "handlebars",
plugins: [plugin],
});
}Plugin Options
dataAttributeOrder
Custom ordering override for data-* attributes.
| Default | CLI Override | API Override |
| --- | --- | --- |
| [] | --data-attribute-order <value> | dataAttributeOrder: string[] |
{
"plugins": ["@poliklot/prettier-plugin-handlebars"],
"dataAttributeOrder": ["data-testid", "data-state", "data-track"]
}maxEmptyLines
Maximum number of consecutive blank lines preserved between nodes.
| Default | CLI Override | API Override |
| --- | --- | --- |
| 1 | --max-empty-lines <int> | maxEmptyLines: number |
{
"plugins": ["@poliklot/prettier-plugin-handlebars"],
"maxEmptyLines": 1
}What The Plugin Handles Today
- HTML elements, void elements, comments, and custom elements
{{mustache}},{{{triple-stash}}}, block helpers,{{else}},{{else if ...}}, partials- whitespace control markers such as
{{~ value ~}}and{{~/if~}} - raw blocks such as
{{{{raw}}}}...{{{{/raw}}}} - block partials such as
{{#> layout}}...{{/layout}} - inline partial definitions such as
{{#*inline "name"}}...{{/inline}} - Handlebars inside attribute values
- unquoted mustache attribute values such as
src={{ imgSrc }} - Handlebars blocks that emit attributes
- multiline class formatting with conditional modifiers
- comparison helper operators such as
{{#ifCompare a '===' b}} - hash params written as
key=value,key= value, orkey = value - long helper and partial calls with nested subexpressions
prettier-ignore,prettier-ignore-start,prettier-ignore-end- root-level plain-text templates with inline mustaches
- embedded JavaScript / CSS formatting for plain
script/styletags - raw
script/stylepreservation when content contains Handlebars or non-JS/CSS types - literal
pre/textareatext preservation - unmatched / incomplete structures preserved as raw nodes instead of crashing
- recovery for some broken formatter output, such as split dynamic attribute names
Real-World Examples
else if chains
{{#if primary}}
Primary
{{else if secondary}}
Secondary
{{else}}
Fallback
{{/if}}Conditional class values
<div
class="
card
{{#if isPrimary}}
card--primary
{{else if isSecondary}}
card--secondary
{{/if}}
"
></div>Partial params with relaxed spacing
{{> 'ui/input-primary/input-primary'
id='compare-family-name'
type='text'
placeholder='Family name'
}}The parser accepts common source styles such as id= 'value' and type = 'text', then prints them consistently as hash params.
Classic comparison helpers
{{#ifCompare ../activeIndex '===' @index}}
active
{{/ifCompare}}Operators like '===', '!==', '>', and '<' are kept as positional params instead of being mistaken for hash pairs.
Current Limits
This is a 0.x formatter focused on classic Handlebars.
Known limits:
- embedded JavaScript / CSS formatting is conservative and only runs for plain safe
script/stylecontent - Glimmer / Ember-only syntax is treated as stress input, not as a compatibility target
- exact byte-level fixtures, such as BOM / no-final-newline tests, may still need project-level
prettier-ignore
Development
npm install
npm run build
npm testUseful scripts:
npm run build- compile the plugin intodist/npm test- run the full automated suitenpm run check- build + test + deterministic fuzz checknpm run corpus:check -- <path> [more-paths...]- run an idempotence / crash-safety sweep over real template corporanpm run corpus:oss- clone and check a public OSS corpus from Ghost themes, Ghost classic templates, WET,express-hbs,pillarjs/hbs, and other real.hbsprojectsnpm run fuzz:parser- run deterministic malformed-template fuzzing against the built pluginnpm run format:hbs-tree -- <path>- format every.hbs/.handlebarsfile under a temp project copy before running that project's own buildPRETTIER_VERSION=3.2 npm run smoke:install- pack the plugin, install it into a clean temp project, format a sample, and verify thathandlebarsis not installednpm run pack:check- inspect npm package contents withnpm pack --dry-run
VS Code Companion
If you work with Handlebars in VS Code, try HBS Master as a companion extension. It pairs well with this formatter and makes day-to-day .hbs editing more comfortable.
For formatter setup details, see Editor Setup.
Troubleshooting and Migration
Notes
- This README is intentionally self-contained so it works well on npm too.
- If your editor does not format
.hbson save, the safest setup is an explicitoverridesrule withparser: "handlebars".
