Haml structure. CoffeeScript semantics. React runtime.
CoffeeHaml is a compiler that transforms an indentation-based, Haml-inspired
authoring language into JavaScript using the modern React JSX runtime
(react/jsx-runtime). It is not a UI framework. React remains the runtime.
CoffeeHaml replaces JSX — with 50% fewer tokens.
%SimulatorPanel{source: "gyro", width: "auto", height: 500}
- for servo in servos
%ServoGraph{channel: servo.channel}
%ServoPanel{servo: servo}
%Button{onClick: save, disabled: !connected}
Save↓ compiles to ↓
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
jsxs(SimulatorPanel, { source: "gyro", width: "auto", height: 500 },
...servos.map(servo => jsxs(Fragment, {},
jsx(ServoGraph, { channel: servo.channel }),
jsx(ServoPanel, { servo: servo })
)),
jsx(Button, { onClick: save, disabled: !connected }, "Save")
);| JSX | CoffeeHaml |
|---|---|
<div className="box"><h1>Hello</h1><p>World</p></div> |
%div.box%h1 Hello%p World |
| Closing tags echo the opener verbatim | Indentation is structure |
| 17 structural tokens | 8 structural tokens |
| Angle brackets, braces, delimiters everywhere | Only semantic characters |
Every character carries weight. No syntactic ceremony — only meaning.
Modern LLMs are increasingly trained on indentation-based languages — Python, YAML, Haml, Sass. Their tokenizers have internalized significant whitespace as structural signal. CoffeeHaml enters this cycle at precisely the right moment: the community is ready for a leap in expressiveness that removes boilerplate rather than adding another layer of abstraction.
npm install coffeehamlGitHub: dantiel/CoffeeHaml
CoffeeScript is an optional peer dependency — install it to compile CoffeeScript expressions in attributes and control flow:
npm install coffeescriptWithout CoffeeScript, expressions pass through as-is (valid for most JS
expressions like {onClick: handler} or {disabled: !connected}).
A complete TextMate bundle ships with the repo at CoffeeHaml.tmbundle/.
Install via symlink:
ln -sfn "$(pwd)/CoffeeHaml.tmbundle" \
~/"Library/Application Support/TextMate/Bundles/CoffeeHaml.tmbundle"Includes syntax highlighting, Prettier formatting (⌃⌥F), compilation commands,
snippets, and symbol navigation. See bundle README.
# Scaffold a new project (interactive wizard)
npx coffeehaml init
# One-shot compile
npx coffeehaml compile app.chaml -o app.js --wrap component
# Watch mode — recompiles on every change
npx coffeehaml watch src/ --wrap observer| Flag | Description |
|---|---|
init |
Interactive project scaffold (Vite, dependency, samples) |
-o, --output |
Write output to file |
--source-map |
Emit inline source map |
--wrap <mode> |
Wrap mode: component, observer, or comma-separated HOC list |
Accepts .coffeehaml, .cohaml, and .chaml extensions.
// vite.config.ts
import coffeehaml from 'coffeehaml/vite';
export default {
plugins: [coffeehaml()],
};Now import .chaml files directly — they compile to React components with HMR.
import { compile, compileFile } from 'coffeehaml';
const result = compile('%div Hello', { filename: 'app.chaml' });
console.log(result.code);
// → import { jsx } from "react/jsx-runtime";
// jsx("div", null, "Hello");| CoffeeHaml | Description |
|---|---|
%tag |
HTML element (%div, %span, %button) |
%Component |
React component (uppercase) |
.class |
CSS class |
#id |
Element ID |
{attr: val} / (attr: val) |
Attributes (CoffeeScript expressions) |
{onClick} |
Shorthand — onClick={onClick} |
= expression |
Inline output (escaped); continuation via indented lines |
- if, - unless |
Conditional |
- code |
Arbitrary CoffeeScript statements; continuation via indented lines |
- for x in xs |
Loop → .map() |
- else, - else if |
Chain conditionals |
-# comment |
Haml comment (stripped) |
/ comment |
HTML comment |
.wrapper |
Implicit div |
Full grammar: docs/grammar.md
- Zero runtime — the compiler is the only artifact
- Emitter-agnostic AST — future backends possible (Solid, Vue, Mithril)
- Source-located errors — line/column hints for every compile failure
- Source maps — debug in CoffeeHaml, not generated JS
- Incremental compilation — Vite HMR out of the box
- No invention — reuse Haml and CoffeeScript conventions
v0.7.0 — production beta. The compiler pipeline (Lexer → Parser → Emitter) is complete. The Prettier plugin provides 16 deactivatable formatting options including attribute style preservation, statement merging, blank line handling, and CoffeeScript code formatting.
| Feature | Status |
|---|---|
| Elements, components, implicit divs | ✅ |
.class / #id modifiers |
✅ |
{attr: val} / (attr: val) + spread {props...} |
✅ |
| attribute style preservation (braces/parens/bare) | ✅ |
Inline = expression output |
✅ |
= expression continuation (indented) |
✅ |
- statement continuation (indented) |
✅ |
- if / - unless / - else / - else if |
✅ |
- for item in items → .map() |
✅ |
- while |
✅ |
Statement merging (- blocks) |
✅ |
| Haml/HTML comments | ✅ |
:filter blocks |
✅ |
Prologue passthrough (import, @decorator) |
✅ |
Component / HOC wrapping (wrap: 'observer') |
✅ |
| Vite plugin with HMR | ✅ |
CLI --wrap flag |
✅ |
CLI watch mode |
✅ |
CLI init scaffold |
✅ |
= expr -> arrow continuation |
✅ |
| Source-located error messages | ✅ |
| Multi-error parser recovery | ✅ |
| Source maps | ✅ |
| CoffeeScript expression compilation | ✅ (requires peer dep) |
| React Fast Refresh annotations | ✅ |
| Prettier formatter plugin (16 options) | ✅ |
| TextMate bundle (commands, snippets, syntax) | ✅ |
MIT
Haml structure. CoffeeScript semantics. React runtime. Nothing more, nothing less.