coffeehaml 0.6.0 → 0.7.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/docs/index.html CHANGED
@@ -18,6 +18,10 @@
18
18
  --code-bg: #0d0d0d;
19
19
  --muted: #888;
20
20
  --border: #333;
21
+ --green: #8fb573;
22
+ --blue: #7ea9d0;
23
+ --red: #d4848c;
24
+ --purple: #b999d6;
21
25
  }
22
26
  * { margin: 0; padding: 0; box-sizing: border-box; }
23
27
  body {
@@ -25,25 +29,28 @@
25
29
  background: var(--bg);
26
30
  color: var(--fg);
27
31
  line-height: 1.6;
28
- max-width: 800px;
32
+ max-width: 840px;
29
33
  margin: 0 auto;
30
34
  padding: 3rem 1.5rem;
31
35
  }
32
36
  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); }
37
+ .tagline { font-size: 1.2rem; color: var(--muted); margin-bottom: 1.5rem; font-style: italic; }
38
+ h2 { font-size: 1.4rem; margin: 2.5rem 0 0.75rem; color: var(--accent); border-bottom: 1px solid var(--border); padding-bottom: 0.4rem; }
39
+ h3 { font-size: 1.1rem; margin: 1.5rem 0 0.5rem; color: var(--fg); }
35
40
  pre {
36
41
  background: var(--code-bg);
37
42
  padding: 1.25rem;
38
43
  border-radius: 8px;
39
44
  overflow-x: auto;
40
45
  font-family: 'SF Mono', 'Fira Code', monospace;
41
- font-size: 0.9rem;
42
- line-height: 1.5;
46
+ font-size: 0.85rem;
47
+ line-height: 1.55;
43
48
  border: 1px solid var(--border);
49
+ margin: 0.75rem 0;
44
50
  }
45
- code { font-family: 'SF Mono', 'Fira Code', monospace; }
46
- a { color: var(--accent); }
51
+ code { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.9em; }
52
+ a { color: var(--accent); text-decoration: none; }
53
+ a:hover { text-decoration: underline; }
47
54
  .btn {
48
55
  display: inline-block;
49
56
  padding: 0.6rem 1.4rem;
@@ -55,75 +62,291 @@
55
62
  margin-right: 0.75rem;
56
63
  margin-top: 0.5rem;
57
64
  }
65
+ .btn:hover { background: #d4b06a; text-decoration: none; }
58
66
  .btn-outline {
59
67
  background: transparent;
60
68
  color: var(--accent);
61
69
  border: 1px solid var(--accent);
62
70
  }
71
+ .btn-outline:hover { background: rgba(200,164,92,0.1); text-decoration: none; }
63
72
  .stats {
64
73
  display: flex;
65
74
  gap: 2rem;
66
75
  margin: 1.5rem 0;
76
+ flex-wrap: wrap;
67
77
  }
68
- .stat { text-align: center; }
78
+ .stat { text-align: center; min-width: 90px; }
69
79
  .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; }
