Skip to content

Repository files navigation

☕ CoffeeHaml

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")
);

Why

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.

The Indentation Renaissance

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.


Install

npm install coffeehaml

GitHub: dantiel/CoffeeHaml

CoffeeScript is an optional peer dependency — install it to compile CoffeeScript expressions in attributes and control flow:

npm install coffeescript

Without CoffeeScript, expressions pass through as-is (valid for most JS expressions like {onClick: handler} or {disabled: !connected}).


Usage

TextMate Bundle

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.

CLI

# 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 plugin

// vite.config.ts
import coffeehaml from 'coffeehaml/vite';

export default {
  plugins: [coffeehaml()],
};

Now import .chaml files directly — they compile to React components with HMR.

Node API

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");

Syntax

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


Design Principles

  • 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

Status

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)

License

MIT


Haml structure. CoffeeScript semantics. React runtime. Nothing more, nothing less.

About

Haml's indentation-first syntax fused with CoffeeScript semantics, compiling directly to React's JSX runtime. Write components in half the tokens of JSX. Zero runtime. Vite-native. Made for humans and LLMs.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages