vite-plugin-htjs-pages 1.1.2 → 1.2.1
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 +239 -173
- package/TODO +2 -0
- package/dist/index.js +58 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/discover.ts +5 -1
- package/src/page-index.ts +5 -4
- package/src/plugin.ts +39 -13
- package/src/route-utils.ts +24 -18
package/README.md
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
# vite-plugin-htjs-pages
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/vite-plugin-htjs-pages)
|
|
3
|
+
[](https://www.npmjs.com/package/vite-plugin-htjs-pages)
|
|
4
|
+
[](https://www.npmjs.com/package/vite-plugin-htjs-pages)
|
|
7
5
|
[](LICENSE)
|
|
8
6
|
[](https://vitejs.dev)
|
|
9
7
|
|
|
10
|
-
Minimal **static site generation
|
|
11
|
-
functions that return HTML.
|
|
8
|
+
Minimal **static site generation for Vite** using JavaScript files that return HTML.
|
|
12
9
|
|
|
13
10
|
Generate static HTML pages from `*.ht.js` modules using Vite and
|
|
14
11
|
[`javascript-to-html`](https://www.npmjs.com/package/javascript-to-html).
|
|
15
12
|
|
|
16
13
|
⭐ If this project helps you, please consider starring it.
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## Built for the Vite ecosystem
|
|
15
|
+
# Built for the Vite ecosystem
|
|
21
16
|
|
|
22
17
|
Works seamlessly with:
|
|
23
18
|
|
|
@@ -28,25 +23,9 @@ Works seamlessly with:
|
|
|
28
23
|
A minimal **static site generator for Vite** that keeps pages as simple
|
|
29
24
|
JavaScript functions returning HTML.
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
# Features
|
|
34
|
-
|
|
35
|
-
- File‑based routing
|
|
36
|
-
- Dynamic routes (`[slug].ht.js`)
|
|
37
|
-
- Catch‑all routes (`[...slug].ht.js`)
|
|
38
|
-
- Static params generation (`generateStaticParams()`)
|
|
39
|
-
- Dev server SSR rendering
|
|
40
|
-
- Clean URL support
|
|
41
|
-
- Parallel batched page rendering
|
|
42
|
-
- Automatic `404.html`
|
|
43
|
-
- Automatic `sitemap.xml`
|
|
44
|
-
- Optional `rss.xml` generation
|
|
45
|
-
- Debug mode for easier troubleshooting
|
|
46
|
-
|
|
47
|
-
------------------------------------------------------------------------
|
|
26
|
+
---
|
|
48
27
|
|
|
49
|
-
|
|
28
|
+
# TL;DR
|
|
50
29
|
|
|
51
30
|
Write:
|
|
52
31
|
|
|
@@ -90,7 +69,34 @@ Get:
|
|
|
90
69
|
</html>
|
|
91
70
|
```
|
|
92
71
|
|
|
93
|
-
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
# Features
|
|
75
|
+
|
|
76
|
+
- File-based routing
|
|
77
|
+
- Dynamic routes `[slug]`
|
|
78
|
+
- Multiple parameters `[year]/[slug]`
|
|
79
|
+
- Catch-all routes `[...slug]`
|
|
80
|
+
- Optional catch-all routes `[...slug]?`
|
|
81
|
+
- Route groups `(admin)/users.ht.js`
|
|
82
|
+
- Index routes `blog/[slug]/index.ht.js`
|
|
83
|
+
- Static params generation
|
|
84
|
+
- Dev server SSR rendering
|
|
85
|
+
- Parallel static generation
|
|
86
|
+
- Automatic `404.html`
|
|
87
|
+
- Automatic `sitemap.xml`
|
|
88
|
+
- Optional RSS feed generation
|
|
89
|
+
- Debug logging
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
# Installation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install vite-plugin-htjs-pages javascript-to-html
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
94
100
|
|
|
95
101
|
# Why this exists
|
|
96
102
|
|
|
@@ -118,146 +124,240 @@ It gives you:
|
|
|
118
124
|
|
|
119
125
|
while keeping pages as **simple JavaScript functions that return HTML**.
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
---
|
|
122
128
|
|
|
123
|
-
#
|
|
129
|
+
# Quick Start
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
src/blog/[slug].ht.js
|
|
127
|
-
│
|
|
128
|
-
▼
|
|
129
|
-
vite-plugin-htjs-pages
|
|
130
|
-
│
|
|
131
|
-
▼
|
|
132
|
-
dist/index.html
|
|
133
|
-
dist/blog/hello-world/index.html
|
|
131
|
+
### vite.config.js
|
|
134
132
|
|
|
135
|
-
|
|
133
|
+
```js
|
|
134
|
+
import { defineConfig } from "vite"
|
|
135
|
+
import { htPages } from "vite-plugin-htjs-pages"
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
export default defineConfig({
|
|
138
|
+
plugins: [htPages()]
|
|
139
|
+
})
|
|
140
|
+
```
|
|
138
141
|
|
|
139
|
-
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
# Example Project Structure
|
|
140
145
|
|
|
141
|
-
``` bash
|
|
142
|
-
npm install vite-plugin-htjs-pages javascript-to-html
|
|
143
146
|
```
|
|
147
|
+
src/
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
index.ht.js
|
|
150
|
+
about.ht.js
|
|
146
151
|
|
|
147
|
-
|
|
152
|
+
blog/
|
|
153
|
+
index.ht.js
|
|
154
|
+
[slug].ht.js
|
|
155
|
+
[year]/[slug].ht.js
|
|
156
|
+
|
|
157
|
+
docs/
|
|
158
|
+
[...slug]?.ht.js
|
|
159
|
+
|
|
160
|
+
(admin)/
|
|
161
|
+
users.ht.js
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
# Routing
|
|
167
|
+
|
|
168
|
+
Routes are generated directly from the filesystem.
|
|
148
169
|
|
|
149
|
-
|
|
170
|
+
|
|
171
|
+
| Feature | File | URL |
|
|
172
|
+
|-----|-----|-----|
|
|
173
|
+
| static routes | `index.ht.js` | `/` |
|
|
174
|
+
| dynamic routes | `blog/[slug].ht.js` | `/blog/my-post` |
|
|
175
|
+
| multiple params | `blog/[year]/[slug].ht.js` | `/blog/2026/my-post` |
|
|
176
|
+
| catch-all | `docs/[...slug].ht.js` | `/docs/api/auth/login` |
|
|
177
|
+
| optional catch-all | `docs/[...slug]?.ht.js` | `/docs` or `/docs/getting-started` |
|
|
178
|
+
| index routes | `products/[product]/index.ht.js` | `/products/iphone-18` |
|
|
179
|
+
| route groups | `(admin)/users.ht.js` | `/users` |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
# Dynamic Routes
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
src/blog/[slug].ht.js
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Matches:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
/blog/hello-world
|
|
193
|
+
/blog/my-first-post
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Example:
|
|
150
197
|
|
|
151
198
|
``` js
|
|
152
|
-
import {
|
|
153
|
-
|
|
199
|
+
import { fragment, html, body, h1 } from 'javascript-to-html'
|
|
200
|
+
|
|
201
|
+
export function generateStaticParams() {
|
|
202
|
+
return [
|
|
203
|
+
{ slug: 'hello-world' },
|
|
204
|
+
{ slug: 'my-first-post' }
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export default ({ params }) => fragment(
|
|
209
|
+
'<!doctype html>',
|
|
210
|
+
html(
|
|
211
|
+
body(
|
|
212
|
+
h1(params.slug)
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
# Multiple Parameters
|
|
154
221
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
})
|
|
222
|
+
```
|
|
223
|
+
src/blog/[year]/[slug].ht.js
|
|
158
224
|
```
|
|
159
225
|
|
|
160
|
-
|
|
226
|
+
Matches:
|
|
161
227
|
|
|
162
|
-
|
|
228
|
+
```
|
|
229
|
+
/blog/2026/vite-routing
|
|
230
|
+
/blog/2025/my-first-post
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Example:
|
|
163
234
|
|
|
164
235
|
``` js
|
|
165
|
-
import { fragment, html,
|
|
236
|
+
import { fragment, html, body, h1 } from 'javascript-to-html'
|
|
166
237
|
|
|
167
|
-
export
|
|
238
|
+
export function generateStaticParams() {
|
|
239
|
+
return [
|
|
240
|
+
{
|
|
241
|
+
year: 2026,
|
|
242
|
+
slug: "vite-routing"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
year: 2025,
|
|
246
|
+
slug: 'my-first-post'
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default ({ params }) => fragment(
|
|
168
252
|
'<!doctype html>',
|
|
169
253
|
html(
|
|
170
|
-
head(
|
|
171
|
-
title('Hello world')
|
|
172
|
-
),
|
|
173
254
|
body(
|
|
174
|
-
h1(
|
|
255
|
+
h1(params.slug)
|
|
175
256
|
)
|
|
176
257
|
)
|
|
177
258
|
)
|
|
178
259
|
```
|
|
179
260
|
|
|
180
|
-
|
|
261
|
+
---
|
|
181
262
|
|
|
182
|
-
|
|
183
|
-
|
|
263
|
+
# Catch-All Routes
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
src/docs/[...slug].ht.js
|
|
184
267
|
```
|
|
185
268
|
|
|
186
|
-
|
|
269
|
+
Matches:
|
|
187
270
|
|
|
188
|
-
```
|
|
189
|
-
|
|
271
|
+
```
|
|
272
|
+
/docs/api/auth/login
|
|
273
|
+
/docs/guides/rendering/static
|
|
190
274
|
```
|
|
191
275
|
|
|
192
|
-
|
|
276
|
+
Params:
|
|
193
277
|
|
|
194
|
-
|
|
278
|
+
```
|
|
279
|
+
params.slug === "api/auth/login"
|
|
280
|
+
```
|
|
195
281
|
|
|
196
|
-
|
|
282
|
+
---
|
|
197
283
|
|
|
198
|
-
#
|
|
284
|
+
# Optional Catch-All Routes
|
|
199
285
|
|
|
200
|
-
|
|
286
|
+
```
|
|
287
|
+
src/docs/[...slug]?.ht.js
|
|
288
|
+
```
|
|
201
289
|
|
|
202
|
-
|
|
290
|
+
Matches both:
|
|
203
291
|
|
|
204
|
-
|
|
205
|
-
|
|
292
|
+
```
|
|
293
|
+
/docs
|
|
294
|
+
/docs/getting-started
|
|
295
|
+
/docs/api/auth/login
|
|
296
|
+
```
|
|
206
297
|
|
|
207
|
-
|
|
208
|
-
[slug].ht.js
|
|
298
|
+
Params:
|
|
209
299
|
|
|
210
|
-
|
|
211
|
-
|
|
300
|
+
| URL | params.slug |
|
|
301
|
+
|-----|-------------|
|
|
302
|
+
| `/docs` | "" |
|
|
303
|
+
| `/docs/api` | "api" |
|
|
304
|
+
| `/docs/api/auth` | "api/auth" |
|
|
212
305
|
|
|
213
|
-
|
|
306
|
+
---
|
|
214
307
|
|
|
215
|
-
|
|
216
|
-
/about/index.html
|
|
217
|
-
/blog/hello-world/index.html
|
|
218
|
-
/docs/getting-started/index.html
|
|
308
|
+
# Route Groups
|
|
219
309
|
|
|
220
|
-
|
|
310
|
+
Folders wrapped in parentheses are ignored in URLs.
|
|
221
311
|
|
|
222
|
-
|
|
312
|
+
```
|
|
313
|
+
src/(admin)/users.ht.js
|
|
314
|
+
```
|
|
223
315
|
|
|
224
|
-
|
|
316
|
+
URL:
|
|
225
317
|
|
|
226
|
-
```
|
|
227
|
-
|
|
318
|
+
```
|
|
319
|
+
/users
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
# Index Routes
|
|
325
|
+
|
|
326
|
+
Files named `index.ht.js` map to the parent route.
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
src/blog/index.ht.js -> /blog
|
|
330
|
+
src/blog/[slug]/index.ht.js -> /blog/my-post
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
# Static Params
|
|
228
336
|
|
|
337
|
+
Dynamic routes can export `generateStaticParams`.
|
|
338
|
+
|
|
339
|
+
```js
|
|
229
340
|
export function generateStaticParams() {
|
|
230
341
|
return [
|
|
231
|
-
{ slug:
|
|
232
|
-
{ slug:
|
|
342
|
+
{ slug: "hello-world" },
|
|
343
|
+
{ slug: "vite-routing" }
|
|
233
344
|
]
|
|
234
345
|
}
|
|
235
|
-
|
|
236
|
-
export default ({ params }) => fragment(
|
|
237
|
-
'<!doctype html>',
|
|
238
|
-
html(
|
|
239
|
-
body(
|
|
240
|
-
h1(params.slug)
|
|
241
|
-
)
|
|
242
|
-
)
|
|
243
|
-
)
|
|
244
346
|
```
|
|
245
347
|
|
|
246
|
-
|
|
348
|
+
---
|
|
247
349
|
|
|
248
350
|
# Data Loading
|
|
249
351
|
|
|
250
352
|
Pages can export a `data()` function.
|
|
251
353
|
|
|
252
|
-
```
|
|
354
|
+
```js
|
|
253
355
|
export async function data({ params }) {
|
|
254
|
-
return {
|
|
255
|
-
title: params.slug
|
|
256
|
-
}
|
|
356
|
+
return { title: params.slug }
|
|
257
357
|
}
|
|
258
358
|
```
|
|
259
359
|
|
|
260
|
-
|
|
360
|
+
---
|
|
261
361
|
|
|
262
362
|
# Layouts
|
|
263
363
|
|
|
@@ -277,37 +377,29 @@ export default (...content) => fragment(
|
|
|
277
377
|
)
|
|
278
378
|
```
|
|
279
379
|
|
|
280
|
-
|
|
380
|
+
---
|
|
281
381
|
|
|
282
382
|
# Plugin Options
|
|
283
383
|
|
|
284
|
-
``` js
|
|
285
|
-
htPages({
|
|
286
|
-
cleanUrls: true,
|
|
287
|
-
renderConcurrency: 8,
|
|
288
|
-
renderBatchSize: 64
|
|
289
|
-
})
|
|
290
|
-
```
|
|
291
|
-
|
|
292
384
|
| Option | Description |
|
|
293
385
|
|------|------|
|
|
294
386
|
| `pagesDir` | root directory for pages |
|
|
295
|
-
| `include` | page glob
|
|
296
|
-
| `exclude` | excluded
|
|
387
|
+
| `include` | page glob |
|
|
388
|
+
| `exclude` | excluded files |
|
|
297
389
|
| `cleanUrls` | `/page/index.html` instead of `/page.html` |
|
|
298
|
-
| `renderConcurrency` |
|
|
299
|
-
| `renderBatchSize` |
|
|
390
|
+
| `renderConcurrency` | parallel rendering |
|
|
391
|
+
| `renderBatchSize` | batch size |
|
|
300
392
|
| `debug` | enable debug logging |
|
|
301
|
-
| `site` | base URL
|
|
302
|
-
| `rss` |
|
|
393
|
+
| `site` | base URL for sitemap |
|
|
394
|
+
| `rss` | RSS configuration |
|
|
303
395
|
|
|
304
|
-
|
|
396
|
+
---
|
|
305
397
|
|
|
306
398
|
# Debug Mode
|
|
307
399
|
|
|
308
|
-
Enable debug
|
|
400
|
+
Enable debug logging when troubleshooting.
|
|
309
401
|
|
|
310
|
-
```
|
|
402
|
+
```js
|
|
311
403
|
htPages({
|
|
312
404
|
debug: true
|
|
313
405
|
})
|
|
@@ -322,38 +414,21 @@ Example output:
|
|
|
322
414
|
This helps diagnose routing or build issues without modifying plugin
|
|
323
415
|
code.
|
|
324
416
|
|
|
325
|
-
|
|
417
|
+
---
|
|
326
418
|
|
|
327
419
|
# Automatic Sitemap
|
|
328
420
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
dist/sitemap.xml
|
|
332
|
-
|
|
333
|
-
Example:
|
|
421
|
+
A `sitemap.xml` is generated automatically.
|
|
334
422
|
|
|
335
|
-
``` xml
|
|
336
|
-
<urlset>
|
|
337
|
-
<url><loc>/</loc></url>
|
|
338
|
-
<url><loc>/blog/hello-world</loc></url>
|
|
339
|
-
</urlset>
|
|
340
423
|
```
|
|
341
|
-
|
|
342
|
-
If you configure a `site` option, URLs become absolute:
|
|
343
|
-
|
|
344
|
-
``` js
|
|
345
|
-
htPages({
|
|
346
|
-
site: "https://example.com"
|
|
347
|
-
})
|
|
424
|
+
dist/sitemap.xml
|
|
348
425
|
```
|
|
349
426
|
|
|
350
|
-
|
|
427
|
+
---
|
|
351
428
|
|
|
352
|
-
#
|
|
429
|
+
# Optional RSS Feed
|
|
353
430
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
``` js
|
|
431
|
+
```js
|
|
357
432
|
htPages({
|
|
358
433
|
rss: {
|
|
359
434
|
site: "https://example.com",
|
|
@@ -364,35 +439,36 @@ htPages({
|
|
|
364
439
|
})
|
|
365
440
|
```
|
|
366
441
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
dist/rss.xml
|
|
370
|
-
|
|
371
|
-
Example item:
|
|
442
|
+
Produces:
|
|
372
443
|
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
<title>/blog/hello-world</title>
|
|
376
|
-
<link>https://example.com/blog/hello-world</link>
|
|
377
|
-
</item>
|
|
444
|
+
```
|
|
445
|
+
dist/rss.xml
|
|
378
446
|
```
|
|
379
447
|
|
|
380
|
-
|
|
448
|
+
---
|
|
381
449
|
|
|
382
|
-
# Performance
|
|
450
|
+
# Performance
|
|
383
451
|
|
|
384
|
-
Large sites
|
|
452
|
+
Large sites can increase concurrency:
|
|
385
453
|
|
|
386
|
-
```
|
|
454
|
+
```js
|
|
387
455
|
htPages({
|
|
388
456
|
renderConcurrency: 16,
|
|
389
457
|
renderBatchSize: 128
|
|
390
458
|
})
|
|
391
459
|
```
|
|
392
460
|
|
|
393
|
-
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
# Comparison
|
|
394
464
|
|
|
395
|
-
|
|
465
|
+
| Tool | Focus |
|
|
466
|
+
|-----|-----|
|
|
467
|
+
| Astro | component‑based SSG |
|
|
468
|
+
| Next.js | React SSR framework |
|
|
469
|
+
| vite-plugin-htjs-pages | minimal HTML SSG for Vite |
|
|
470
|
+
|
|
471
|
+
---
|
|
396
472
|
|
|
397
473
|
# Use Cases
|
|
398
474
|
|
|
@@ -406,17 +482,7 @@ This keeps memory usage stable during large builds.
|
|
|
406
482
|
- **Minimal static site generators**
|
|
407
483
|
- **HTML‑first Vite projects**
|
|
408
484
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
# Comparison
|
|
412
|
-
|
|
413
|
-
| Tool | Focus |
|
|
414
|
-
|-----|-----|
|
|
415
|
-
| Astro | component framework |
|
|
416
|
-
| Next.js | SSR framework |
|
|
417
|
-
| vite-plugin-htjs-pages | minimal static HTML generation |
|
|
418
|
-
|
|
419
|
-
------------------------------------------------------------------------
|
|
485
|
+
---
|
|
420
486
|
|
|
421
487
|
# License
|
|
422
488
|
|
package/TODO
ADDED
package/dist/index.js
CHANGED
|
@@ -27,10 +27,18 @@ function normalizeFsPath(p) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// src/route-utils.ts
|
|
30
|
+
function safeDecodeURIComponent(str) {
|
|
31
|
+
try {
|
|
32
|
+
return decodeURIComponent(str);
|
|
33
|
+
} catch {
|
|
34
|
+
return str;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
30
37
|
var DYNAMIC_SEGMENT_RE = /\[([A-Za-z0-9_]+)\]/g;
|
|
31
38
|
var CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]/g;
|
|
32
39
|
var OPTIONAL_CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]\?/g;
|
|
33
40
|
var ANY_PARAM_RE = /\[(?:\.\.\.)?([A-Za-z0-9_]+)\]\??/g;
|
|
41
|
+
var ROUTE_GROUP_RE = /(^|\/)\(([^)]+)\)(?=\/|$)/g;
|
|
34
42
|
function getParamNames(relativeFromPagesDir) {
|
|
35
43
|
return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);
|
|
36
44
|
}
|
|
@@ -39,11 +47,13 @@ function isDynamicPage(relativeFromPagesDir) {
|
|
|
39
47
|
}
|
|
40
48
|
function toRoutePattern(relativeFromPagesDir) {
|
|
41
49
|
const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));
|
|
42
|
-
const
|
|
50
|
+
const withoutGroups = noExt.replace(ROUTE_GROUP_RE, "$1");
|
|
51
|
+
const withoutIndex = withoutGroups.replace(/\/index$/i, "").replace(/^index$/i, "");
|
|
52
|
+
const raw = withoutIndex.replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, "*?:$1").replace(CATCH_ALL_SEGMENT_RE, "*:$1").replace(DYNAMIC_SEGMENT_RE, ":$1");
|
|
43
53
|
return normalizeRoutePath(raw || "/");
|
|
44
54
|
}
|
|
45
55
|
function fillParams(pattern, params) {
|
|
46
|
-
|
|
56
|
+
const result = pattern.replace(/\*\?:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
47
57
|
const value = params[key];
|
|
48
58
|
if (value == null || value === "") {
|
|
49
59
|
return "";
|
|
@@ -59,7 +69,8 @@ function fillParams(pattern, params) {
|
|
|
59
69
|
throw new Error(`Missing route param "${key}"`);
|
|
60
70
|
}
|
|
61
71
|
return encodeURIComponent(params[key]);
|
|
62
|
-
})
|
|
72
|
+
});
|
|
73
|
+
return normalizeRoutePath(result || "/");
|
|
63
74
|
}
|
|
64
75
|
function fileNameFromRoute(routePath, cleanUrls) {
|
|
65
76
|
const normalized = normalizeRoutePath(routePath);
|
|
@@ -72,9 +83,7 @@ function expandStaticPaths(basePage, rows, cleanUrls) {
|
|
|
72
83
|
const params = Object.fromEntries(
|
|
73
84
|
Object.entries(row).map(([k, v]) => [k, String(v)])
|
|
74
85
|
);
|
|
75
|
-
const routePath =
|
|
76
|
-
fillParams(basePage.routePattern, params)
|
|
77
|
-
);
|
|
86
|
+
const routePath = fillParams(basePage.routePattern, params);
|
|
78
87
|
return {
|
|
79
88
|
...basePage,
|
|
80
89
|
routePath,
|
|
@@ -91,20 +100,18 @@ function routeMatch(pattern, urlPath) {
|
|
|
91
100
|
const patternSeg = a[i];
|
|
92
101
|
const urlSeg = b[i];
|
|
93
102
|
if (patternSeg.startsWith("*?:")) {
|
|
94
|
-
params[patternSeg.slice(3)] = i < b.length ? b.slice(i).map(
|
|
103
|
+
params[patternSeg.slice(3)] = i < b.length ? b.slice(i).map(safeDecodeURIComponent).join("/") : "";
|
|
95
104
|
return params;
|
|
96
105
|
}
|
|
97
106
|
if (patternSeg.startsWith("*:")) {
|
|
98
107
|
const rest = b.slice(i);
|
|
99
|
-
if (rest.length === 0)
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
params[patternSeg.slice(2)] = rest.map(decodeURIComponent).join("/");
|
|
108
|
+
if (rest.length === 0) return null;
|
|
109
|
+
params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join("/");
|
|
103
110
|
return params;
|
|
104
111
|
}
|
|
105
112
|
if (!urlSeg) return null;
|
|
106
113
|
if (patternSeg.startsWith(":")) {
|
|
107
|
-
params[patternSeg.slice(1)] =
|
|
114
|
+
params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);
|
|
108
115
|
continue;
|
|
109
116
|
}
|
|
110
117
|
if (patternSeg !== urlSeg) return null;
|
|
@@ -136,7 +143,7 @@ function compareRoutePriority(a, b) {
|
|
|
136
143
|
return aDynamic ? 1 : -1;
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
|
-
return
|
|
146
|
+
return bSegs.length - aSegs.length;
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
// src/constants.ts
|
|
@@ -147,7 +154,11 @@ var CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;
|
|
|
147
154
|
|
|
148
155
|
// src/discover.ts
|
|
149
156
|
async function discoverEntryPages(root, options) {
|
|
150
|
-
const
|
|
157
|
+
const rawInclude = Array.isArray(options.include) ? options.include : [options.include ?? "src/**/*.ht.js"];
|
|
158
|
+
let include = rawInclude.filter((p) => typeof p === "string" && p.length > 0);
|
|
159
|
+
if (include.length === 0) {
|
|
160
|
+
include = ["src/**/*.ht.js"];
|
|
161
|
+
}
|
|
151
162
|
const exclude = Array.isArray(options.exclude) ? options.exclude : options.exclude ? [options.exclude] : [];
|
|
152
163
|
const pagesDir = options.pagesDir ?? "src";
|
|
153
164
|
const pagesRoot = normalizeFsPath(path2.join(root, pagesDir));
|
|
@@ -271,7 +282,7 @@ async function buildPageIndex(args) {
|
|
|
271
282
|
for (const entry of entries) {
|
|
272
283
|
const mod = modulesByEntry.get(entry.entryPath) ?? {};
|
|
273
284
|
if (entry.dynamic) {
|
|
274
|
-
const rows = mod.generateStaticParams ? await mod.generateStaticParams() : [];
|
|
285
|
+
const rows = (mod.generateStaticParams ? await mod.generateStaticParams() : []) ?? [];
|
|
275
286
|
pages.push(
|
|
276
287
|
...expandStaticPaths(
|
|
277
288
|
{
|
|
@@ -283,7 +294,7 @@ async function buildPageIndex(args) {
|
|
|
283
294
|
dynamic: entry.dynamic,
|
|
284
295
|
paramNames: entry.paramNames
|
|
285
296
|
},
|
|
286
|
-
rows,
|
|
297
|
+
Array.isArray(rows) ? rows : [],
|
|
287
298
|
cleanUrls
|
|
288
299
|
)
|
|
289
300
|
);
|
|
@@ -388,12 +399,16 @@ async function buildRenderBundle(args) {
|
|
|
388
399
|
|
|
389
400
|
// src/plugin.ts
|
|
390
401
|
function chunkArray(items, size) {
|
|
402
|
+
const safeSize = Math.max(1, Math.floor(size));
|
|
391
403
|
const out = [];
|
|
392
|
-
for (let i = 0; i < items.length; i +=
|
|
393
|
-
out.push(items.slice(i, i +
|
|
404
|
+
for (let i = 0; i < items.length; i += safeSize) {
|
|
405
|
+
out.push(items.slice(i, i + safeSize));
|
|
394
406
|
}
|
|
395
407
|
return out;
|
|
396
408
|
}
|
|
409
|
+
function escapeXml(text) {
|
|
410
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
411
|
+
}
|
|
397
412
|
function createEntriesKey(entries) {
|
|
398
413
|
const raw = entries.map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`).join("\n");
|
|
399
414
|
return createHash2("sha256").update(raw).digest("hex");
|
|
@@ -408,12 +423,22 @@ function htPages(options = {}) {
|
|
|
408
423
|
let devPages = [];
|
|
409
424
|
let cachedManifestKey = null;
|
|
410
425
|
let cachedBundlePath = null;
|
|
426
|
+
let loadDevPagesInFlight = null;
|
|
411
427
|
const cleanUrls = options.cleanUrls ?? true;
|
|
412
428
|
function logDebug(enabled, ...args) {
|
|
413
429
|
if (!enabled) return;
|
|
414
430
|
console.log(`[${PLUGIN_NAME}]`, ...args);
|
|
415
431
|
}
|
|
416
432
|
async function loadDevPages() {
|
|
433
|
+
if (loadDevPagesInFlight) return loadDevPagesInFlight;
|
|
434
|
+
loadDevPagesInFlight = doLoadDevPages();
|
|
435
|
+
try {
|
|
436
|
+
return await loadDevPagesInFlight;
|
|
437
|
+
} finally {
|
|
438
|
+
loadDevPagesInFlight = null;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function doLoadDevPages() {
|
|
417
442
|
const entries = await discoverEntryPages(root, options);
|
|
418
443
|
const modulesByEntry = /* @__PURE__ */ new Map();
|
|
419
444
|
logDebug(options.debug, "discovered entries", entries.map((e) => e.relativePath));
|
|
@@ -531,8 +556,12 @@ function htPages(options = {}) {
|
|
|
531
556
|
async generateBundle(_, bundle) {
|
|
532
557
|
const { modulesByEntry, pages } = await buildPagesPipeline();
|
|
533
558
|
logDebug(options.debug, "emitting pages", pages.map((p) => p.fileName));
|
|
534
|
-
const
|
|
535
|
-
const
|
|
559
|
+
const concurrency = Math.max(1, options.renderConcurrency ?? 8);
|
|
560
|
+
const limit = pLimit(concurrency);
|
|
561
|
+
const batchSize = Math.max(
|
|
562
|
+
1,
|
|
563
|
+
options.renderBatchSize ?? Math.max(concurrency, 32)
|
|
564
|
+
);
|
|
536
565
|
for (const batch of chunkArray(pages, batchSize)) {
|
|
537
566
|
await Promise.all(
|
|
538
567
|
batch.map(
|
|
@@ -558,7 +587,9 @@ function htPages(options = {}) {
|
|
|
558
587
|
if (sitemapRoutes.length > 0) {
|
|
559
588
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
560
589
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
561
|
-
${sitemapRoutes.map(
|
|
590
|
+
${sitemapRoutes.map(
|
|
591
|
+
(route) => ` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`
|
|
592
|
+
).join("\n")}
|
|
562
593
|
</urlset>
|
|
563
594
|
`;
|
|
564
595
|
this.emitFile({
|
|
@@ -572,17 +603,17 @@ function htPages(options = {}) {
|
|
|
572
603
|
const rssItems = pages.filter((page) => page.routePath.startsWith(routePrefix)).map((page) => {
|
|
573
604
|
const url = `${options.rss.site}${page.routePath}`;
|
|
574
605
|
return ` <item>
|
|
575
|
-
<title>${page.routePath}</title>
|
|
576
|
-
<link>${url}</link>
|
|
577
|
-
<guid>${url}</guid>
|
|
606
|
+
<title>${escapeXml(page.routePath)}</title>
|
|
607
|
+
<link>${escapeXml(url)}</link>
|
|
608
|
+
<guid>${escapeXml(url)}</guid>
|
|
578
609
|
</item>`;
|
|
579
610
|
}).join("\n");
|
|
580
611
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
|
581
612
|
<rss version="2.0">
|
|
582
613
|
<channel>
|
|
583
|
-
<title>${options.rss.title ?? PLUGIN_NAME}</title>
|
|
584
|
-
<link>${options.rss.site}</link>
|
|
585
|
-
<description>${options.rss.description ?? "RSS feed"}</description>
|
|
614
|
+
<title>${escapeXml(options.rss.title ?? PLUGIN_NAME)}</title>
|
|
615
|
+
<link>${escapeXml(options.rss.site)}</link>
|
|
616
|
+
<description>${escapeXml(options.rss.description ?? "RSS feed")}</description>
|
|
586
617
|
${rssItems}
|
|
587
618
|
</channel>
|
|
588
619
|
</rss>
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts","../src/discover.ts","../src/path-utils.ts","../src/route-utils.ts","../src/constants.ts","../src/errors.ts","../src/render-runtime.ts","../src/dev-server.ts","../src/page-index.ts","../src/render-bundle.ts","../src/manifest.ts"],"sourcesContent":["import path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { createHash } from 'node:crypto';\nimport pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { buildPageIndex } from './page-index';\nimport { buildRenderBundle } from './render-bundle';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID, CACHE_DIR_NAME } from './constants';\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n out.push(items.slice(i, i + size));\n }\n return out;\n}\n\nfunction createEntriesKey(entries: HtPageInfo[]): string {\n const raw = entries\n .map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)\n .join('\\n');\n\n return createHash('sha256').update(raw).digest('hex');\n}\n\nasync function importManifest(\n bundlePath: string,\n): Promise<Array<{ page: HtPageInfo; mod: HtPageModule }>> {\n const mod = await import(pathToFileURL(bundlePath).href + `?t=${Date.now()}`);\n return mod.manifest as Array<{ page: HtPageInfo; mod: HtPageModule }>;\n}\n\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n\n let cachedManifestKey: string | null = null;\n let cachedBundlePath: string | null = null;\n\n const cleanUrls = options.cleanUrls ?? true;\n\n function logDebug(enabled: boolean | undefined, ...args: unknown[]) {\n if (!enabled) return;\n console.log(`[${PLUGIN_NAME}]`, ...args);\n }\n\n async function loadDevPages(): Promise<HtPageInfo[]> {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n logDebug(options.debug, 'discovered entries', entries.map((e) => e.relativePath));\n\n if (!server) return [];\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n devPages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n logDebug(\n options.debug,\n 'dev pages',\n devPages.map((p) => `${p.routePath} -> ${p.relativePath}`),\n );\n\n return devPages;\n }\n\n async function buildPagesPipeline() {\n const entries = await discoverEntryPages(root, options);\n const cacheDir = path.join(root, CACHE_DIR_NAME);\n\n const entriesKey = createEntriesKey(entries);\n\n let bundlePath: string;\n if (cachedBundlePath && cachedManifestKey === entriesKey) {\n bundlePath = cachedBundlePath;\n } else {\n bundlePath = await buildRenderBundle({\n entries,\n cacheDir,\n ssrPlugins: options.ssrPlugins,\n });\n cachedManifestKey = entriesKey;\n cachedBundlePath = bundlePath;\n }\n\n logDebug(options.debug, 'render bundle', bundlePath);\n\n const manifest = await importManifest(bundlePath);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n for (const rec of manifest) {\n modulesByEntry.set(rec.page.entryPath, rec.mod);\n }\n\n const pages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n // Ensure static hosts get a 404.html\n const notFoundPage = pages.find((p) => p.routePath === '/404');\n\n if (notFoundPage && !pages.some((p) => p.fileName === '404.html')) {\n pages.push({\n ...notFoundPage,\n fileName: '404.html',\n });\n } \n\n return { entries, bundlePath, modulesByEntry, pages };\n }\n\n return {\n name: PLUGIN_NAME,\n\n config(userConfig, env) {\n if (env.command !== 'build') return;\n\n const hasExplicitInput = userConfig.build?.rollupOptions?.input != null;\n if (hasExplicitInput) return;\n\n return {\n build: {\n rollupOptions: {\n input: VIRTUAL_BUILD_ENTRY_ID,\n },\n },\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) return id;\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) {\n return 'export default {};';\n }\n return null;\n },\n\n configResolved(resolved) {\n root = resolved.root;\n },\n\n async buildStart() {\n const entries = await discoverEntryPages(root, options);\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n },\n\n configureServer(_server) {\n server = _server;\n\n installDevServer({\n server,\n getPages: async () => {\n if (devPages.length > 0) return devPages;\n return loadDevPages();\n },\n });\n\n loadDevPages().catch((error) => {\n server?.config.logger.error(\n `[${PLUGIN_NAME}] loadDevPages failed: ${\n error instanceof Error ? error.stack ?? error.message : String(error)\n }`,\n );\n });\n },\n\n async handleHotUpdate(ctx) {\n if (!server) return;\n \n const file = ctx.file;\n \n if (\n file.endsWith('.ht.js') ||\n file.includes('/templates/')\n ) {\n logDebug(options.debug, 'reindex triggered by', file);\n await loadDevPages();\n }\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n \n logDebug(options.debug, 'emitting pages', pages.map((p) => p.fileName));\n \n const limit = pLimit(options.renderConcurrency ?? 8);\n const batchSize =\n options.renderBatchSize ??\n Math.max(options.renderConcurrency ?? 8, 32);\n \n for (const batch of chunkArray(pages, batchSize)) {\n await Promise.all(\n batch.map((page) =>\n limit(async () => {\n const mod = modulesByEntry.get(page.entryPath);\n \n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,\n );\n }\n \n const html = await renderPage(page, mod, false);\n \n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n \n // Generate sitemap.xml\n const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))]\n .filter((route) => !route.includes(':') && !route.includes('*'));\n \n if (sitemapRoutes.length > 0) {\n const sitemap = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n ${sitemapRoutes\n .map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)\n .join('\\n')}\n </urlset>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n });\n }\n \n // Generate rss.xml\n if (options.rss?.site) {\n const routePrefix = options.rss.routePrefix ?? '/blog';\n \n const rssItems = pages\n .filter((page) => page.routePath.startsWith(routePrefix))\n .map((page) => {\n const url = `${options.rss!.site}${page.routePath}`;\n return ` <item>\n <title>${page.routePath}</title>\n <link>${url}</link>\n <guid>${url}</guid>\n </item>`;\n })\n .join('\\n');\n \n const rss = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <rss version=\"2.0\">\n <channel>\n <title>${options.rss.title ?? PLUGIN_NAME}</title>\n <link>${options.rss.site}</link>\n <description>${options.rss.description ?? 'RSS feed'}</description>\n ${rssItems}\n </channel>\n </rss>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'rss.xml',\n source: rss,\n });\n }\n \n // Remove the dummy virtual build entry chunk\n for (const [fileName, output] of Object.entries(bundle)) {\n if (\n output.type === 'chunk' &&\n output.facadeModuleId === VIRTUAL_BUILD_ENTRY_ID\n ) {\n delete bundle[fileName];\n }\n }\n } \n\n };\n}\n","import path from 'node:path';\nimport fg from 'fast-glob';\nimport { normalizeFsPath, toPosix } from './path-utils';\nimport { getParamNames, isDynamicPage, toRoutePattern } from './route-utils';\nimport type { HtPageInfo, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME } from './constants';\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const include = Array.isArray(options.include)\n ? options.include\n : [options.include ?? 'src/**/*.ht.js'];\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesDir = options.pagesDir ?? 'src';\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg(include, {\n cwd: root,\n ignore: exclude,\n absolute: true,\n });\n\n return files\n .sort()\n .map((absolutePath) => {\n const entryPath = normalizeFsPath(absolutePath);\n const relativePath = toPosix(path.relative(root, entryPath));\n const relativeFromPagesDir = toPosix(path.relative(pagesRoot, entryPath));\n\n if (\n relativeFromPagesDir.startsWith('../') ||\n relativeFromPagesDir === '..'\n ) {\n throw new Error(\n `[${PLUGIN_NAME}] Page is outside pagesDir: ${entryPath} (pagesDir: ${pagesDir})`,\n );\n }\n\n const dynamic = isDynamicPage(relativeFromPagesDir);\n const routePattern = toRoutePattern(relativeFromPagesDir);\n\n return {\n id: entryPath,\n entryPath,\n absolutePath: entryPath,\n relativePath,\n routePattern,\n routePath: routePattern,\n fileName: '',\n dynamic,\n paramNames: getParamNames(relativeFromPagesDir),\n params: {},\n } satisfies HtPageInfo;\n });\n}","import path from 'node:path';\n\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\nexport function stripHtSuffix(file: string): string {\n return file.replace(/\\.ht\\.js$/i, '');\n}\n\nexport function normalizeRoutePath(p: string): string {\n let out = p.startsWith('/') ? p : `/${p}`;\n out = out.replace(/\\/+/g, '/');\n if (out !== '/' && out.endsWith('/')) out = out.slice(0, -1);\n return out;\n}\n\nexport function normalizeFsPath(p: string): string {\n return toPosix(path.resolve(p));\n}","import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';\nimport type { HtPageInfo, StaticParamRecord } from './types';\n\nconst DYNAMIC_SEGMENT_RE = /\\[([A-Za-z0-9_]+)\\]/g;\nconst CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]/g;\nconst OPTIONAL_CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]\\?/g;\nconst ANY_PARAM_RE = /\\[(?:\\.\\.\\.)?([A-Za-z0-9_]+)\\]\\??/g;\n\nexport function getParamNames(relativeFromPagesDir: string): string[] {\n return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);\n}\n\nexport function isDynamicPage(relativeFromPagesDir: string): boolean {\n return /\\[(?:\\.\\.\\.)?[A-Za-z0-9_]+\\]\\??/.test(relativeFromPagesDir);\n}\n\nexport function toRoutePattern(relativeFromPagesDir: string): string {\n const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));\n\n const raw = noExt\n .replace(/(^|\\/)index$/i, '$1')\n .replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, '*?:$1')\n .replace(CATCH_ALL_SEGMENT_RE, '*:$1')\n .replace(DYNAMIC_SEGMENT_RE, ':$1');\n\n return normalizeRoutePath(raw || '/');\n}\n\nexport function fillParams(\n pattern: string,\n params: Record<string, string>,\n): string {\n return pattern\n .replace(/\\*\\?:([A-Za-z0-9_]+)/g, (_, key) => {\n const value = params[key];\n\n if (value == null || value === '') {\n return '';\n }\n\n return String(value)\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/\\*:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing catch-all route param \"${key}\"`);\n }\n\n return String(params[key])\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing route param \"${key}\"`);\n }\n\n return encodeURIComponent(params[key]);\n })\n .replace(/\\/+/g, '/')\n .replace(/\\/$/, '') || '/';\n}\n\nexport function fileNameFromRoute(\n routePath: string,\n cleanUrls: boolean,\n): string {\n const normalized = normalizeRoutePath(routePath);\n\n if (normalized === '/') return 'index.html';\n\n const base = normalized.slice(1);\n return cleanUrls ? `${base}/index.html` : `${base}.html`;\n}\n\nexport function expandStaticPaths(\n basePage: Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows: StaticParamRecord[],\n cleanUrls: boolean,\n): HtPageInfo[] {\n return rows.map((row) => {\n const params = Object.fromEntries(\n Object.entries(row).map(([k, v]) => [k, String(v)]),\n );\n\n const routePath = normalizeRoutePath(\n fillParams(basePage.routePattern, params),\n );\n\n return {\n ...basePage,\n routePath,\n fileName: fileNameFromRoute(routePath, cleanUrls),\n params,\n };\n });\n}\n\nexport function routeMatch(\n pattern: string,\n urlPath: string,\n): Record<string, string> | null {\n const a = normalizeRoutePath(pattern).split('/').filter(Boolean);\n const b = normalizeRoutePath(urlPath).split('/').filter(Boolean);\n const params: Record<string, string> = {};\n\n for (let i = 0; i < a.length; i++) {\n const patternSeg = a[i];\n const urlSeg = b[i];\n\n if (patternSeg.startsWith('*?:')) {\n params[patternSeg.slice(3)] =\n i < b.length ? b.slice(i).map(decodeURIComponent).join('/') : '';\n return params;\n }\n\n if (patternSeg.startsWith('*:')) {\n const rest = b.slice(i);\n\n if (rest.length === 0) {\n return null;\n }\n\n params[patternSeg.slice(2)] = rest.map(decodeURIComponent).join('/');\n return params;\n }\n\n if (!urlSeg) return null;\n\n if (patternSeg.startsWith(':')) {\n params[patternSeg.slice(1)] = decodeURIComponent(urlSeg);\n continue;\n }\n\n if (patternSeg !== urlSeg) return null;\n }\n\n return a.length === b.length ? params : null;\n}\n\nexport function compareRoutePriority(a: string, b: string): number {\n const aSegs = normalizeRoutePath(a).split('/').filter(Boolean);\n const bSegs = normalizeRoutePath(b).split('/').filter(Boolean);\n const len = Math.max(aSegs.length, bSegs.length);\n\n for (let i = 0; i < len; i++) {\n const aa = aSegs[i];\n const bb = bSegs[i];\n\n if (aa == null) return 1;\n if (bb == null) return -1;\n\n const aOptionalCatchAll = aa.startsWith('*?:');\n const bOptionalCatchAll = bb.startsWith('*?:');\n if (aOptionalCatchAll !== bOptionalCatchAll) {\n return aOptionalCatchAll ? 1 : -1;\n }\n\n const aCatchAll = aa.startsWith('*:');\n const bCatchAll = bb.startsWith('*:');\n if (aCatchAll !== bCatchAll) {\n return aCatchAll ? 1 : -1;\n }\n\n const aDynamic = aa.startsWith(':');\n const bDynamic = bb.startsWith(':');\n if (aDynamic !== bDynamic) {\n return aDynamic ? 1 : -1;\n }\n }\n\n return 0;\n}","export const PLUGIN_NAME = 'vite-plugin-htjs-pages';\nexport const VIRTUAL_BUILD_ENTRY_ID = `\\0${PLUGIN_NAME}:build-entry`;\nexport const VIRTUAL_MANIFEST_ID = `\\0virtual:${PLUGIN_NAME}-manifest`;\nexport const CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;","import type { HtPageInfo } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport function invalidHtmlReturn(\n page: HtPageInfo,\n value: unknown,\n): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" must resolve to an HTML string, got ${typeof value}`,\n );\n}\n\nexport function missingDefaultExport(page: HtPageInfo): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" does not export a default renderer`,\n );\n}\n\nexport function pageError(page: HtPageInfo, cause: unknown): Error {\n const message = `[${PLUGIN_NAME}] Failed to render \"${page.relativePath}\" at route \"${page.routePath}\"`;\n\n if (cause instanceof Error) {\n const err = new Error(`${message}: ${cause.message}`);\n\n if (cause.stack) {\n err.stack = `${err.stack}\\nCaused by:\\n${cause.stack}`;\n }\n\n return err;\n }\n\n return new Error(`${message}: ${String(cause)}`);\n}","import { invalidHtmlReturn, pageError, missingDefaultExport } from './errors';\nimport type { HtPageInfo, HtPageModule, HtPageRenderContext } from './types';\n\nexport async function renderPage(\n page: HtPageInfo,\n mod: HtPageModule,\n dev = false,\n): Promise<string> {\n const ctx: HtPageRenderContext = {\n page,\n params: page.params,\n dev,\n };\n\n try {\n if (typeof mod.data === 'function') {\n ctx.data = await mod.data(ctx);\n }\n\n const entry = mod.default;\n\n if (entry == null) {\n throw missingDefaultExport(page);\n }\n\n const html = typeof entry === 'function' ? await entry(ctx) : entry;\n\n if (typeof html !== 'string') {\n throw invalidHtmlReturn(page, html);\n }\n\n return html;\n } catch (error) {\n throw pageError(page, error);\n }\n}","import type { ViteDevServer } from 'vite';\nimport { renderPage } from './render-runtime';\nimport { routeMatch } from './route-utils';\nimport type { HtPageInfo, HtPageModule } from './types';\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages } = args;\n\n server.middlewares.use(async (req, res, next) => {\n try {\n if (!req.url || req.method !== 'GET') return next();\n\n const pathname = req.url.split('?')[0];\n const pages = await getPages();\n\n for (const page of pages) {\n const params = routeMatch(page.routePattern, pathname);\n if (!params) continue;\n\n const mod = (await server.ssrLoadModule(\n `/${page.relativePath}`,\n )) as HtPageModule;\n\n const resolvedPage = {\n ...page,\n routePath: pathname || '/',\n params,\n };\n\n const html = await renderPage(resolvedPage, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n });\n}","import {\n compareRoutePriority,\n expandStaticPaths,\n fileNameFromRoute,\n} from './route-utils';\nimport type { HtPageInfo, HtPageModule, StaticParamRecord } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport async function buildPageIndex(args: {\n entries: HtPageInfo[];\n modulesByEntry: Map<string, HtPageModule>;\n cleanUrls: boolean;\n}): Promise<HtPageInfo[]> {\n const { entries, modulesByEntry, cleanUrls } = args;\n const pages: HtPageInfo[] = [];\n\n for (const entry of entries) {\n const mod = modulesByEntry.get(entry.entryPath) ?? {};\n\n if (entry.dynamic) {\n const rows = mod.generateStaticParams\n ? await mod.generateStaticParams()\n : [];\n\n pages.push(\n ...expandStaticPaths(\n {\n id: entry.id,\n entryPath: entry.entryPath,\n absolutePath: entry.absolutePath,\n relativePath: entry.relativePath,\n routePattern: entry.routePattern,\n dynamic: entry.dynamic,\n paramNames: entry.paramNames,\n } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows as StaticParamRecord[],\n cleanUrls,\n ),\n );\n } else {\n pages.push({\n ...entry,\n routePath: entry.routePattern,\n fileName: fileNameFromRoute(entry.routePattern, cleanUrls),\n params: {},\n });\n }\n }\n\n pages.sort((a, b) => compareRoutePriority(a.routePattern, b.routePattern));\n\n const seenRoutes = new Map<string, HtPageInfo>();\n\n for (const page of pages) {\n const existing = seenRoutes.get(page.routePath);\n\n if (existing) {\n throw new Error(\n `[${PLUGIN_NAME}] Duplicate route generated: \"${page.routePath}\" from \"${existing.relativePath}\" and \"${page.relativePath}\"`,\n );\n }\n\n seenRoutes.set(page.routePath, page);\n }\n\n return pages;\n}","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { rollup, type Plugin as RollupPlugin } from 'rollup';\nimport { nodeResolve } from '@rollup/plugin-node-resolve';\nimport { createManifestModule } from './manifest';\nimport type { HtPageInfo } from './types';\nimport { PLUGIN_NAME, VIRTUAL_MANIFEST_ID } from './constants';\n\n\nexport async function buildRenderBundle(args: {\n entries: HtPageInfo[];\n cacheDir: string;\n ssrPlugins?: RollupPlugin[];\n}): Promise<string> {\n const { entries, cacheDir, ssrPlugins = [] } = args;\n\n const source = createManifestModule(entries);\n const hash = createHash('sha256').update(source).digest('hex').slice(0, 12);\n const bundlePath = path.join(cacheDir, `render-${hash}.mjs`);\n\n await fs.mkdir(cacheDir, { recursive: true });\n\n try {\n await fs.access(bundlePath);\n return bundlePath;\n } catch {\n // cache miss, continue\n }\n\n const bundle = await rollup({\n input: VIRTUAL_MANIFEST_ID,\n plugins: [\n {\n name: `${PLUGIN_NAME}:virtual-manifest`,\n resolveId(id) {\n return id === VIRTUAL_MANIFEST_ID ? id : null;\n },\n load(id) {\n return id === VIRTUAL_MANIFEST_ID ? source : null;\n },\n },\n nodeResolve({\n preferBuiltins: true,\n exportConditions: ['node'],\n }),\n ...ssrPlugins,\n ],\n treeshake: true,\n });\n\n try {\n const { output } = await bundle.generate({\n format: 'esm',\n exports: 'named',\n inlineDynamicImports: true,\n });\n\n const chunk = output.find((item) => item.type === 'chunk');\n\n if (!chunk || chunk.type !== 'chunk') {\n throw new Error(`[${PLUGIN_NAME}] Failed to generate HT.js pages render bundle.`);\n }\n\n await fs.writeFile(bundlePath, chunk.code, 'utf8');\n return bundlePath;\n } finally {\n await bundle.close();\n }\n}","import type { HtPageInfo } from './types';\n\nfunction js(value: unknown): string {\n return JSON.stringify(value);\n}\n\nexport function createManifestModule(entries: HtPageInfo[]): string {\n const imports = entries\n .map((page, i) => `import * as page${i} from ${js(page.entryPath)};`)\n .join('\\n');\n\n const records = entries\n .map(\n (page, i) => `{\n page: ${js(page)},\n mod: page${i}\n}`,\n )\n .join(',\\n');\n\n return `${imports}\n\nexport const manifest = [\n${records}\n];\n`;\n}"],"mappings":"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","names":["path","createHash","path","path","path","path","createHash","path"]}
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/discover.ts","../src/path-utils.ts","../src/route-utils.ts","../src/constants.ts","../src/errors.ts","../src/render-runtime.ts","../src/dev-server.ts","../src/page-index.ts","../src/render-bundle.ts","../src/manifest.ts"],"sourcesContent":["import path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { createHash } from 'node:crypto';\nimport pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { buildPageIndex } from './page-index';\nimport { buildRenderBundle } from './render-bundle';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID, CACHE_DIR_NAME } from './constants';\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const safeSize = Math.max(1, Math.floor(size));\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += safeSize) {\n out.push(items.slice(i, i + safeSize));\n }\n return out;\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\nfunction createEntriesKey(entries: HtPageInfo[]): string {\n const raw = entries\n .map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)\n .join('\\n');\n\n return createHash('sha256').update(raw).digest('hex');\n}\n\nasync function importManifest(\n bundlePath: string,\n): Promise<Array<{ page: HtPageInfo; mod: HtPageModule }>> {\n const mod = await import(pathToFileURL(bundlePath).href + `?t=${Date.now()}`);\n return mod.manifest as Array<{ page: HtPageInfo; mod: HtPageModule }>;\n}\n\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n\n let cachedManifestKey: string | null = null;\n let cachedBundlePath: string | null = null;\n let loadDevPagesInFlight: Promise<HtPageInfo[]> | null = null;\n\n const cleanUrls = options.cleanUrls ?? true;\n\n function logDebug(enabled: boolean | undefined, ...args: unknown[]) {\n if (!enabled) return;\n console.log(`[${PLUGIN_NAME}]`, ...args);\n }\n\n async function loadDevPages(): Promise<HtPageInfo[]> {\n if (loadDevPagesInFlight) return loadDevPagesInFlight;\n loadDevPagesInFlight = doLoadDevPages();\n try {\n return await loadDevPagesInFlight;\n } finally {\n loadDevPagesInFlight = null;\n }\n }\n\n async function doLoadDevPages(): Promise<HtPageInfo[]> {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n logDebug(options.debug, 'discovered entries', entries.map((e) => e.relativePath));\n\n if (!server) return [];\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n devPages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n logDebug(\n options.debug,\n 'dev pages',\n devPages.map((p) => `${p.routePath} -> ${p.relativePath}`),\n );\n\n return devPages;\n }\n\n async function buildPagesPipeline() {\n const entries = await discoverEntryPages(root, options);\n const cacheDir = path.join(root, CACHE_DIR_NAME);\n\n const entriesKey = createEntriesKey(entries);\n\n let bundlePath: string;\n if (cachedBundlePath && cachedManifestKey === entriesKey) {\n bundlePath = cachedBundlePath;\n } else {\n bundlePath = await buildRenderBundle({\n entries,\n cacheDir,\n ssrPlugins: options.ssrPlugins,\n });\n cachedManifestKey = entriesKey;\n cachedBundlePath = bundlePath;\n }\n\n logDebug(options.debug, 'render bundle', bundlePath);\n\n const manifest = await importManifest(bundlePath);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n for (const rec of manifest) {\n modulesByEntry.set(rec.page.entryPath, rec.mod);\n }\n\n const pages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n // Ensure static hosts get a 404.html\n const notFoundPage = pages.find((p) => p.routePath === '/404');\n\n if (notFoundPage && !pages.some((p) => p.fileName === '404.html')) {\n pages.push({\n ...notFoundPage,\n fileName: '404.html',\n });\n } \n\n return { entries, bundlePath, modulesByEntry, pages };\n }\n\n return {\n name: PLUGIN_NAME,\n\n config(userConfig, env) {\n if (env.command !== 'build') return;\n\n const hasExplicitInput = userConfig.build?.rollupOptions?.input != null;\n if (hasExplicitInput) return;\n\n return {\n build: {\n rollupOptions: {\n input: VIRTUAL_BUILD_ENTRY_ID,\n },\n },\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) return id;\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) {\n return 'export default {};';\n }\n return null;\n },\n\n configResolved(resolved) {\n root = resolved.root;\n },\n\n async buildStart() {\n const entries = await discoverEntryPages(root, options);\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n },\n\n configureServer(_server) {\n server = _server;\n\n installDevServer({\n server,\n getPages: async () => {\n if (devPages.length > 0) return devPages;\n return loadDevPages();\n },\n });\n\n loadDevPages().catch((error) => {\n server?.config.logger.error(\n `[${PLUGIN_NAME}] loadDevPages failed: ${\n error instanceof Error ? error.stack ?? error.message : String(error)\n }`,\n );\n });\n },\n\n async handleHotUpdate(ctx) {\n if (!server) return;\n \n const file = ctx.file;\n \n if (\n file.endsWith('.ht.js') ||\n file.includes('/templates/')\n ) {\n logDebug(options.debug, 'reindex triggered by', file);\n await loadDevPages();\n }\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n \n logDebug(options.debug, 'emitting pages', pages.map((p) => p.fileName));\n \n const concurrency = Math.max(1, options.renderConcurrency ?? 8);\n const limit = pLimit(concurrency);\n const batchSize = Math.max(\n 1,\n options.renderBatchSize ?? Math.max(concurrency, 32),\n );\n \n for (const batch of chunkArray(pages, batchSize)) {\n await Promise.all(\n batch.map((page) =>\n limit(async () => {\n const mod = modulesByEntry.get(page.entryPath);\n \n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,\n );\n }\n \n const html = await renderPage(page, mod, false);\n \n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n \n // Generate sitemap.xml\n const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))]\n .filter((route) => !route.includes(':') && !route.includes('*'));\n \n if (sitemapRoutes.length > 0) {\n const sitemap = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n ${sitemapRoutes\n .map(\n (route) =>\n ` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`,\n )\n .join('\\n')}\n </urlset>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n });\n }\n \n // Generate rss.xml\n if (options.rss?.site) {\n const routePrefix = options.rss.routePrefix ?? '/blog';\n \n const rssItems = pages\n .filter((page) => page.routePath.startsWith(routePrefix))\n .map((page) => {\n const url = `${options.rss!.site}${page.routePath}`;\n return ` <item>\n <title>${escapeXml(page.routePath)}</title>\n <link>${escapeXml(url)}</link>\n <guid>${escapeXml(url)}</guid>\n </item>`;\n })\n .join('\\n');\n \n const rss = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <rss version=\"2.0\">\n <channel>\n <title>${escapeXml(options.rss.title ?? PLUGIN_NAME)}</title>\n <link>${escapeXml(options.rss.site)}</link>\n <description>${escapeXml(options.rss.description ?? 'RSS feed')}</description>\n ${rssItems}\n </channel>\n </rss>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'rss.xml',\n source: rss,\n });\n }\n \n // Remove the dummy virtual build entry chunk\n for (const [fileName, output] of Object.entries(bundle)) {\n if (\n output.type === 'chunk' &&\n output.facadeModuleId === VIRTUAL_BUILD_ENTRY_ID\n ) {\n delete bundle[fileName];\n }\n }\n } \n\n };\n}\n","import path from 'node:path';\nimport fg from 'fast-glob';\nimport { normalizeFsPath, toPosix } from './path-utils';\nimport { getParamNames, isDynamicPage, toRoutePattern } from './route-utils';\nimport type { HtPageInfo, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME } from './constants';\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const rawInclude = Array.isArray(options.include)\n ? options.include\n : [options.include ?? 'src/**/*.ht.js'];\n let include = rawInclude.filter((p): p is string => typeof p === 'string' && p.length > 0);\n if (include.length === 0) {\n include = ['src/**/*.ht.js'];\n }\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesDir = options.pagesDir ?? 'src';\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg(include, {\n cwd: root,\n ignore: exclude,\n absolute: true,\n });\n\n return files\n .sort()\n .map((absolutePath) => {\n const entryPath = normalizeFsPath(absolutePath);\n const relativePath = toPosix(path.relative(root, entryPath));\n const relativeFromPagesDir = toPosix(path.relative(pagesRoot, entryPath));\n\n if (\n relativeFromPagesDir.startsWith('../') ||\n relativeFromPagesDir === '..'\n ) {\n throw new Error(\n `[${PLUGIN_NAME}] Page is outside pagesDir: ${entryPath} (pagesDir: ${pagesDir})`,\n );\n }\n\n const dynamic = isDynamicPage(relativeFromPagesDir);\n const routePattern = toRoutePattern(relativeFromPagesDir);\n\n return {\n id: entryPath,\n entryPath,\n absolutePath: entryPath,\n relativePath,\n routePattern,\n routePath: routePattern,\n fileName: '',\n dynamic,\n paramNames: getParamNames(relativeFromPagesDir),\n params: {},\n } satisfies HtPageInfo;\n });\n}","import path from 'node:path';\n\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\nexport function stripHtSuffix(file: string): string {\n return file.replace(/\\.ht\\.js$/i, '');\n}\n\nexport function normalizeRoutePath(p: string): string {\n let out = p.startsWith('/') ? p : `/${p}`;\n out = out.replace(/\\/+/g, '/');\n if (out !== '/' && out.endsWith('/')) out = out.slice(0, -1);\n return out;\n}\n\nexport function normalizeFsPath(p: string): string {\n return toPosix(path.resolve(p));\n}","import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';\nimport type { HtPageInfo, StaticParamRecord } from './types';\n\nfunction safeDecodeURIComponent(str: string): string {\n try {\n return decodeURIComponent(str);\n } catch {\n return str;\n }\n}\n\nconst DYNAMIC_SEGMENT_RE = /\\[([A-Za-z0-9_]+)\\]/g;\nconst CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]/g;\nconst OPTIONAL_CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]\\?/g;\nconst ANY_PARAM_RE = /\\[(?:\\.\\.\\.)?([A-Za-z0-9_]+)\\]\\??/g;\nconst ROUTE_GROUP_RE = /(^|\\/)\\(([^)]+)\\)(?=\\/|$)/g;\n\nexport function getParamNames(relativeFromPagesDir: string): string[] {\n return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);\n}\n\nexport function isDynamicPage(relativeFromPagesDir: string): boolean {\n return /\\[(?:\\.\\.\\.)?[A-Za-z0-9_]+\\]\\??/.test(relativeFromPagesDir);\n}\n\nexport function toRoutePattern(relativeFromPagesDir: string): string {\n const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));\n\n const withoutGroups = noExt.replace(ROUTE_GROUP_RE, '$1');\n const withoutIndex = withoutGroups.replace(/\\/index$/i, '').replace(/^index$/i, '');\n\n const raw = withoutIndex\n .replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, '*?:$1')\n .replace(CATCH_ALL_SEGMENT_RE, '*:$1')\n .replace(DYNAMIC_SEGMENT_RE, ':$1');\n\n return normalizeRoutePath(raw || '/');\n}\n\nexport function fillParams(\n pattern: string,\n params: Record<string, string>,\n): string {\n const result = pattern\n .replace(/\\*\\?:([A-Za-z0-9_]+)/g, (_, key) => {\n const value = params[key];\n if (value == null || value === '') {\n return '';\n }\n\n return String(value)\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/\\*:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing catch-all route param \"${key}\"`);\n }\n\n return String(params[key])\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing route param \"${key}\"`);\n }\n\n return encodeURIComponent(params[key]);\n });\n\n return normalizeRoutePath(result || '/');\n}\n\nexport function fileNameFromRoute(\n routePath: string,\n cleanUrls: boolean,\n): string {\n const normalized = normalizeRoutePath(routePath);\n\n if (normalized === '/') return 'index.html';\n\n const base = normalized.slice(1);\n return cleanUrls ? `${base}/index.html` : `${base}.html`;\n}\n\nexport function expandStaticPaths(\n basePage: Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows: StaticParamRecord[],\n cleanUrls: boolean,\n): HtPageInfo[] {\n return rows.map((row) => {\n const params = Object.fromEntries(\n Object.entries(row).map(([k, v]) => [k, String(v)]),\n );\n\n const routePath = fillParams(basePage.routePattern, params);\n\n return {\n ...basePage,\n routePath,\n fileName: fileNameFromRoute(routePath, cleanUrls),\n params,\n };\n });\n}\n\nexport function routeMatch(\n pattern: string,\n urlPath: string,\n): Record<string, string> | null {\n const a = normalizeRoutePath(pattern).split('/').filter(Boolean);\n const b = normalizeRoutePath(urlPath).split('/').filter(Boolean);\n const params: Record<string, string> = {};\n\n for (let i = 0; i < a.length; i++) {\n const patternSeg = a[i];\n const urlSeg = b[i];\n\n if (patternSeg.startsWith('*?:')) {\n params[patternSeg.slice(3)] =\n i < b.length ? b.slice(i).map(safeDecodeURIComponent).join('/') : '';\n return params;\n }\n\n if (patternSeg.startsWith('*:')) {\n const rest = b.slice(i);\n if (rest.length === 0) return null;\n\n params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join('/');\n return params;\n }\n\n if (!urlSeg) return null;\n\n if (patternSeg.startsWith(':')) {\n params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);\n continue;\n }\n\n if (patternSeg !== urlSeg) return null;\n }\n\n return a.length === b.length ? params : null;\n}\n\nexport function compareRoutePriority(a: string, b: string): number {\n const aSegs = normalizeRoutePath(a).split('/').filter(Boolean);\n const bSegs = normalizeRoutePath(b).split('/').filter(Boolean);\n const len = Math.max(aSegs.length, bSegs.length);\n\n for (let i = 0; i < len; i++) {\n const aa = aSegs[i];\n const bb = bSegs[i];\n\n if (aa == null) return 1;\n if (bb == null) return -1;\n\n const aOptionalCatchAll = aa.startsWith('*?:');\n const bOptionalCatchAll = bb.startsWith('*?:');\n if (aOptionalCatchAll !== bOptionalCatchAll) {\n return aOptionalCatchAll ? 1 : -1;\n }\n\n const aCatchAll = aa.startsWith('*:');\n const bCatchAll = bb.startsWith('*:');\n if (aCatchAll !== bCatchAll) {\n return aCatchAll ? 1 : -1;\n }\n\n const aDynamic = aa.startsWith(':');\n const bDynamic = bb.startsWith(':');\n if (aDynamic !== bDynamic) {\n return aDynamic ? 1 : -1;\n }\n }\n\n // More specific / longer routes first when otherwise equal\n return bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-htjs-pages';\nexport const VIRTUAL_BUILD_ENTRY_ID = `\\0${PLUGIN_NAME}:build-entry`;\nexport const VIRTUAL_MANIFEST_ID = `\\0virtual:${PLUGIN_NAME}-manifest`;\nexport const CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;","import type { HtPageInfo } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport function invalidHtmlReturn(\n page: HtPageInfo,\n value: unknown,\n): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" must resolve to an HTML string, got ${typeof value}`,\n );\n}\n\nexport function missingDefaultExport(page: HtPageInfo): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" does not export a default renderer`,\n );\n}\n\nexport function pageError(page: HtPageInfo, cause: unknown): Error {\n const message = `[${PLUGIN_NAME}] Failed to render \"${page.relativePath}\" at route \"${page.routePath}\"`;\n\n if (cause instanceof Error) {\n const err = new Error(`${message}: ${cause.message}`);\n\n if (cause.stack) {\n err.stack = `${err.stack}\\nCaused by:\\n${cause.stack}`;\n }\n\n return err;\n }\n\n return new Error(`${message}: ${String(cause)}`);\n}","import { invalidHtmlReturn, pageError, missingDefaultExport } from './errors';\nimport type { HtPageInfo, HtPageModule, HtPageRenderContext } from './types';\n\nexport async function renderPage(\n page: HtPageInfo,\n mod: HtPageModule,\n dev = false,\n): Promise<string> {\n const ctx: HtPageRenderContext = {\n page,\n params: page.params,\n dev,\n };\n\n try {\n if (typeof mod.data === 'function') {\n ctx.data = await mod.data(ctx);\n }\n\n const entry = mod.default;\n\n if (entry == null) {\n throw missingDefaultExport(page);\n }\n\n const html = typeof entry === 'function' ? await entry(ctx) : entry;\n\n if (typeof html !== 'string') {\n throw invalidHtmlReturn(page, html);\n }\n\n return html;\n } catch (error) {\n throw pageError(page, error);\n }\n}","import type { ViteDevServer } from 'vite';\nimport { renderPage } from './render-runtime';\nimport { routeMatch } from './route-utils';\nimport type { HtPageInfo, HtPageModule } from './types';\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages } = args;\n\n server.middlewares.use(async (req, res, next) => {\n try {\n if (!req.url || req.method !== 'GET') return next();\n\n const pathname = req.url.split('?')[0];\n const pages = await getPages();\n\n for (const page of pages) {\n const params = routeMatch(page.routePattern, pathname);\n if (!params) continue;\n\n const mod = (await server.ssrLoadModule(\n `/${page.relativePath}`,\n )) as HtPageModule;\n\n const resolvedPage = {\n ...page,\n routePath: pathname || '/',\n params,\n };\n\n const html = await renderPage(resolvedPage, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n });\n}","import {\n compareRoutePriority,\n expandStaticPaths,\n fileNameFromRoute,\n} from './route-utils';\nimport type { HtPageInfo, HtPageModule, StaticParamRecord } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport async function buildPageIndex(args: {\n entries: HtPageInfo[];\n modulesByEntry: Map<string, HtPageModule>;\n cleanUrls: boolean;\n}): Promise<HtPageInfo[]> {\n const { entries, modulesByEntry, cleanUrls } = args;\n const pages: HtPageInfo[] = [];\n\n for (const entry of entries) {\n const mod = modulesByEntry.get(entry.entryPath) ?? {};\n\n if (entry.dynamic) {\n const rows =\n (mod.generateStaticParams\n ? await mod.generateStaticParams()\n : []) ?? [];\n\n pages.push(\n ...expandStaticPaths(\n {\n id: entry.id,\n entryPath: entry.entryPath,\n absolutePath: entry.absolutePath,\n relativePath: entry.relativePath,\n routePattern: entry.routePattern,\n dynamic: entry.dynamic,\n paramNames: entry.paramNames,\n } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n Array.isArray(rows) ? rows : [],\n cleanUrls,\n ),\n );\n } else {\n pages.push({\n ...entry,\n routePath: entry.routePattern,\n fileName: fileNameFromRoute(entry.routePattern, cleanUrls),\n params: {},\n });\n }\n }\n\n pages.sort((a, b) => compareRoutePriority(a.routePattern, b.routePattern));\n\n const seenRoutes = new Map<string, HtPageInfo>();\n\n for (const page of pages) {\n const existing = seenRoutes.get(page.routePath);\n\n if (existing) {\n throw new Error(\n `[${PLUGIN_NAME}] Duplicate route generated: \"${page.routePath}\" from \"${existing.relativePath}\" and \"${page.relativePath}\"`,\n );\n }\n\n seenRoutes.set(page.routePath, page);\n }\n\n return pages;\n}","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { rollup, type Plugin as RollupPlugin } from 'rollup';\nimport { nodeResolve } from '@rollup/plugin-node-resolve';\nimport { createManifestModule } from './manifest';\nimport type { HtPageInfo } from './types';\nimport { PLUGIN_NAME, VIRTUAL_MANIFEST_ID } from './constants';\n\n\nexport async function buildRenderBundle(args: {\n entries: HtPageInfo[];\n cacheDir: string;\n ssrPlugins?: RollupPlugin[];\n}): Promise<string> {\n const { entries, cacheDir, ssrPlugins = [] } = args;\n\n const source = createManifestModule(entries);\n const hash = createHash('sha256').update(source).digest('hex').slice(0, 12);\n const bundlePath = path.join(cacheDir, `render-${hash}.mjs`);\n\n await fs.mkdir(cacheDir, { recursive: true });\n\n try {\n await fs.access(bundlePath);\n return bundlePath;\n } catch {\n // cache miss, continue\n }\n\n const bundle = await rollup({\n input: VIRTUAL_MANIFEST_ID,\n plugins: [\n {\n name: `${PLUGIN_NAME}:virtual-manifest`,\n resolveId(id) {\n return id === VIRTUAL_MANIFEST_ID ? id : null;\n },\n load(id) {\n return id === VIRTUAL_MANIFEST_ID ? source : null;\n },\n },\n nodeResolve({\n preferBuiltins: true,\n exportConditions: ['node'],\n }),\n ...ssrPlugins,\n ],\n treeshake: true,\n });\n\n try {\n const { output } = await bundle.generate({\n format: 'esm',\n exports: 'named',\n inlineDynamicImports: true,\n });\n\n const chunk = output.find((item) => item.type === 'chunk');\n\n if (!chunk || chunk.type !== 'chunk') {\n throw new Error(`[${PLUGIN_NAME}] Failed to generate HT.js pages render bundle.`);\n }\n\n await fs.writeFile(bundlePath, chunk.code, 'utf8');\n return bundlePath;\n } finally {\n await bundle.close();\n }\n}","import type { HtPageInfo } from './types';\n\nfunction js(value: unknown): string {\n return JSON.stringify(value);\n}\n\nexport function createManifestModule(entries: HtPageInfo[]): string {\n const imports = entries\n .map((page, i) => `import * as page${i} from ${js(page.entryPath)};`)\n .join('\\n');\n\n const records = entries\n .map(\n (page, i) => `{\n page: ${js(page)},\n mod: page${i}\n}`,\n )\n .join(',\\n');\n\n return `${imports}\n\nexport const manifest = [\n${records}\n];\n`;\n}"],"mappings":"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","names":["path","createHash","path","path","path","path","createHash","path"]}
|
package/package.json
CHANGED
package/src/discover.ts
CHANGED
|
@@ -9,9 +9,13 @@ export async function discoverEntryPages(
|
|
|
9
9
|
root: string,
|
|
10
10
|
options: HtPagesPluginOptions,
|
|
11
11
|
): Promise<HtPageInfo[]> {
|
|
12
|
-
const
|
|
12
|
+
const rawInclude = Array.isArray(options.include)
|
|
13
13
|
? options.include
|
|
14
14
|
: [options.include ?? 'src/**/*.ht.js'];
|
|
15
|
+
let include = rawInclude.filter((p): p is string => typeof p === 'string' && p.length > 0);
|
|
16
|
+
if (include.length === 0) {
|
|
17
|
+
include = ['src/**/*.ht.js'];
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
const exclude = Array.isArray(options.exclude)
|
|
17
21
|
? options.exclude
|
package/src/page-index.ts
CHANGED
|
@@ -17,9 +17,10 @@ export async function buildPageIndex(args: {
|
|
|
17
17
|
const mod = modulesByEntry.get(entry.entryPath) ?? {};
|
|
18
18
|
|
|
19
19
|
if (entry.dynamic) {
|
|
20
|
-
const rows =
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const rows =
|
|
21
|
+
(mod.generateStaticParams
|
|
22
|
+
? await mod.generateStaticParams()
|
|
23
|
+
: []) ?? [];
|
|
23
24
|
|
|
24
25
|
pages.push(
|
|
25
26
|
...expandStaticPaths(
|
|
@@ -32,7 +33,7 @@ export async function buildPageIndex(args: {
|
|
|
32
33
|
dynamic: entry.dynamic,
|
|
33
34
|
paramNames: entry.paramNames,
|
|
34
35
|
} as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,
|
|
35
|
-
rows
|
|
36
|
+
Array.isArray(rows) ? rows : [],
|
|
36
37
|
cleanUrls,
|
|
37
38
|
),
|
|
38
39
|
);
|
package/src/plugin.ts
CHANGED
|
@@ -14,13 +14,23 @@ import type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';
|
|
|
14
14
|
import { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID, CACHE_DIR_NAME } from './constants';
|
|
15
15
|
|
|
16
16
|
function chunkArray<T>(items: T[], size: number): T[][] {
|
|
17
|
+
const safeSize = Math.max(1, Math.floor(size));
|
|
17
18
|
const out: T[][] = [];
|
|
18
|
-
for (let i = 0; i < items.length; i +=
|
|
19
|
-
out.push(items.slice(i, i +
|
|
19
|
+
for (let i = 0; i < items.length; i += safeSize) {
|
|
20
|
+
out.push(items.slice(i, i + safeSize));
|
|
20
21
|
}
|
|
21
22
|
return out;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
function escapeXml(text: string): string {
|
|
26
|
+
return text
|
|
27
|
+
.replace(/&/g, '&')
|
|
28
|
+
.replace(/</g, '<')
|
|
29
|
+
.replace(/>/g, '>')
|
|
30
|
+
.replace(/"/g, '"')
|
|
31
|
+
.replace(/'/g, ''');
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
function createEntriesKey(entries: HtPageInfo[]): string {
|
|
25
35
|
const raw = entries
|
|
26
36
|
.map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)
|
|
@@ -43,6 +53,7 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
43
53
|
|
|
44
54
|
let cachedManifestKey: string | null = null;
|
|
45
55
|
let cachedBundlePath: string | null = null;
|
|
56
|
+
let loadDevPagesInFlight: Promise<HtPageInfo[]> | null = null;
|
|
46
57
|
|
|
47
58
|
const cleanUrls = options.cleanUrls ?? true;
|
|
48
59
|
|
|
@@ -52,6 +63,16 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
async function loadDevPages(): Promise<HtPageInfo[]> {
|
|
66
|
+
if (loadDevPagesInFlight) return loadDevPagesInFlight;
|
|
67
|
+
loadDevPagesInFlight = doLoadDevPages();
|
|
68
|
+
try {
|
|
69
|
+
return await loadDevPagesInFlight;
|
|
70
|
+
} finally {
|
|
71
|
+
loadDevPagesInFlight = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function doLoadDevPages(): Promise<HtPageInfo[]> {
|
|
55
76
|
const entries = await discoverEntryPages(root, options);
|
|
56
77
|
const modulesByEntry = new Map<string, HtPageModule>();
|
|
57
78
|
|
|
@@ -210,10 +231,12 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
210
231
|
|
|
211
232
|
logDebug(options.debug, 'emitting pages', pages.map((p) => p.fileName));
|
|
212
233
|
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
const concurrency = Math.max(1, options.renderConcurrency ?? 8);
|
|
235
|
+
const limit = pLimit(concurrency);
|
|
236
|
+
const batchSize = Math.max(
|
|
237
|
+
1,
|
|
238
|
+
options.renderBatchSize ?? Math.max(concurrency, 32),
|
|
239
|
+
);
|
|
217
240
|
|
|
218
241
|
for (const batch of chunkArray(pages, batchSize)) {
|
|
219
242
|
await Promise.all(
|
|
@@ -248,7 +271,10 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
248
271
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
249
272
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
250
273
|
${sitemapRoutes
|
|
251
|
-
.map(
|
|
274
|
+
.map(
|
|
275
|
+
(route) =>
|
|
276
|
+
` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`,
|
|
277
|
+
)
|
|
252
278
|
.join('\n')}
|
|
253
279
|
</urlset>
|
|
254
280
|
`;
|
|
@@ -269,9 +295,9 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
269
295
|
.map((page) => {
|
|
270
296
|
const url = `${options.rss!.site}${page.routePath}`;
|
|
271
297
|
return ` <item>
|
|
272
|
-
<title>${page.routePath}</title>
|
|
273
|
-
<link>${url}</link>
|
|
274
|
-
<guid>${url}</guid>
|
|
298
|
+
<title>${escapeXml(page.routePath)}</title>
|
|
299
|
+
<link>${escapeXml(url)}</link>
|
|
300
|
+
<guid>${escapeXml(url)}</guid>
|
|
275
301
|
</item>`;
|
|
276
302
|
})
|
|
277
303
|
.join('\n');
|
|
@@ -279,9 +305,9 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
279
305
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
|
280
306
|
<rss version="2.0">
|
|
281
307
|
<channel>
|
|
282
|
-
<title>${options.rss.title ?? PLUGIN_NAME}</title>
|
|
283
|
-
<link>${options.rss.site}</link>
|
|
284
|
-
<description>${options.rss.description ?? 'RSS feed'}</description>
|
|
308
|
+
<title>${escapeXml(options.rss.title ?? PLUGIN_NAME)}</title>
|
|
309
|
+
<link>${escapeXml(options.rss.site)}</link>
|
|
310
|
+
<description>${escapeXml(options.rss.description ?? 'RSS feed')}</description>
|
|
285
311
|
${rssItems}
|
|
286
312
|
</channel>
|
|
287
313
|
</rss>
|
package/src/route-utils.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';
|
|
2
2
|
import type { HtPageInfo, StaticParamRecord } from './types';
|
|
3
3
|
|
|
4
|
+
function safeDecodeURIComponent(str: string): string {
|
|
5
|
+
try {
|
|
6
|
+
return decodeURIComponent(str);
|
|
7
|
+
} catch {
|
|
8
|
+
return str;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
const DYNAMIC_SEGMENT_RE = /\[([A-Za-z0-9_]+)\]/g;
|
|
5
13
|
const CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]/g;
|
|
6
14
|
const OPTIONAL_CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]\?/g;
|
|
7
15
|
const ANY_PARAM_RE = /\[(?:\.\.\.)?([A-Za-z0-9_]+)\]\??/g;
|
|
16
|
+
const ROUTE_GROUP_RE = /(^|\/)\(([^)]+)\)(?=\/|$)/g;
|
|
8
17
|
|
|
9
18
|
export function getParamNames(relativeFromPagesDir: string): string[] {
|
|
10
19
|
return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);
|
|
@@ -17,8 +26,10 @@ export function isDynamicPage(relativeFromPagesDir: string): boolean {
|
|
|
17
26
|
export function toRoutePattern(relativeFromPagesDir: string): string {
|
|
18
27
|
const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));
|
|
19
28
|
|
|
20
|
-
const
|
|
21
|
-
|
|
29
|
+
const withoutGroups = noExt.replace(ROUTE_GROUP_RE, '$1');
|
|
30
|
+
const withoutIndex = withoutGroups.replace(/\/index$/i, '').replace(/^index$/i, '');
|
|
31
|
+
|
|
32
|
+
const raw = withoutIndex
|
|
22
33
|
.replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, '*?:$1')
|
|
23
34
|
.replace(CATCH_ALL_SEGMENT_RE, '*:$1')
|
|
24
35
|
.replace(DYNAMIC_SEGMENT_RE, ':$1');
|
|
@@ -30,10 +41,9 @@ export function fillParams(
|
|
|
30
41
|
pattern: string,
|
|
31
42
|
params: Record<string, string>,
|
|
32
43
|
): string {
|
|
33
|
-
|
|
44
|
+
const result = pattern
|
|
34
45
|
.replace(/\*\?:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
35
46
|
const value = params[key];
|
|
36
|
-
|
|
37
47
|
if (value == null || value === '') {
|
|
38
48
|
return '';
|
|
39
49
|
}
|
|
@@ -59,9 +69,9 @@ export function fillParams(
|
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
return encodeURIComponent(params[key]);
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return normalizeRoutePath(result || '/');
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
export function fileNameFromRoute(
|
|
@@ -86,9 +96,7 @@ export function expandStaticPaths(
|
|
|
86
96
|
Object.entries(row).map(([k, v]) => [k, String(v)]),
|
|
87
97
|
);
|
|
88
98
|
|
|
89
|
-
const routePath =
|
|
90
|
-
fillParams(basePage.routePattern, params),
|
|
91
|
-
);
|
|
99
|
+
const routePath = fillParams(basePage.routePattern, params);
|
|
92
100
|
|
|
93
101
|
return {
|
|
94
102
|
...basePage,
|
|
@@ -113,25 +121,22 @@ export function routeMatch(
|
|
|
113
121
|
|
|
114
122
|
if (patternSeg.startsWith('*?:')) {
|
|
115
123
|
params[patternSeg.slice(3)] =
|
|
116
|
-
i < b.length ? b.slice(i).map(
|
|
124
|
+
i < b.length ? b.slice(i).map(safeDecodeURIComponent).join('/') : '';
|
|
117
125
|
return params;
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
if (patternSeg.startsWith('*:')) {
|
|
121
129
|
const rest = b.slice(i);
|
|
130
|
+
if (rest.length === 0) return null;
|
|
122
131
|
|
|
123
|
-
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
params[patternSeg.slice(2)] = rest.map(decodeURIComponent).join('/');
|
|
132
|
+
params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join('/');
|
|
128
133
|
return params;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
if (!urlSeg) return null;
|
|
132
137
|
|
|
133
138
|
if (patternSeg.startsWith(':')) {
|
|
134
|
-
params[patternSeg.slice(1)] =
|
|
139
|
+
params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);
|
|
135
140
|
continue;
|
|
136
141
|
}
|
|
137
142
|
|
|
@@ -172,5 +177,6 @@ export function compareRoutePriority(a: string, b: string): number {
|
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
// More specific / longer routes first when otherwise equal
|
|
181
|
+
return bSegs.length - aSegs.length;
|
|
176
182
|
}
|