80
+ .stat-label { font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
81
+ .badge {
82
+ display: inline-block;
83
+ padding: 0.15rem 0.5rem;
84
+ border-radius: 4px;
85
+ font-size: 0.7rem;
86
+ font-weight: 600;
87
+ text-transform: uppercase;
88
+ letter-spacing: 0.05em;
89
+ background: #2a3a1a;
90
+ color: var(--green);
91
+ border: 1px solid rgba(143,181,115,0.3);
92
+ }
93
+ .badge-new {
94
+ background: #2a2010;
95
+ color: var(--accent);
96
+ border: 1px solid rgba(200,164,92,0.3);
97
+ }
98
+ table {
99
+ width: 100%;
100
+ border-collapse: collapse;
101
+ margin: 1rem 0;
102
+ font-size: 0.9rem;
103
+ }
104
+ th, td {
105
+ padding: 0.5rem 0.75rem;
106
+ text-align: left;
107
+ border-bottom: 1px solid var(--border);
108
+ }
109
+ th { color: var(--accent); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; }
110
+ td:last-child { text-align: right; }
111
+ .two-col {
112
+ display: grid;
113
+ grid-template-columns: 1fr 1fr;
114
+ gap: 1.5rem;
115
+ margin: 1rem 0;
116
+ }
117
+ @media (max-width: 640px) {
118
+ .two-col { grid-template-columns: 1fr; }
119
+ body { padding: 1.5rem 1rem; }
120
+ h1 { font-size: 2rem; }
121
+ }
122
+ .example-label {
123
+ font-size: 0.75rem;
124
+ text-transform: uppercase;
125
+ letter-spacing: 0.08em;
126
+ color: var(--muted);
127
+ margin-bottom: 0.25rem;
128
+ margin-top: 1.25rem;
129
+ }
130
+ .example-label:first-of-type { margin-top: 0; }
131
+ .hl { color: var(--muted); } /* comment */
132
+ .kw { color: var(--red); } /* keyword */
133
+ .tg { color: var(--blue); } /* tag */
134
+ .at { color: var(--green); } /* attr */
135
+ .st { color: var(--green); } /* string */
136
+ .fn { color: var(--purple); } /* func */
137
+ .cl { color: var(--blue); } /* class */
138
+ .op { color: var(--accent); } /* operator */
139
+ footer { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border); color: var(--muted); font-size: 0.85rem; }
140
+ .feature-grid {
141
+ display: grid;
142
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
143
+ gap: 0.5rem 1.5rem;
144
+ margin: 1rem 0;
145
+ }
146
+ .feature-grid li { font-size: 0.9rem; margin-bottom: 0.3rem; }
147
+ blockquote {
148
+ border-left: 2px solid var(--accent);
149
+ padding-left: 1rem;
150
+ margin: 1rem 0;
151
+ color: var(--muted);
152
+ font-style: italic;
153
+ }
72
154
  </style>
73
155
  </head>
74
156
  <body>
75
- <h1>☕ CoffeeHaml</h1>
76
- <p class="tagline">Haml structure. CoffeeScript semantics. React runtime. <br>Zero overhead. 50% fewer tokens than JSX.</p>
157
+
158
+ <h1>&#9749; CoffeeHaml</h1>
159
+ <p class="tagline">Haml structure. CoffeeScript semantics. React runtime.<br>Zero overhead. 50% fewer tokens than JSX.</p>
77
160
 
78
161
  <p>
79
162
  <a class="btn" href="https://github.com/dantiel/CoffeeHaml">GitHub</a>
80
163
  <a class="btn btn-outline" href="https://www.npmjs.com/package/coffeehaml">npm</a>
164
+ <a class="btn btn-outline" href="#quick-start">Quick Start</a>
81
165
  </p>
82
166
 
83
167
  <div class="stats">
84
168
  <div class="stat"><div class="stat-num">0</div><div class="stat-label">Runtime KB</div></div>
85
169
  <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>
170
+ <div class="stat"><div class="stat-num">52 kB</div><div class="stat-label">Compiler Size</div></div>
171
+ <div class="stat"><div class="stat-num">25+</div><div class="stat-label">Features</div></div>
87
172
  </div>
88
173
 
174
+ <!-- EXAMPLE -->
89
175
  <h2>Write This</h2>
90
- <pre>.card
91
- %h2= post.title
92
- - if showBody
93
- .body= post.content</pre>
176
+
177
+ <pre><span class="hl">-# Dashboard.chaml</span>
178
+ <span class="hl">import</span> { observer } <span class="hl">from</span> <span class="st">'mobx-react-lite'</span>
179
+ <span class="hl">import</span> { useMemo } <span class="hl">from</span> <span class="st">'react'</span>
180
+
181
+ <span class="hl">@observer</span>
182
+ <span class="tg">%div</span>.<span class="cl">page</span>
183
+ <span class="tg">%header</span>
184
+ <span class="tg">%h1</span>= <span class="st">"Welcome, #{user.name}"</span>
185
+ <span class="tg">%nav</span>{ <span class="at">role</span>: <span class="st">'primary'</span> }
186
+ - <span class="kw">for</span> link <span class="kw">in</span> navLinks
187
+ <span class="tg">%a</span>{ <span class="at">href</span>: link.url }= link.label
188
+
189
+ <span class="tg">%main</span>
190
+ .dashboard-grid
191
+ = widgets.map <span class="fn">(widget) -&gt;</span>
192
+ <span class="tg">%Widget</span>{ widget, <span class="at">onDelete</span>: <span class="fn">-&gt;</span> handleDelete(widget.id) }
193
+
194
+ <span class="tg">%aside</span>
195
+ - <span class="kw">if</span> alerts.length &gt; 0
196
+ .alert-banner{ <span class="at">role</span>: <span class="st">'alert'</span> }
197
+ - <span class="kw">for</span> alert <span class="kw">in</span> alerts
198
+ <span class="tg">%p</span>= alert.message
199
+ - <span class="kw">else</span>
200
+ <span class="tg">%p</span>.<span class="cl">empty-state</span> No alerts — all clear.</pre>
94
201
 
