coffeehaml 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/ast.d.ts +107 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +125 -0
- package/dist/ast.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +53 -0
- package/dist/cli.js.map +1 -0
- package/dist/compiler.d.ts +10 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +83 -0
- package/dist/compiler.js.map +1 -0
- package/dist/emitter.d.ts +4 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +464 -0
- package/dist/emitter.js.map +1 -0
- package/dist/errors.d.ts +3 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -0
- package/dist/expressions.d.ts +10 -0
- package/dist/expressions.d.ts.map +1 -0
- package/dist/expressions.js +70 -0
- package/dist/expressions.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +29 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +315 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +4 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +408 -0
- package/dist/parser.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/vite-plugin.d.ts +7 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +44 -0
- package/dist/vite-plugin.js.map +1 -0
- package/docs/README.md +110 -0
- package/docs/architecture.md +619 -0
- package/docs/ast.md +413 -0
- package/docs/examples.md +719 -0
- package/docs/grammar.md +432 -0
- package/docs/index.html +129 -0
- package/package.json +61 -0
package/docs/grammar.md
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# CoffeeHaml Grammar
|
|
2
|
+
|
|
3
|
+
## Notation
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Rule := Alternative1 | Alternative2
|
|
7
|
+
X? := zero or one X
|
|
8
|
+
X* := zero or more X
|
|
9
|
+
X+ := one or more X
|
|
10
|
+
X (sep Y)* := X optionally followed by repeated (Y X)
|
|
11
|
+
'literal' := literal token
|
|
12
|
+
[class] := character class
|
|
13
|
+
[^class] := negated character class
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Whitespace is **significant**: indentation determines nesting. The lexer
|
|
17
|
+
emits `INDENT`/`DEDENT` tokens analogous to Python/CoffeeScript.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Top-Level Production
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Document := (Node | BlankLine)* EOF
|
|
25
|
+
|
|
26
|
+
BlankLine := Whitespace* Newline
|
|
27
|
+
|
|
28
|
+
Node := Doctype
|
|
29
|
+
| Comment
|
|
30
|
+
| Filter
|
|
31
|
+
| ControlFlow
|
|
32
|
+
| Output
|
|
33
|
+
| Element
|
|
34
|
+
| ImplicitDiv
|
|
35
|
+
| RawText
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Doctype
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Doctype := '!!!' Whitespace? DoctypeValue? Newline
|
|
44
|
+
DoctypeValue := [^\n]+
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Examples: `!!!`, `!!! 5`, `!!! html`
|
|
48
|
+
|
|
49
|
+
Emits nothing in React output (React renders into a DOM container, not a full
|
|
50
|
+
document). Retained for Haml compatibility; may emit an HTML comment in
|
|
51
|
+
non-React backends.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Comments
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Comment := HamlComment | HTMLComment
|
|
59
|
+
|
|
60
|
+
HamlComment := '-#' Whitespace? CommentText? Newline
|
|
61
|
+
(Indent Node* Dedent)?
|
|
62
|
+
// Removed entirely from output
|
|
63
|
+
|
|
64
|
+
HTMLComment := '/' Whitespace? CommentText? Newline
|
|
65
|
+
(Indent Node* Dedent)?
|
|
66
|
+
// Emitted as <!-- ... --> in development
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Haml comments (`-#`) are stripped. HTML comments (`/`) are preserved in the
|
|
70
|
+
AST and emitted as `<!-- ... -->` (wrapped in a raw HTML insertion at
|
|
71
|
+
runtime).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Elements
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Element := '%' TagName TagModifiers? AttributeBlock? InlineContent? SelfClose? Newline
|
|
79
|
+
(Indent Node* Dedent)?
|
|
80
|
+
|
|
81
|
+
SelfClose := '/' Whitespace? // preceding newline
|
|
82
|
+
// %br/ → <br />
|
|
83
|
+
|
|
84
|
+
TagName := Identifier (':' Identifier)*
|
|
85
|
+
// Allows SVG: %svg:circle, %math:mi
|
|
86
|
+
|
|
87
|
+
TagModifiers := (ClassModifier | IdModifier)+
|
|
88
|
+
ClassModifier := '.' ClassName
|
|
89
|
+
IdModifier := '#' IdName
|
|
90
|
+
|
|
91
|
+
ClassName := Identifier
|
|
92
|
+
IdName := Identifier
|
|
93
|
+
|
|
94
|
+
Identifier := [a-zA-Z_$] [a-zA-Z0-9_$]*
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Implicit Div
|
|
98
|
+
|
|
99
|
+
When a line begins with `.class` or `#id` without a `%tag`, a `<div>` is
|
|
100
|
+
implied — exactly as in Haml:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
ImplicitDiv := TagModifiers AttributeBlock? InlineContent? Newline
|
|
104
|
+
(Indent Node* Dedent)?
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
.container
|
|
109
|
+
%h1 Hello
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Is equivalent to:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
%div.container
|
|
116
|
+
%h1 Hello
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Attributes
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
AttributeBlock := AttrBraces | AttrParens
|
|
125
|
+
|
|
126
|
+
AttrBraces := '{' AttrContent? '}'
|
|
127
|
+
AttrParens := '(' AttrContent? ')'
|
|
128
|
+
|
|
129
|
+
AttrContent := Arbitrary CoffeeScript until matching delimiter.
|
|
130
|
+
// Parsed as a CoffeeScript object literal.
|
|
131
|
+
// Comma-optional, supports splats, nested objects,
|
|
132
|
+
// function expressions, destructuring.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The content between `{...}` or `(...)` is extracted verbatim and parsed
|
|
136
|
+
by the CoffeeScript compiler as an object literal. This means:
|
|
137
|
+
|
|
138
|
+
```haml
|
|
139
|
+
%div{class: "container", id: if active then "on" else "off"}
|
|
140
|
+
%Button{onClick: (e) -> handleClick(e), disabled: !ready}
|
|
141
|
+
%Panel{
|
|
142
|
+
style: {color: "red", margin: 10}
|
|
143
|
+
"aria-label": "Close"
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
All CoffeeScript expression forms are valid inside attribute values:
|
|
148
|
+
strings, numbers, bools, functions (`->`), conditionals, existential
|
|
149
|
+
operator (`?.`), destructuring, splats (`...`).
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Inline Content
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
InlineContent := Whitespace InlineText? OutputSuffix?
|
|
157
|
+
|
|
158
|
+
InlineText := ( [^\n%=] [^\n%]* )?
|
|
159
|
+
// Text up to newline, '%', or '='
|
|
160
|
+
|
|
161
|
+
OutputSuffix := '=' Whitespace? Expression
|
|
162
|
+
// Mixed text + output on same line:
|
|
163
|
+
// %p Hello, = user.name
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Inline content follows the tag and attributes on the same line:
|
|
167
|
+
|
|
168
|
+
```haml
|
|
169
|
+
%h1 Welcome, = user.name
|
|
170
|
+
%p This is a paragraph with inline content.
|
|
171
|
+
%span.status{class: status} = statusText
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Output
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
Output := '=' Whitespace? Expression Newline // escaped
|
|
180
|
+
| '==' Whitespace? Expression Newline // unescaped (raw)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`=` inserts the result of a CoffeeScript expression as a child node.
|
|
184
|
+
`==` inserts without HTML escaping (for raw HTML — use cautiously).
|
|
185
|
+
|
|
186
|
+
```haml
|
|
187
|
+
%h1
|
|
188
|
+
= pageTitle
|
|
189
|
+
%div
|
|
190
|
+
= formatMarkdown(content)
|
|
191
|
+
== user.bioHTML
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Control Flow
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
ControlFlow := '-' Whitespace? ControlExpression
|
|
200
|
+
|
|
201
|
+
ControlExpression := IfChain
|
|
202
|
+
| UnlessBlock
|
|
203
|
+
| ForBlock
|
|
204
|
+
| WhileBlock
|
|
205
|
+
| CoffeeStatement
|
|
206
|
+
|
|
207
|
+
IfChain := IfBlock (ElseIfBlock | ElseBlock)*
|
|
208
|
+
|
|
209
|
+
IfBlock := 'if' Whitespace Expression Newline
|
|
210
|
+
(Indent Node* Dedent)?
|
|
211
|
+
UnlessBlock := 'unless' Whitespace Expression Newline
|
|
212
|
+
(Indent Node* Dedent)?
|
|
213
|
+
ElseIfBlock := 'else' Whitespace 'if' Whitespace Expression Newline
|
|
214
|
+
(Indent Node* Dedent)?
|
|
215
|
+
ElseBlock := 'else' Newline
|
|
216
|
+
(Indent Node* Dedent)?
|
|
217
|
+
|
|
218
|
+
ForBlock := 'for' Whitespace ForClause Newline
|
|
219
|
+
(Indent Node* Dedent)?
|
|
220
|
+
ForClause := ForIn | ForOf
|
|
221
|
+
ForIn := Pattern 'in' Expression
|
|
222
|
+
ForOf := Pattern 'of' Expression
|
|
223
|
+
Pattern := Identifier (',' Identifier)*
|
|
224
|
+
| '[' Pattern (',' Pattern)* ']'
|
|
225
|
+
| '{' Pattern (',' Pattern)* '}'
|
|
226
|
+
|
|
227
|
+
WhileBlock := 'while' Whitespace Expression Newline
|
|
228
|
+
(Indent Node* Dedent)?
|
|
229
|
+
|
|
230
|
+
CoffeeStatement := Any valid CoffeeScript statement.
|
|
231
|
+
// Arbitrary CoffeeScript code inserted verbatim.
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Control flow bodies contain CoffeeHaml nodes (elements, output, nested
|
|
235
|
+
control flow) and compile to JavaScript that produces child arrays.
|
|
236
|
+
|
|
237
|
+
```haml
|
|
238
|
+
- if user
|
|
239
|
+
%WelcomeBanner{user: user}
|
|
240
|
+
- else
|
|
241
|
+
%LoginPrompt
|
|
242
|
+
|
|
243
|
+
- for item, index in items
|
|
244
|
+
%ItemCard{item: item, key: index}
|
|
245
|
+
|
|
246
|
+
- unless loading
|
|
247
|
+
%Content{data: data}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Semantics
|
|
251
|
+
|
|
252
|
+
Control flow bodies compile to expressions that evaluate to arrays of
|
|
253
|
+
React elements:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
- for x in xs → ...xs.map((x) => jsx(...))
|
|
257
|
+
- if cond → ...(cond ? [jsx(...)] : [])
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Filters
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
Filter := ':' FilterName Newline
|
|
266
|
+
(Indent FilterContent Dedent)?
|
|
267
|
+
|
|
268
|
+
FilterName := Identifier
|
|
269
|
+
FilterContent := Lines of text at increased indentation.
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Haml-style filters for embedded content. Common filters:
|
|
273
|
+
|
|
274
|
+
| Filter | Purpose |
|
|
275
|
+
|--------|---------|
|
|
276
|
+
| `:css` | Embedded CSS block |
|
|
277
|
+
| `:javascript` | Embedded JS block |
|
|
278
|
+
| `:coffeescript` | Embedded CoffeeScript block |
|
|
279
|
+
| `:markdown` | Markdown content → compiled at build time |
|
|
280
|
+
| `:plain` | Pass-through text (no processing) |
|
|
281
|
+
|
|
282
|
+
```haml
|
|
283
|
+
%head
|
|
284
|
+
:css
|
|
285
|
+
body { margin: 0; }
|
|
286
|
+
:javascript
|
|
287
|
+
console.log("loaded");
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Filters are compiled at build time. `:css` may be extracted or inlined
|
|
291
|
+
depending on configuration.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Raw Text / Passthrough
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
RawText := [^%#\-=/:!. \t\n] [^\n]* Newline
|
|
299
|
+
// Any line not matching a recognized pattern.
|
|
300
|
+
// Treated as literal text (wrapped in a text node).
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Lines that don't start with a special character (`%`, `.`, `#`, `-`, `=`,
|
|
304
|
+
`/`, `:`, `!`) are treated as raw text. This enables "plain text" regions
|
|
305
|
+
within templates.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Expression Reference
|
|
310
|
+
|
|
311
|
+
All `Expression` productions delegate to the CoffeeScript parser. Any
|
|
312
|
+
valid CoffeeScript expression is accepted — the CoffeeHaml parser does not
|
|
313
|
+
re-invent CoffeeScript's expression grammar.
|
|
314
|
+
|
|
315
|
+
Key CoffeeScript expression forms used in CoffeeHaml:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
// Functions
|
|
319
|
+
-> // zero-arg arrow
|
|
320
|
+
(x) -> // single-arg arrow
|
|
321
|
+
(x, y) -> // multi-arg arrow
|
|
322
|
+
=> // bound arrow (fat arrow)
|
|
323
|
+
|
|
324
|
+
// Conditionals (expression form)
|
|
325
|
+
if cond then a else b
|
|
326
|
+
unless cond then a
|
|
327
|
+
|
|
328
|
+
// Existential
|
|
329
|
+
obj?.prop
|
|
330
|
+
obj?[key]
|
|
331
|
+
|
|
332
|
+
// Comprehensions
|
|
333
|
+
(x for x in arr)
|
|
334
|
+
(x for x in arr when x > 0)
|
|
335
|
+
|
|
336
|
+
// Splats
|
|
337
|
+
arr...
|
|
338
|
+
{obj..., newKey: val}
|
|
339
|
+
|
|
340
|
+
// String interpolation
|
|
341
|
+
"Hello, #{name}"
|
|
342
|
+
|
|
343
|
+
// Heregex
|
|
344
|
+
/// pattern /g
|
|
345
|
+
|
|
346
|
+
// Destructuring
|
|
347
|
+
{a, b} = obj
|
|
348
|
+
[first, rest...] = arr
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Tokens (Lexer)
|
|
354
|
+
|
|
355
|
+
The lexer produces these token types:
|
|
356
|
+
|
|
357
|
+
| Token | Pattern | Notes |
|
|
358
|
+
|-------|---------|-------|
|
|
359
|
+
| `TAG` | `%[a-zA-Z_$]` | Element tag |
|
|
360
|
+
| `CLASS` | `\.[a-zA-Z_$]` | Class modifier |
|
|
361
|
+
| `ID` | `#[a-zA-Z_$]` | ID modifier |
|
|
362
|
+
| `ATTR_OPEN_BRACE` | `{` | Attribute block start |
|
|
363
|
+
| `ATTR_OPEN_PAREN` | `(` | Attribute block start (alt) |
|
|
364
|
+
| `ATTR_CLOSE_BRACE` | `}` | Attribute block end |
|
|
365
|
+
| `ATTR_CLOSE_PAREN` | `)` | Attribute block end (alt) |
|
|
366
|
+
| `OUTPUT` | `=` | Inline expression output |
|
|
367
|
+
| `OUTPUT_RAW` | `==` | Unescaped output |
|
|
368
|
+
| `CONTROL` | `-` | Control flow prefix |
|
|
369
|
+
| `COMMENT` | `-#` | Haml comment |
|
|
370
|
+
| `HTML_COMMENT` | `/` | HTML comment |
|
|
371
|
+
| `FILTER` | `:` | Filter prefix |
|
|
372
|
+
| `DOCTYPE` | `!!!` | Doctype declaration |
|
|
373
|
+
| `SELF_CLOSE` | `/` | Self-closing tag |
|
|
374
|
+
| `TEXT` | (fallback) | Literal text |
|
|
375
|
+
| `NEWLINE` | `\n`, `\r\n` | Line terminator |
|
|
376
|
+
| `INDENT` | (virtual) | Increased indentation |
|
|
377
|
+
| `DEDENT` | (virtual) | Decreased indentation |
|
|
378
|
+
| `WHITESPACE` | `[ \t]` | Spaces/tabs (handled by indenter) |
|
|
379
|
+
| `EOF` | (virtual) | End of file |
|
|
380
|
+
|
|
381
|
+
The lexer is **context-aware** after certain tokens:
|
|
382
|
+
- After `{` or `(`, scan for matching closing delimiter, extracting the
|
|
383
|
+
contained source as a raw string for CoffeeScript parsing.
|
|
384
|
+
- After `=`, scan to end of line for expression source.
|
|
385
|
+
- After `-`, scan to end of line for control expression source.
|
|
386
|
+
- After `:`, scan for filter name.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Indentation Rules
|
|
391
|
+
|
|
392
|
+
CoffeeHaml uses **significant indentation** exactly like Haml and
|
|
393
|
+
CoffeeScript:
|
|
394
|
+
|
|
395
|
+
1. The first non-blank line of a document establishes the base indent
|
|
396
|
+
level (typically 0).
|
|
397
|
+
2. A line with **more** indentation than the current line opens a new
|
|
398
|
+
nesting level → emit `INDENT`.
|
|
399
|
+
3. A line with **less** indentation closes one or more nesting levels →
|
|
400
|
+
emit `DEDENT` for each closed level.
|
|
401
|
+
4. Tabs and spaces **cannot** be mixed. The indent character is detected
|
|
402
|
+
from the first indented line.
|
|
403
|
+
5. Blank lines are ignored for indentation purposes.
|
|
404
|
+
6. Multi-line expressions (attribute blocks, expressions) defer the
|
|
405
|
+
`NEWLINE` token until the construct is complete.
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
%div // indent 0
|
|
409
|
+
%p // indent 2 → INDENT
|
|
410
|
+
Hello // indent 4 → INDENT
|
|
411
|
+
%p // indent 2 → DEDENT
|
|
412
|
+
World // indent 4 → INDENT
|
|
413
|
+
// EOF → DEDENT, DEDENT
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Precedence of Line-Level Patterns
|
|
419
|
+
|
|
420
|
+
When the lexer encounters the start of a line (after optional whitespace),
|
|
421
|
+
it checks patterns in this order:
|
|
422
|
+
|
|
423
|
+
1. `!!!` → Doctype
|
|
424
|
+
2. `%` + identifier → Element
|
|
425
|
+
3. `.` + identifier → ImplicitDiv (class)
|
|
426
|
+
4. `#` + identifier → ImplicitDiv (id)
|
|
427
|
+
5. `-#` → HamlComment
|
|
428
|
+
6. `/` → HTMLComment (unless followed by `/` or `*`)
|
|
429
|
+
7. `-` → ControlFlow
|
|
430
|
+
8. `=` / `==` → Output
|
|
431
|
+
9. `:` + identifier → Filter
|
|
432
|
+
10. anything else → RawText
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>CoffeeHaml — Haml structure. CoffeeScript semantics. React runtime.</title>
|
|
7
|
+
<meta name="description" content="CoffeeHaml compiles Haml-inspired indentation syntax with CoffeeScript expressions directly to React's JSX runtime. Zero runtime. Vite-native. 50% fewer tokens than JSX.">
|
|
8
|
+
<meta property="og:title" content="CoffeeHaml">
|
|
9
|
+
<meta property="og:description" content="Haml structure. CoffeeScript semantics. React runtime. Zero overhead.">
|
|
10
|
+
<meta property="og:url" content="https://dantiel.github.io/CoffeeHaml/">
|
|
11
|
+
<meta property="og:type" content="website">
|
|
12
|
+
<meta name="twitter:card" content="summary">
|
|
13
|
+
<style>
|
|
14
|
+
:root {
|
|
15
|
+
--bg: #1a1a1a;
|
|
16
|
+
--fg: #e0d8c8;
|
|
17
|
+
--accent: #c8a45c;
|
|
18
|
+
--code-bg: #0d0d0d;
|
|
19
|
+
--muted: #888;
|
|
20
|
+
--border: #333;
|
|
21
|
+
}
|
|
22
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
23
|
+
body {
|
|
24
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--fg);
|
|
27
|
+
line-height: 1.6;
|
|
28
|
+
max-width: 800px;
|
|
29
|
+
margin: 0 auto;
|
|
30
|
+
padding: 3rem 1.5rem;
|
|
31
|
+
}
|
|
32
|
+
h1 { font-size: 2.5rem; margin-bottom: 0.25rem; color: var(--accent); }
|
|
33
|
+
.tagline { font-size: 1.2rem; color: var(--muted); margin-bottom: 2rem; font-style: italic; }
|
|
34
|
+
h2 { font-size: 1.4rem; margin: 2rem 0 0.75rem; color: var(--accent); }
|
|
35
|
+
pre {
|
|
36
|
+
background: var(--code-bg);
|
|
37
|
+
padding: 1.25rem;
|
|
38
|
+
border-radius: 8px;
|
|
39
|
+
overflow-x: auto;
|
|
40
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
41
|
+
font-size: 0.9rem;
|
|
42
|
+
line-height: 1.5;
|
|
43
|
+
border: 1px solid var(--border);
|
|
44
|
+
}
|
|
45
|
+
code { font-family: 'SF Mono', 'Fira Code', monospace; }
|
|
46
|
+
a { color: var(--accent); }
|
|
47
|
+
.btn {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
padding: 0.6rem 1.4rem;
|
|
50
|
+
background: var(--accent);
|
|
51
|
+
color: var(--bg);
|
|
52
|
+
border-radius: 6px;
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
margin-right: 0.75rem;
|
|
56
|
+
margin-top: 0.5rem;
|
|
57
|
+
}
|
|
58
|
+
.btn-outline {
|
|
59
|
+
background: transparent;
|
|
60
|
+
color: var(--accent);
|
|
61
|
+
border: 1px solid var(--accent);
|
|
62
|
+
}
|
|
63
|
+
.stats {
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: 2rem;
|
|
66
|
+
margin: 1.5rem 0;
|
|
67
|
+
}
|
|
68
|
+
.stat { text-align: center; }
|
|
69
|
+
.stat-num { font-size: 2rem; color: var(--accent); font-weight: bold; }
|
|
70
|
+
.stat-label { font-size: 0.8rem; color: var(--muted); text-transform: uppercase; }
|
|
71
|
+
footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); color: var(--muted); font-size: 0.85rem; }
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<h1>☕ CoffeeHaml</h1>
|
|
76
|
+
<p class="tagline">Haml structure. CoffeeScript semantics. React runtime. <br>Zero overhead. 50% fewer tokens than JSX.</p>
|
|
77
|
+
|
|
78
|
+
<p>
|
|
79
|
+
<a class="btn" href="https://github.com/dantiel/CoffeeHaml">GitHub</a>
|
|
80
|
+
<a class="btn btn-outline" href="https://www.npmjs.com/package/coffeehaml">npm</a>
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
<div class="stats">
|
|
84
|
+
<div class="stat"><div class="stat-num">0</div><div class="stat-label">Runtime KB</div></div>
|
|
85
|
+
<div class="stat"><div class="stat-num">~50%</div><div class="stat-label">Token Reduction</div></div>
|
|
86
|
+
<div class="stat"><div class="stat-num">3</div><div class="stat-label">Extensions</div></div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<h2>Write This</h2>
|
|
90
|
+
<pre>.card
|
|
91
|
+
%h2= post.title
|
|
92
|
+
- if showBody
|
|
93
|
+
.body= post.content</pre>
|
|
94
|
+
|
|
95
|
+
<h2>Get This</h2>
|
|
96
|
+
<pre>import { jsx, jsxs } from "react/jsx-runtime";
|
|
97
|
+
|
|
98
|
+
jsxs("div", { className: "card" },
|
|
99
|
+
jsx("h2", null, post.title),
|
|
100
|
+
...(showBody ? [
|
|
101
|
+
jsx("div", { className: "body" }, post.content)
|
|
102
|
+
] : [])
|
|
103
|
+
);</pre>
|
|
104
|
+
|
|
105
|
+
<h2>Quick Start</h2>
|
|
106
|
+
<pre>npm install coffeehaml</pre>
|
|
107
|
+
|
|
108
|
+
<h3>Vite</h3>
|
|
109
|
+
<pre>// vite.config.ts
|
|
110
|
+
import coffeehaml from 'coffeehaml/vite';
|
|
111
|
+
|
|
112
|
+
export default {
|
|
113
|
+
plugins: [coffeehaml()],
|
|
114
|
+
};</pre>
|
|
115
|
+
|
|
116
|
+
<h3>CLI</h3>
|
|
117
|
+
<pre>npx coffeehaml compile app.coffeehaml</pre>
|
|
118
|
+
|
|
119
|
+
<p style="margin-top:1.5rem;">
|
|
120
|
+
Supports <code>.chaml</code>, <code>.coffeehaml</code>, and <code>.cohaml</code> extensions.
|
|
121
|
+
Full documentation on <a href="https://github.com/dantiel/CoffeeHaml">GitHub</a>.
|
|
122
|
+
</p>
|
|
123
|
+
|
|
124
|
+
<footer>
|
|
125
|
+
MIT License · Created by <a href="https://github.com/dantiel">dantiel</a><br>
|
|
126
|
+
CoffeeHaml replaces JSX — not React. No runtime. No framework. Just a compiler.
|
|
127
|
+
</footer>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coffeehaml",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Haml structure. CoffeeScript semantics. React runtime.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "dantiel <dantiel@proton.me>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dantiel/CoffeeHaml.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/dantiel/CoffeeHaml/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/dantiel/CoffeeHaml#readme",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"bin": {
|
|
19
|
+
"coffeehaml": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./dist/index.js",
|
|
23
|
+
"./vite": "./dist/vite-plugin.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"docs"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "tsc --watch",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"prepublishOnly": "npm run build"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"coffeehaml",
|
|
40
|
+
"haml",
|
|
41
|
+
"coffeescript",
|
|
42
|
+
"react",
|
|
43
|
+
"jsx",
|
|
44
|
+
"compiler",
|
|
45
|
+
"vite",
|
|
46
|
+
"template-language",
|
|
47
|
+
"indentation-based",
|
|
48
|
+
"jsx-runtime"
|
|
49
|
+
],
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"coffeescript": "^2.7.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"typescript": "^5.3.0",
|
|
56
|
+
"vitest": "^1.0.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"source-map": "^0.7.4"
|
|
60
|
+
}
|
|
61
|
+
}
|