vite-plugin-htjs-pages 0.2.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/README.md ADDED
@@ -0,0 +1,629 @@
1
+ # vite-plugin-ht-pages
2
+
3
+ A lightweight Vite plugin for generating static HTML from `*.ht.js` page modules.
4
+
5
+ Pages are written as JavaScript that returns HTML. The plugin turns them into static HTML files during build.
6
+
7
+ This makes it ideal for **blogs, documentation sites, and marketing pages** where you want full control over HTML but still want a modern build pipeline.
8
+
9
+ ---
10
+
11
+ # Features
12
+
13
+ - File‑based routing
14
+ - Dynamic routes (`[slug].ht.js`)
15
+ - Catch‑all routes (`[...slug].ht.js`)
16
+ - Static params generation (`generateStaticParams()`)
17
+ - Dev server SSR rendering
18
+ - Clean URL support
19
+ - Parallel + batched page rendering
20
+ - Compatible with HT.js style HTML generation
21
+
22
+ ---
23
+
24
+ # Installation
25
+
26
+ ```bash
27
+ npm install vite-plugin-ht-pages --save-dev
28
+ ```
29
+
30
+ ---
31
+
32
+ # Quick Start
33
+
34
+ ### 1. Configure Vite
35
+
36
+ ```ts
37
+ import { defineConfig } from 'vite'
38
+ import { htPages } from 'vite-plugin-ht-pages'
39
+
40
+ export default defineConfig({
41
+ plugins: [htPages()]
42
+ })
43
+ ```
44
+
45
+ ---
46
+
47
+ ### 2. Create a page
48
+
49
+ ```
50
+ src/index.ht.js
51
+ ```
52
+
53
+ ```js
54
+ import { fragment, html, head, title, body, h1 } from 'javascript-to-html'
55
+
56
+ export default () => fragment(
57
+ '<!doctype html>',
58
+ html(
59
+ head(
60
+ title('hello world')
61
+ ),
62
+ body(
63
+ h1('Hello world')
64
+ )
65
+ )
66
+ )
67
+ ```
68
+
69
+ ---
70
+
71
+ ### 3. Run dev server
72
+
73
+ ```bash
74
+ vite
75
+ ```
76
+
77
+ Open:
78
+
79
+ ```
80
+ http://localhost:5173/
81
+ ```
82
+
83
+ ---
84
+
85
+ ### 4. Build static HTML
86
+
87
+ ```bash
88
+ vite build
89
+ ```
90
+
91
+ Output:
92
+
93
+ ```
94
+ dist/index.html
95
+ ```
96
+
97
+ ---
98
+
99
+ # Project Structure Example
100
+
101
+ ```
102
+ src/
103
+
104
+ index.ht.js
105
+
106
+ blog/
107
+ [slug].ht.js
108
+
109
+ docs/
110
+ [...slug].ht.js
111
+
112
+ templates/
113
+ layout.js
114
+ ```
115
+
116
+ Build output:
117
+
118
+ ```
119
+ dist/
120
+
121
+ index.html
122
+
123
+ blog/
124
+ hello-world/index.html
125
+
126
+ docs/
127
+ getting-started/index.html
128
+ ```
129
+
130
+ ---
131
+
132
+ # Page Modules
133
+
134
+ Pages export a function returning HTML using `javascript-to-html` helpers.
135
+
136
+ ```js
137
+ import { fragment, html, body, h1 } from 'javascript-to-html'
138
+
139
+ export default () => fragment(
140
+ '<!doctype html>',
141
+ html(
142
+ body(
143
+ h1('Hello')
144
+ )
145
+ )
146
+ )
147
+ ```
148
+
149
+ Async rendering works too:
150
+
151
+ ```js
152
+ import { fragment, html, body, h1 } from 'javascript-to-html'
153
+
154
+ export default async () => {
155
+ const post = await loadPost()
156
+
157
+ return fragment(
158
+ '<!doctype html>',
159
+ html(
160
+ body(
161
+ h1(post.title)
162
+ )
163
+ )
164
+ )
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ # Data Loading
171
+
172
+ Pages can export a `data()` function.
173
+
174
+ ```js
175
+ import { fragment, html, body, h1 } from 'javascript-to-html'
176
+
177
+ export async function data({ params }) {
178
+ return {
179
+ title: params.slug
180
+ }
181
+ }
182
+
183
+ export default ({ data }) => fragment(
184
+ '<!doctype html>',
185
+ html(
186
+ body(
187
+ h1(data.title)
188
+ )
189
+ )
190
+ )
191
+ ```
192
+
193
+ ---
194
+
195
+ # Dynamic Routes
196
+
197
+ ```
198
+ src/blog/[slug].ht.js
199
+ ```
200
+
201
+ ```js
202
+ import { fragment, html, body, h1 } from 'javascript-to-html'
203
+
204
+ export function generateStaticParams() {
205
+ return [
206
+ { slug: 'hello-world' },
207
+ { slug: 'deep-dive' }
208
+ ]
209
+ }
210
+
211
+ export default ({ params }) => fragment(
212
+ '<!doctype html>',
213
+ html(
214
+ body(
215
+ h1(params.slug)
216
+ )
217
+ )
218
+ )
219
+ ```
220
+
221
+ Output:
222
+
223
+ ```
224
+ /blog/hello-world/index.html
225
+ /blog/deep-dive/index.html
226
+ ```
227
+
228
+ ---
229
+
230
+ # Catch‑All Routes
231
+
232
+ ```
233
+ src/docs/[...slug].ht.js
234
+ ```
235
+
236
+ ```js
237
+ import { fragment, html, body, h1 } from 'javascript-to-html'
238
+
239
+ export function generateStaticParams() {
240
+ return [
241
+ { slug: 'getting-started' },
242
+ { slug: 'api/auth/login' }
243
+ ]
244
+ }
245
+
246
+ export default ({ params }) => fragment(
247
+ '<!doctype html>',
248
+ html(
249
+ body(
250
+ h1(`Docs: ${params.slug}`)
251
+ )
252
+ )
253
+ )
254
+ ```
255
+
256
+ Output:
257
+
258
+ ```
259
+ /docs/getting-started/index.html
260
+ /docs/api/auth/login/index.html
261
+ ```
262
+
263
+ ---
264
+
265
+ # Routing Rules
266
+
267
+ Routes are automatically sorted so specific routes win.
268
+
269
+ | Type | Example | Priority |
270
+ |-----|------|------|
271
+ | Static | `/blog/new` | Highest |
272
+ | Dynamic | `/blog/:slug` | Medium |
273
+ | Catch‑all | `/docs/*:slug` | Lowest |
274
+
275
+ This prevents dynamic routes from accidentally overriding static pages.
276
+
277
+ ---
278
+
279
+ # Plugin Options
280
+
281
+ ```ts
282
+ htPages({
283
+ cleanUrls: true,
284
+ renderConcurrency: 8,
285
+ renderBatchSize: 64
286
+ })
287
+ ```
288
+
289
+ | Option | Description |
290
+ |------|------|
291
+ | `cleanUrls` | `/page/index.html` instead of `/page.html` |
292
+ | `renderConcurrency` | concurrent page renders |
293
+ | `renderBatchSize` | render batch size |
294
+ | `include` | page file glob |
295
+ | `exclude` | excluded globs |
296
+ | `pagesDir` | route root directory |
297
+ | `mapOutputPath` | customize output path |
298
+
299
+ ---
300
+
301
+ # HT.js Example
302
+
303
+ ```js
304
+ import { fragment, html, body, h1, p } from 'javascript-to-html'
305
+
306
+ export default () => fragment(
307
+ '<!doctype html>',
308
+ html(
309
+ body(
310
+ h1('Hello'),
311
+ p('Welcome to my site')
312
+ )
313
+ )
314
+ )
315
+ ```
316
+
317
+ ---
318
+
319
+ # Example Blog Page
320
+
321
+ ```
322
+ src/blog/[slug].ht.js
323
+ ```
324
+
325
+ ```js
326
+ import { fragment, html, body, article, h1, p } from 'javascript-to-html'
327
+
328
+ export function generateStaticParams() {
329
+ return [
330
+ { slug: 'my-first-post' },
331
+ { slug: 'another-post' }
332
+ ]
333
+ }
334
+
335
+ export async function data({ params }) {
336
+ const post = await loadPost(params.slug)
337
+ return { post }
338
+ }
339
+
340
+ export default ({ data }) => fragment(
341
+ '<!doctype html>',
342
+ html(
343
+ body(
344
+ article(
345
+ h1(data.post.title),
346
+ p(data.post.content)
347
+ )
348
+ )
349
+ )
350
+ )
351
+ ```
352
+
353
+ ---
354
+
355
+ # Best Practices
356
+
357
+ ### Keep layouts reusable
358
+
359
+ ```
360
+ src/templates/layout.js
361
+ ```
362
+
363
+ Pages import layouts instead of duplicating HTML.
364
+
365
+ ---
366
+
367
+ ### Keep data loading separate
368
+
369
+ Prefer this:
370
+
371
+ ```js
372
+ export async function data() {}
373
+ ```
374
+
375
+ Instead of heavy logic in the render function.
376
+
377
+ ---
378
+
379
+ ### Prefer deterministic builds
380
+
381
+ Dynamic pages should use `generateStaticParams()` so builds are predictable.
382
+
383
+ ---
384
+
385
+ # Layouts
386
+
387
+ A common HT.js pattern is creating reusable layout templates.
388
+
389
+ ```
390
+ src/templates/layout.js
391
+ ```
392
+
393
+ Example layout:
394
+
395
+ ```js
396
+ import { fragment, html, head, body } from 'javascript-to-html'
397
+
398
+ export default (...content) => fragment(
399
+ '<!doctype html>',
400
+ html(
401
+ head(),
402
+ body(
403
+ ...content
404
+ )
405
+ )
406
+ )
407
+ ```
408
+
409
+ Then use the layout in a page:
410
+
411
+ ```js
412
+ import { h1 } from 'javascript-to-html'
413
+ import layout from '../templates/layout.js'
414
+
415
+ export default () => layout(
416
+ h1('Home page')
417
+ )
418
+ ```
419
+
420
+ This keeps page files small while sharing global structure.
421
+
422
+ ---
423
+
424
+ # Reusable Components
425
+
426
+ HT.js works well with small reusable components.
427
+
428
+ Example component:
429
+
430
+ ```
431
+ src/components/nav.js
432
+ ```
433
+
434
+ ```js
435
+ import { nav, a } from 'javascript-to-html'
436
+
437
+ export default () => nav(
438
+ a({ href: '/' }, 'Home'),
439
+ a({ href: '/blog' }, 'Blog')
440
+ )
441
+ ```
442
+
443
+ Use inside pages or layouts:
444
+
445
+ ```js
446
+ import { fragment, html, body, main, h1 } from 'javascript-to-html'
447
+ import nav from '../components/nav.js'
448
+
449
+ export default () => fragment(
450
+ '<!doctype html>',
451
+ html(
452
+ body(
453
+ nav(),
454
+ main(
455
+ h1('Welcome')
456
+ )
457
+ )
458
+ )
459
+ )
460
+ ```
461
+
462
+ Breaking UI into small components keeps templates maintainable and mirrors the approach used by component frameworks while remaining pure HTML generation.
463
+
464
+ ---
465
+
466
+ # Performance Tips
467
+
468
+ Large sites (500+ pages) should increase batching:
469
+
470
+ ```ts
471
+ htPages({
472
+ renderConcurrency: 16,
473
+ renderBatchSize: 128
474
+ })
475
+ ```
476
+
477
+ This keeps memory usage stable during builds.
478
+
479
+ ---
480
+
481
+ # Comparison
482
+
483
+ | Tool | Focus |
484
+ |----|----|
485
+ | Astro | full framework |
486
+ | Next.js | SSR framework |
487
+ | vite-plugin-ht-pages | minimal static HTML generation |
488
+
489
+ This plugin intentionally stays **small and unopinionated**.
490
+
491
+ ---
492
+
493
+ # FAQ
494
+
495
+ ### Can I use React/Vue inside pages?
496
+
497
+ Technically yes, but this plugin is intended for **HTML generation**, not SPA rendering.
498
+
499
+ ### Can I add layouts?
500
+
501
+ Yes — just import shared functions.
502
+
503
+ ```
504
+ import layout from '../templates/layout.js'
505
+ ```
506
+
507
+ ### Does it support thousands of pages?
508
+
509
+ Yes. Batched rendering keeps builds stable even for very large sites.
510
+
511
+ ---
512
+
513
+ # License
514
+
515
+ ```
516
+ MIT
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Example dynamic page
522
+
523
+ ```js
524
+ // src/blog/[slug].ht.js
525
+ import { fragment, html, head, title, body, main, h1 } from 'javascript-to-html'
526
+
527
+ export function generateStaticParams() {
528
+ return [
529
+ { slug: 'hello-world' },
530
+ { slug: 'deep-dive' },
531
+ ];
532
+ }
533
+
534
+ export async function data({ params }) {
535
+ return {
536
+ title: params.slug,
537
+ };
538
+ }
539
+
540
+ export default ({ data }) => fragment(
541
+ '<!doctype html>',
542
+ html(
543
+ head(
544
+ title(data.title)
545
+ ),
546
+ body(
547
+ main(
548
+ h1(data.title)
549
+ )
550
+ )
551
+ )
552
+ );
553
+ ```
554
+
555
+ ---
556
+
557
+ ## Example catch-all page
558
+
559
+ ```js
560
+ // src/docs/[...slug].ht.js
561
+ import { fragment, html, head, title, body, main, h1 } from 'javascript-to-html'
562
+
563
+ export function generateStaticParams() {
564
+ return [
565
+ { slug: 'getting-started' },
566
+ { slug: 'api/auth/login' },
567
+ { slug: 'guides/rendering/static' },
568
+ ];
569
+ }
570
+
571
+ export default ({ params }) => fragment(
572
+ '<!doctype html>',
573
+ html(
574
+ head(
575
+ title(params.slug)
576
+ ),
577
+ body(
578
+ main(
579
+ h1(params.slug)
580
+ )
581
+ )
582
+ )
583
+ );
584
+ ```
585
+
586
+ ---
587
+
588
+ ## Notes
589
+
590
+ ### Route precedence
591
+
592
+ Given both:
593
+
594
+ - `src/blog/new.ht.js`
595
+ - `src/blog/[slug].ht.js`
596
+
597
+ `/blog/new` will match the static page first.
598
+
599
+ ### Catch-all routes
600
+
601
+ `src/docs/[...slug].ht.js` matches nested paths and expects `generateStaticParams()` to provide values like:
602
+
603
+ - `{ slug: 'getting-started' }`
604
+ - `{ slug: 'api/auth/login' }`
605
+
606
+ ### Batched rendering
607
+
608
+ Large builds are processed in chunks for lower peak memory and more stable execution.
609
+
610
+ ---
611
+
612
+ ## What this version does well
613
+
614
+ - very small mental model
615
+ - static routes beat dynamic routes
616
+ - catch-all routes are supported
617
+ - no Node per-page dynamic-import loop in build mode
618
+ - static params are first-class
619
+ - dev mode stays simple by letting Vite SSR-load the page module directly
620
+ - build mode is calmer for large page counts
621
+
622
+ ## What it intentionally does not do
623
+
624
+ - no custom DOM patch HMR
625
+ - no incremental dependency invalidation
626
+ - no smart route groups or layout system
627
+ - no optional catch-all routes yet
628
+
629
+ That tradeoff is deliberate: this is a strong small-core version to prototype or publish from.