95
202
  <h2>Get This</h2>
96
- <pre>import { jsx, jsxs } from "react/jsx-runtime";
97
203
 
98
- jsxs("div", { className: "card" },
99
- jsx("h2", null, post.title),
100
- ...(showBody ? [
101
- jsx("div", { className: "body" }, post.content)
102
- ] : [])
103
- );</pre>
204
+ <pre><span class="hl">import</span> { jsx, jsxs, Fragment } <span class="hl">from</span> <span class="st">"react/jsx-runtime"</span>;
205
+ <span class="hl">import</span> { observer } <span class="hl">from</span> <span class="st">"mobx-react-lite"</span>;
206
+ <span class="hl">import</span> { useMemo } <span class="hl">from</span> <span class="st">"react"</span>;
104
207
 
105
- <h2>Quick Start</h2>
106
- <pre>npm install coffeehaml</pre>
208
+ <span class="hl">export default</span> observer(<span class="kw">function</span> <span class="fn">Dashboard</span>(props) {
209
+ <span class="kw">return</span> jsxs(<span class="st">"div"</span>, { <span class="at">className</span>: <span class="st">"page"</span> },
210
+ jsxs(<span class="st">"header"</span>, <span class="kw">null</span>,
211
+ jsx(<span class="st">"h1"</span>, <span class="kw">null</span>, <span class="st">"Welcome, "</span> + user.name),
212
+ jsx(<span class="st">"nav"</span>, { <span class="at">role</span>: <span class="st">"primary"</span> },
213
+ ...navLinks.map(link =&gt; jsx(<span class="st">"a"</span>, { <span class="at">href</span>: link.url }, link.label))
214
+ )
215
+ ),
216
+ jsxs(<span class="st">"main"</span>, <span class="kw">null</span>,
217
+ jsx(<span class="st">"div"</span>, { <span class="at">className</span>: <span class="st">"dashboard-grid"</span> },
218
+ ...widgets.map(widget =&gt; jsx(Widget, { widget, <span class="at">onDelete</span>: () =&gt; handleDelete(widget.id) }))
219
+ ),
220
+ jsx(<span class="st">"aside"</span>, <span class="kw">null</span>,
221
+ ...(alerts.length &gt; 0 ? [
222
+ jsxs(<span class="st">"div"</span>, { <span class="at">className</span>: <span class="st">"alert-banner"</span>, <span class="at">role</span>: <span class="st">"alert"</span> },
223
+ ...alerts.map(alert =&gt; jsx(<span class="st">"p"</span>, <span class="kw">null</span>, alert.message))
224
+ )
225
+ ] : [
226
+ jsx(<span class="st">"p"</span>, { <span class="at">className</span>: <span class="st">"empty-state"</span> }, <span class="st">"No alerts — all clear."</span>)
227
+ ])
228
+ )
229
+ )
230
+ );
231
+ });</pre>
107
232
 
108
- <h3>Vite</h3>
109
- <pre>// vite.config.ts
110
- import coffeehaml from 'coffeehaml/vite';
233
+ <!-- QUICK START -->
234
+ <h2 id="quick-start">Quick Start</h2>
111
235
 
112
- export default {
113
- plugins: [coffeehaml()],
236
+ <h3>Scaffold a project <span class="badge badge-new">v0.6.0</span></h3>
237
+ <pre>$ <span class="op">npx coffeehaml init</span>
238
+
239
+ &#9749; CoffeeHaml init — project scaffold
240
+ Working directory: /my-react-app
241
+ Detected: package.json, Vite, TypeScript
242
+
243
+ Add coffeehaml as devDependency? <span class="op">(Y/n):</span>
244
+ Configure Vite plugin? <span class="op">(Y/n):</span>
245
+ Create sample .chaml component? <span class="op">(Y/n):</span>
246
+ Add build/watch scripts to package.json? <span class="op">(Y/n):</span></pre>
247
+
248
+ <h3>Or install manually</h3>
249
+ <pre>$ npm install coffeehaml</pre>
250
+
251
+ <div class="two-col">
252
+ <div>
253
+ <h3>Vite</h3>
254
+ <pre><span class="hl">// vite.config.ts</span>
255
+ <span class="hl">import</span> coffeehaml <span class="hl">from</span> <span class="st">'coffeehaml/vite'</span>;
256
+
257
+ <span class="hl">export default</span> {
258
+ <span class="at">plugins</span>: [coffeehaml()],
114
259
  };</pre>
260
+ </div>
261
+ <div>
262
+ <h3>CLI</h3>
263
+ <pre><span class="hl"># One-shot compile</span>
264
+ $ npx coffeehaml compile app.chaml <span class="op">-o</span> app.js
115
265
 
116
- <h3>CLI</h3>
117
- <pre>npx coffeehaml compile app.coffeehaml</pre>
266
+ <span class="hl"># Watch mode (v0.5.0)</span>
267
+ $ npx coffeehaml watch src/ <span class="op">--wrap</span> component</pre>
268
+ </div>
269
+ </div>
118
270
 
119
- <p style="margin-top:1.5rem;">
271
+ <p style="margin-top:0.5rem; color:var(--muted);">
120
272
  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>.
273
+ </p>
274
+
275
+ <!-- FEATURES -->
276
+ <h2>All Features</h2>
277
+
278
+ <table>
279
+ <tr><th>Feature</th><th>Status</th></tr>
280
+ <tr><td>Elements, components, implicit divs</td><td>&#10003;</td></tr>
281
+ <tr><td><code>.class</code> / <code>#id</code> shorthand</td><td>&#10003;</td></tr>
282
+ <tr><td><code>{attr: val}</code> + spread <code>{props...}</code></td><td>&#10003;</td></tr>
283
+ <tr><td>Inline <code>= expression</code> output</td><td>&#10003;</td></tr>
284
+ <tr><td><code>=</code> expression continuation <span class="badge badge-new">v0.5.0</span></td><td>&#10003;</td></tr>
285
+ <tr><td><code>= expr -&gt;</code> arrow continuation <span class="badge badge-new">v0.6.0</span></td><td>&#10003;</td></tr>
286
+ <tr><td><code>-</code> statement continuation <span class="badge">v0.4.2</span></td><td>&#10003;</td></tr>
287
+ <tr><td><code>- if</code> / <code>- unless</code> / <code>- else</code> / <code>- else if</code></td><td>&#10003;</td></tr>
288
+ <tr><td><code>- for item in items</code> → <code>.map()</code></td><td>&#10003;</td></tr>
289
+ <tr><td><code>- while</code> loops</td><td>&#10003;</td></tr>
290
+ <tr><td>Optional chaining <code>props.gyro?.roll</code></td><td>&#10003;</td></tr>
291
+ <tr><td>Haml / HTML comments</td><td>&#10003;</td></tr>
292
+ <tr><td><code>:filter</code> plugins (markdown, asciidoc)</td><td>&#10003;</td></tr>
293
+ <tr><td>Prologue passthrough (<code>import</code>, <code>@decorator</code>)</td><td>&#10003;</td></tr>
294
+ <tr><td>Component / HOC wrapping</td><td>&#10003;</td></tr>
295
+ <tr><td>Vite plugin (HMR + Fast Refresh <span class="badge badge-new">v0.6.0</span>)</td><td>&#10003;</td></tr>
296
+ <tr><td>CLI <code>--wrap</code> flag <span class="badge badge-new">v0.5.0</span></td><td>&#10003;</td></tr>
297
+ <tr><td>CLI <code>watch</code> mode <span class="badge badge-new">v0.5.0</span></td><td>&#10003;</td></tr>
298
+ <tr><td>CLI <code>init</code> scaffold <span class="badge badge-new">v0.6.0</span></td><td>&#10003;</td></tr>
299
+ <tr><td>Source-located error messages <span class="badge badge-new">v0.5.0</span></td><td>&#10003;</td></tr>
300
+ <tr><td>Multi-error parser recovery <span class="badge badge-new">v0.6.0</span></td><td>&#10003;</td></tr>
301
+ <tr><td>Source maps</td><td>&#10003;</td></tr>
302
+ <tr><td>CoffeeScript expression compilation</td><td>&#10003; (peer dep)</td></tr>
303
+ </table>
304
+
305
+ <!-- NODE API -->
306
+ <h2>Node API</h2>
307
+
308
+ <pre><span class="hl">import</span> { compile, compileFile } <span class="hl">from</span> <span class="st">'coffeehaml'</span>;
309
+
310
+ <span class="kw">const</span> result = compile(<span class="st">'%div Hello'</span>, {
311
+ <span class="at">filename</span>: <span class="st">'app.chaml'</span>,
312
+ <span class="at">wrap</span>: <span class="st">'component'</span>, <span class="hl">// 'none' | 'component' | 'observer' | string[]</span>
313
+ <span class="at">sourceMap</span>: <span class="kw">true</span>,
314
+ });
315
+
316
+ console.log(result.code);
317
+ <span class="hl">// → import { jsx } from "react/jsx-runtime";</span>
318
+ <span class="hl">// export default function App(props) {</span>
319
+ <span class="hl">// return jsx("div", null, "Hello");</span>
320
+ <span class="hl">// }</span></pre>
321
+
322
+ <!-- DESIGN -->
323
+ <h2>Design Principles</h2>
324
+
325
+ <ul class="feature-grid">
326
+ <li><strong>Zero runtime</strong> — compiler is the only artifact</li>
327
+ <li><strong>CoffeeScript-centric</strong> — not a Ruby-Haml port; CoffeeScript's semantics, not Ruby's</li>
328
+ <li><strong>Emitter-agnostic AST</strong> — future backends (Solid, Vue, Mithril)</li>
329
+ <li><strong>Source-located errors</strong> — line/column hints on every failure</li>
330
+ <li><strong>Source maps</strong> — debug in <code>.chaml</code>, not generated JS</li>
331
+ <li><strong>Incremental compilation</strong> — Vite HMR + Fast Refresh</li>
332
+ <li><strong>Multi-error recovery</strong> — parser collects all errors, not just first</li>
333
+ <li><strong>No invention</strong> — reuse Haml and CoffeeScript conventions</li>
334
+ </ul>
335
+
336
+ <!-- DOCS -->
337
+ <h2>Documentation</h2>
338
+ <p>
339
+ <a href="grammar.md">Grammar Reference</a> &middot;
340
+ <a href="examples.md">Syntax Examples</a> &middot;
341
+ <a href="architecture.md">Architecture</a> &middot;
342
+ <a href="ast.md">AST Specification</a> &middot;
343
+ <a href="https://github.com/dantiel/CoffeeHaml">GitHub README</a>
122
344
  </p>
123
345
 
124
346
  <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.
347
+ <strong>CoffeeHaml v0.6.0</strong> &mdash; MIT License &middot; Created by <a href="https://github.com/dantiel">dantiel</a><br>
348
+ Haml structure. CoffeeScript semantics. React runtime. Nothing more, nothing less.
127
349
  </footer>
350
+
128
351
  </body>
129
352
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coffeehaml",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Haml structure. CoffeeScript semantics. React runtime. Zero runtime. Vite-native.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,6 +26,10 @@
26
26
  "./vite": {
27
27
  "types": "./dist/vite-plugin.d.ts",
28
28
  "default": "./dist/vite-plugin.js"
29
+ },
30
+ "./prettier": {
31
+ "types": "./dist/prettier/index.d.ts",
32
+ "default": "./dist/prettier/index.js"
29
33
  }
30
34
  },
31
35
  "files": [
@@ -64,7 +68,8 @@
64
68
  "dev": "tsc --watch",
65
69
  "test": "vitest run",
66
70
  "test:watch": "vitest",
67
- "prepublishOnly": "npm run build"
71
+ "prepublishOnly": "npm run build",
72
+ "fmt": "prettier --write --plugin=. **/*.chaml"
68
73
  },
69
74
  "keywords": [
70
75
  "coffeehaml",
@@ -79,10 +84,12 @@
79
84
  "jsx-runtime"
80
85
  ],
81
86
  "peerDependencies": {
82
- "coffeescript": "^2.7.0"
87
+ "coffeescript": "^2.7.0",
88
+ "prettier": "^3.9.6"
83
89
  },
84
90
  "devDependencies": {
85
91
  "@types/node": "^20.0.0",
92
+ "prettier": "*",
86
93
  "typescript": "^5.3.0",
87
94
  "vitest": "^1.0.0"
88
95
  },