vite-plugin-htjs-pages 1.2.1 → 1.3.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/TODO +16 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +145 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dev-server.ts +34 -9
- package/src/fetch-cache.ts +92 -0
- package/src/index.ts +5 -1
- package/src/plugin.ts +56 -122
- package/src/render-bundle.ts +24 -6
- package/src/types.ts +2 -0
package/TODO
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
- caching for API fetching inside generateStaticParams() and data() like getCachedData.js
|
|
2
|
-
- `export const dynamic = true` to disable static generation for specific routes
|
|
2
|
+
- `export const dynamic = true` to disable static generation for specific routes
|
|
3
|
+
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
const fetchWithCache = async (url, options = {}) => {
|
|
6
|
+
|
|
7
|
+
// try `node_modules/.cache/${cacheKey}`
|
|
8
|
+
|
|
9
|
+
const cacheKey = `${url}-${JSON.stringify(options)}`
|
|
10
|
+
if (cache.has(cacheKey)) {
|
|
11
|
+
return cache.get(cacheKey)
|
|
12
|
+
}
|
|
13
|
+
const response = await fetch(url, options)
|
|
14
|
+
const json = await response.json()
|
|
15
|
+
cache.set(cacheKey, json)
|
|
16
|
+
return json
|
|
17
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ interface HtPageModule {
|
|
|
26
26
|
default?: string | ((ctx: HtPageRenderContext) => string | Promise<string>);
|
|
27
27
|
data?: (ctx: HtPageRenderContext) => unknown | Promise<unknown>;
|
|
28
28
|
generateStaticParams?: () => StaticParamRecord[] | Promise<StaticParamRecord[]>;
|
|
29
|
+
dynamic?: boolean;
|
|
30
|
+
prerender?: boolean;
|
|
29
31
|
}
|
|
30
32
|
interface HtPagesPluginOptions {
|
|
31
33
|
root?: string;
|
|
@@ -49,4 +51,11 @@ interface HtPagesPluginOptions {
|
|
|
49
51
|
|
|
50
52
|
declare function htPages(options?: HtPagesPluginOptions): Plugin$1;
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
interface FetchAndCacheOptions {
|
|
55
|
+
maxAge?: number;
|
|
56
|
+
cacheKey?: string;
|
|
57
|
+
forceRefresh?: boolean;
|
|
58
|
+
}
|
|
59
|
+
declare function fetchAndCache(input: RequestInfo | URL, init?: RequestInit, options?: FetchAndCacheOptions): Promise<Response>;
|
|
60
|
+
|
|
61
|
+
export { type FetchAndCacheOptions, type HtPageInfo, type HtPageModule, type HtPageRenderContext, type HtPagesPluginOptions, type StaticParamRecord, fetchAndCache, htPages };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/plugin.ts
|
|
2
2
|
import path4 from "path";
|
|
3
3
|
import { pathToFileURL } from "url";
|
|
4
|
-
import { createHash as createHash2 } from "crypto";
|
|
5
4
|
import pLimit from "p-limit";
|
|
6
5
|
|
|
7
6
|
// src/discover.ts
|
|
@@ -244,25 +243,42 @@ async function renderPage(page, mod, dev = false) {
|
|
|
244
243
|
}
|
|
245
244
|
|
|
246
245
|
// src/dev-server.ts
|
|
246
|
+
function isDynamicOnly(mod) {
|
|
247
|
+
return mod.dynamic === true || mod.prerender === false;
|
|
248
|
+
}
|
|
247
249
|
function installDevServer(args) {
|
|
248
|
-
const { server, getPages } = args;
|
|
250
|
+
const { server, getPages, getEntries } = args;
|
|
249
251
|
server.middlewares.use(async (req, res, next) => {
|
|
250
252
|
try {
|
|
251
253
|
if (!req.url || req.method !== "GET") return next();
|
|
252
254
|
const pathname = req.url.split("?")[0];
|
|
253
255
|
const pages = await getPages();
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
const staticPage = pages.find((p) => p.routePath === pathname);
|
|
257
|
+
if (staticPage) {
|
|
258
|
+
const mod = await server.ssrLoadModule(
|
|
259
|
+
`/${staticPage.relativePath}`
|
|
260
|
+
);
|
|
261
|
+
const html = await renderPage(staticPage, mod, true);
|
|
262
|
+
res.statusCode = 200;
|
|
263
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
264
|
+
res.end(html);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const entries = await getEntries();
|
|
268
|
+
for (const entry of entries) {
|
|
257
269
|
const mod = await server.ssrLoadModule(
|
|
258
|
-
`/${
|
|
270
|
+
`/${entry.relativePath}`
|
|
259
271
|
);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
272
|
+
if (!isDynamicOnly(mod)) continue;
|
|
273
|
+
const params = routeMatch(entry.routePattern, pathname);
|
|
274
|
+
if (!params) continue;
|
|
275
|
+
const page = {
|
|
276
|
+
...entry,
|
|
277
|
+
routePath: pathname,
|
|
278
|
+
fileName: "",
|
|
263
279
|
params
|
|
264
280
|
};
|
|
265
|
-
const html = await renderPage(
|
|
281
|
+
const html = await renderPage(page, mod, true);
|
|
266
282
|
res.statusCode = 200;
|
|
267
283
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
268
284
|
res.end(html);
|
|
@@ -349,10 +365,20 @@ ${records}
|
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
// src/render-bundle.ts
|
|
368
|
+
async function createRenderBundleHash(entries, manifestSource) {
|
|
369
|
+
const hash = createHash("sha256");
|
|
370
|
+
hash.update(manifestSource);
|
|
371
|
+
for (const entry of entries) {
|
|
372
|
+
hash.update(entry.entryPath);
|
|
373
|
+
const source = await fs.readFile(entry.entryPath, "utf8");
|
|
374
|
+
hash.update(source);
|
|
375
|
+
}
|
|
376
|
+
return hash.digest("hex").slice(0, 12);
|
|
377
|
+
}
|
|
352
378
|
async function buildRenderBundle(args) {
|
|
353
379
|
const { entries, cacheDir, ssrPlugins = [] } = args;
|
|
354
|
-
const
|
|
355
|
-
const hash =
|
|
380
|
+
const manifestSource = createManifestModule(entries);
|
|
381
|
+
const hash = await createRenderBundleHash(entries, manifestSource);
|
|
356
382
|
const bundlePath = path3.join(cacheDir, `render-${hash}.mjs`);
|
|
357
383
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
358
384
|
try {
|
|
@@ -369,7 +395,7 @@ async function buildRenderBundle(args) {
|
|
|
369
395
|
return id === VIRTUAL_MANIFEST_ID ? id : null;
|
|
370
396
|
},
|
|
371
397
|
load(id) {
|
|
372
|
-
return id === VIRTUAL_MANIFEST_ID ?
|
|
398
|
+
return id === VIRTUAL_MANIFEST_ID ? manifestSource : null;
|
|
373
399
|
}
|
|
374
400
|
},
|
|
375
401
|
nodeResolve({
|
|
@@ -388,7 +414,9 @@ async function buildRenderBundle(args) {
|
|
|
388
414
|
});
|
|
389
415
|
const chunk = output.find((item) => item.type === "chunk");
|
|
390
416
|
if (!chunk || chunk.type !== "chunk") {
|
|
391
|
-
throw new Error(
|
|
417
|
+
throw new Error(
|
|
418
|
+
`[${PLUGIN_NAME}] Failed to generate HT pages render bundle.`
|
|
419
|
+
);
|
|
392
420
|
}
|
|
393
421
|
await fs.writeFile(bundlePath, chunk.code, "utf8");
|
|
394
422
|
return bundlePath;
|
|
@@ -399,20 +427,12 @@ async function buildRenderBundle(args) {
|
|
|
399
427
|
|
|
400
428
|
// src/plugin.ts
|
|
401
429
|
function chunkArray(items, size) {
|
|
402
|
-
const safeSize = Math.max(1, Math.floor(size));
|
|
403
430
|
const out = [];
|
|
404
|
-
for (let i = 0; i < items.length; i +=
|
|
405
|
-
out.push(items.slice(i, i +
|
|
431
|
+
for (let i = 0; i < items.length; i += size) {
|
|
432
|
+
out.push(items.slice(i, i + size));
|
|
406
433
|
}
|
|
407
434
|
return out;
|
|
408
435
|
}
|
|
409
|
-
function escapeXml(text) {
|
|
410
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
411
|
-
}
|
|
412
|
-
function createEntriesKey(entries) {
|
|
413
|
-
const raw = entries.map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`).join("\n");
|
|
414
|
-
return createHash2("sha256").update(raw).digest("hex");
|
|
415
|
-
}
|
|
416
436
|
async function importManifest(bundlePath) {
|
|
417
437
|
const mod = await import(pathToFileURL(bundlePath).href + `?t=${Date.now()}`);
|
|
418
438
|
return mod.manifest;
|
|
@@ -421,27 +441,19 @@ function htPages(options = {}) {
|
|
|
421
441
|
let root = process.cwd();
|
|
422
442
|
let server = null;
|
|
423
443
|
let devPages = [];
|
|
424
|
-
let cachedManifestKey = null;
|
|
425
|
-
let cachedBundlePath = null;
|
|
426
|
-
let loadDevPagesInFlight = null;
|
|
427
444
|
const cleanUrls = options.cleanUrls ?? true;
|
|
428
445
|
function logDebug(enabled, ...args) {
|
|
429
446
|
if (!enabled) return;
|
|
430
447
|
console.log(`[${PLUGIN_NAME}]`, ...args);
|
|
431
448
|
}
|
|
432
449
|
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() {
|
|
442
450
|
const entries = await discoverEntryPages(root, options);
|
|
443
451
|
const modulesByEntry = /* @__PURE__ */ new Map();
|
|
444
|
-
logDebug(
|
|
452
|
+
logDebug(
|
|
453
|
+
options.debug,
|
|
454
|
+
"discovered entries",
|
|
455
|
+
entries.map((e) => e.relativePath)
|
|
456
|
+
);
|
|
445
457
|
if (!server) return [];
|
|
446
458
|
for (const entry of entries) {
|
|
447
459
|
const mod = await server.ssrLoadModule(
|
|
@@ -464,19 +476,11 @@ function htPages(options = {}) {
|
|
|
464
476
|
async function buildPagesPipeline() {
|
|
465
477
|
const entries = await discoverEntryPages(root, options);
|
|
466
478
|
const cacheDir = path4.join(root, CACHE_DIR_NAME);
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
bundlePath = await buildRenderBundle({
|
|
473
|
-
entries,
|
|
474
|
-
cacheDir,
|
|
475
|
-
ssrPlugins: options.ssrPlugins
|
|
476
|
-
});
|
|
477
|
-
cachedManifestKey = entriesKey;
|
|
478
|
-
cachedBundlePath = bundlePath;
|
|
479
|
-
}
|
|
479
|
+
const bundlePath = await buildRenderBundle({
|
|
480
|
+
entries,
|
|
481
|
+
cacheDir,
|
|
482
|
+
ssrPlugins: options.ssrPlugins
|
|
483
|
+
});
|
|
480
484
|
logDebug(options.debug, "render bundle", bundlePath);
|
|
481
485
|
const manifest = await importManifest(bundlePath);
|
|
482
486
|
const modulesByEntry = /* @__PURE__ */ new Map();
|
|
@@ -488,13 +492,6 @@ function htPages(options = {}) {
|
|
|
488
492
|
modulesByEntry,
|
|
489
493
|
cleanUrls
|
|
490
494
|
});
|
|
491
|
-
const notFoundPage = pages.find((p) => p.routePath === "/404");
|
|
492
|
-
if (notFoundPage && !pages.some((p) => p.fileName === "404.html")) {
|
|
493
|
-
pages.push({
|
|
494
|
-
...notFoundPage,
|
|
495
|
-
fileName: "404.html"
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
495
|
return { entries, bundlePath, modulesByEntry, pages };
|
|
499
496
|
}
|
|
500
497
|
return {
|
|
@@ -537,7 +534,8 @@ function htPages(options = {}) {
|
|
|
537
534
|
getPages: async () => {
|
|
538
535
|
if (devPages.length > 0) return devPages;
|
|
539
536
|
return loadDevPages();
|
|
540
|
-
}
|
|
537
|
+
},
|
|
538
|
+
getEntries: async () => discoverEntryPages(root, options)
|
|
541
539
|
});
|
|
542
540
|
loadDevPages().catch((error) => {
|
|
543
541
|
server?.config.logger.error(
|
|
@@ -547,21 +545,22 @@ function htPages(options = {}) {
|
|
|
547
545
|
},
|
|
548
546
|
async handleHotUpdate(ctx) {
|
|
549
547
|
if (!server) return;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
logDebug(options.debug, "reindex triggered by", file);
|
|
553
|
-
await loadDevPages();
|
|
548
|
+
if (!ctx.file.endsWith(".ht.js")) {
|
|
549
|
+
return;
|
|
554
550
|
}
|
|
551
|
+
logDebug(options.debug, "page updated", ctx.file);
|
|
552
|
+
await loadDevPages();
|
|
553
|
+
return void 0;
|
|
555
554
|
},
|
|
556
555
|
async generateBundle(_, bundle) {
|
|
557
556
|
const { modulesByEntry, pages } = await buildPagesPipeline();
|
|
558
|
-
logDebug(
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
1,
|
|
563
|
-
options.renderBatchSize ?? Math.max(concurrency, 32)
|
|
557
|
+
logDebug(
|
|
558
|
+
options.debug,
|
|
559
|
+
"emitting pages",
|
|
560
|
+
pages.map((p) => p.fileName)
|
|
564
561
|
);
|
|
562
|
+
const limit = pLimit(options.renderConcurrency ?? 8);
|
|
563
|
+
const batchSize = options.renderBatchSize ?? Math.max(options.renderConcurrency ?? 8, 32);
|
|
565
564
|
for (const batch of chunkArray(pages, batchSize)) {
|
|
566
565
|
await Promise.all(
|
|
567
566
|
batch.map(
|
|
@@ -583,15 +582,15 @@ function htPages(options = {}) {
|
|
|
583
582
|
);
|
|
584
583
|
}
|
|
585
584
|
const sitemapBase = options.site ?? "";
|
|
586
|
-
const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(
|
|
585
|
+
const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(
|
|
586
|
+
(route) => !route.includes(":") && !route.includes("*")
|
|
587
|
+
);
|
|
587
588
|
if (sitemapRoutes.length > 0) {
|
|
588
589
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
</urlset>
|
|
594
|
-
`;
|
|
590
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
591
|
+
${sitemapRoutes.map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`).join("\n")}
|
|
592
|
+
</urlset>
|
|
593
|
+
`;
|
|
595
594
|
this.emitFile({
|
|
596
595
|
type: "asset",
|
|
597
596
|
fileName: "sitemap.xml",
|
|
@@ -603,21 +602,21 @@ function htPages(options = {}) {
|
|
|
603
602
|
const rssItems = pages.filter((page) => page.routePath.startsWith(routePrefix)).map((page) => {
|
|
604
603
|
const url = `${options.rss.site}${page.routePath}`;
|
|
605
604
|
return ` <item>
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
605
|
+
<title>${page.routePath}</title>
|
|
606
|
+
<link>${url}</link>
|
|
607
|
+
<guid>${url}</guid>
|
|
608
|
+
</item>`;
|
|
610
609
|
}).join("\n");
|
|
611
610
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
611
|
+
<rss version="2.0">
|
|
612
|
+
<channel>
|
|
613
|
+
<title>${options.rss.title ?? PLUGIN_NAME}</title>
|
|
614
|
+
<link>${options.rss.site}</link>
|
|
615
|
+
<description>${options.rss.description ?? "RSS feed"}</description>
|
|
616
|
+
${rssItems}
|
|
617
|
+
</channel>
|
|
618
|
+
</rss>
|
|
619
|
+
`;
|
|
621
620
|
this.emitFile({
|
|
622
621
|
type: "asset",
|
|
623
622
|
fileName: "rss.xml",
|
|
@@ -632,7 +631,65 @@ function htPages(options = {}) {
|
|
|
632
631
|
}
|
|
633
632
|
};
|
|
634
633
|
}
|
|
634
|
+
|
|
635
|
+
// src/fetch-cache.ts
|
|
636
|
+
import fs2 from "fs/promises";
|
|
637
|
+
import path5 from "path";
|
|
638
|
+
import { createHash as createHash2 } from "crypto";
|
|
639
|
+
function createDefaultCacheKey(input, init) {
|
|
640
|
+
const raw = JSON.stringify({
|
|
641
|
+
url: String(input),
|
|
642
|
+
method: init?.method ?? "GET",
|
|
643
|
+
headers: init?.headers ?? {},
|
|
644
|
+
body: init?.body ?? null
|
|
645
|
+
});
|
|
646
|
+
return createHash2("sha256").update(raw).digest("hex");
|
|
647
|
+
}
|
|
648
|
+
function getCacheFilePath(cacheKey) {
|
|
649
|
+
return path5.join(process.cwd(), CACHE_DIR_NAME, "fetch", `${cacheKey}.json`);
|
|
650
|
+
}
|
|
651
|
+
async function fetchAndCache(input, init, options = {}) {
|
|
652
|
+
const maxAge = options.maxAge ?? 60 * 60;
|
|
653
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
654
|
+
if (method !== "GET" && !options.cacheKey) {
|
|
655
|
+
return fetch(input, init);
|
|
656
|
+
}
|
|
657
|
+
const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);
|
|
658
|
+
const filePath = getCacheFilePath(cacheKey);
|
|
659
|
+
await fs2.mkdir(path5.dirname(filePath), { recursive: true });
|
|
660
|
+
if (!options.forceRefresh) {
|
|
661
|
+
try {
|
|
662
|
+
const raw = await fs2.readFile(filePath, "utf8");
|
|
663
|
+
const cached = JSON.parse(raw);
|
|
664
|
+
const ageSeconds = (Date.now() - cached.timestamp) / 1e3;
|
|
665
|
+
if (ageSeconds <= maxAge) {
|
|
666
|
+
return new Response(cached.body, {
|
|
667
|
+
status: cached.status,
|
|
668
|
+
statusText: cached.statusText,
|
|
669
|
+
headers: cached.headers
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const res = await fetch(input, init);
|
|
676
|
+
const body = await res.text();
|
|
677
|
+
const record = {
|
|
678
|
+
timestamp: Date.now(),
|
|
679
|
+
status: res.status,
|
|
680
|
+
statusText: res.statusText,
|
|
681
|
+
headers: [...res.headers.entries()],
|
|
682
|
+
body
|
|
683
|
+
};
|
|
684
|
+
await fs2.writeFile(filePath, JSON.stringify(record), "utf8");
|
|
685
|
+
return new Response(body, {
|
|
686
|
+
status: res.status,
|
|
687
|
+
statusText: res.statusText,
|
|
688
|
+
headers: res.headers
|
|
689
|
+
});
|
|
690
|
+
}
|
|
635
691
|
export {
|
|
692
|
+
fetchAndCache,
|
|
636
693
|
htPages
|
|
637
694
|
};
|
|
638
695
|
//# sourceMappingURL=index.js.map
|
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 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"]}
|
|
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","../src/fetch-cache.ts"],"sourcesContent":["import path from 'node:path';\nimport { pathToFileURL } from 'node:url';\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 {\n PLUGIN_NAME,\n VIRTUAL_BUILD_ENTRY_ID,\n CACHE_DIR_NAME,\n} 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\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 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(\n options.debug,\n 'discovered entries',\n entries.map((e) => e.relativePath),\n );\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 bundlePath = await buildRenderBundle({\n entries,\n cacheDir,\n ssrPlugins: options.ssrPlugins,\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 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 getEntries: async () => discoverEntryPages(root, options),\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 if (!ctx.file.endsWith('.ht.js')) {\n return;\n }\n\n logDebug(options.debug, 'page updated', ctx.file);\n\n await loadDevPages();\n return undefined;\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n\n logDebug(\n options.debug,\n 'emitting pages',\n pages.map((p) => p.fileName),\n );\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 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 const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(\n (route) => !route.includes(':') && !route.includes('*'),\n );\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 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 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","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\nfunction isDynamicOnly(mod: HtPageModule): boolean {\n return mod.dynamic === true || mod.prerender === false;\n}\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n getEntries: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages, getEntries } = 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\n const pages = await getPages();\n const staticPage = pages.find((p) => p.routePath === pathname);\n\n if (staticPage) {\n const mod = (await server.ssrLoadModule(\n `/${staticPage.relativePath}`,\n )) as HtPageModule;\n\n const html = await renderPage(staticPage, 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 const entries = await getEntries();\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n if (!isDynamicOnly(mod)) continue;\n\n const params = routeMatch(entry.routePattern, pathname);\n if (!params) continue;\n\n const page: HtPageInfo = {\n ...entry,\n routePath: pathname,\n fileName: '',\n params,\n };\n\n const html = await renderPage(page, 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';\n\nimport { createManifestModule } from './manifest';\nimport type { HtPageInfo } from './types';\nimport { PLUGIN_NAME, VIRTUAL_MANIFEST_ID } from './constants';\n\nasync function createRenderBundleHash(\n entries: HtPageInfo[],\n manifestSource: string,\n): Promise<string> {\n const hash = createHash('sha256');\n hash.update(manifestSource);\n\n for (const entry of entries) {\n hash.update(entry.entryPath);\n const source = await fs.readFile(entry.entryPath, 'utf8');\n hash.update(source);\n }\n\n return hash.digest('hex').slice(0, 12);\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 manifestSource = createManifestModule(entries);\n const hash = await createRenderBundleHash(entries, manifestSource);\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\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 ? manifestSource : 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(\n `[${PLUGIN_NAME}] Failed to generate HT pages render bundle.`,\n );\n }\n\n await fs.writeFile(bundlePath, chunk.code, 'utf8');\n return bundlePath;\n } finally {\n await bundle.close();\n }\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}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { CACHE_DIR_NAME } from './constants';\n\nexport interface FetchAndCacheOptions {\n maxAge?: number;\n cacheKey?: string;\n forceRefresh?: boolean;\n}\n\ntype CachedResponseRecord = {\n timestamp: number;\n status: number;\n statusText: string;\n headers: [string, string][];\n body: string;\n};\n\nfunction createDefaultCacheKey(\n input: RequestInfo | URL,\n init?: RequestInit,\n): string {\n const raw = JSON.stringify({\n url: String(input),\n method: init?.method ?? 'GET',\n headers: init?.headers ?? {},\n body: init?.body ?? null,\n });\n\n return createHash('sha256').update(raw).digest('hex');\n}\n\nfunction getCacheFilePath(cacheKey: string): string {\n return path.join(process.cwd(), CACHE_DIR_NAME, 'fetch', `${cacheKey}.json`);\n}\n\nexport async function fetchAndCache(\n input: RequestInfo | URL,\n init?: RequestInit,\n options: FetchAndCacheOptions = {},\n): Promise<Response> {\n const maxAge = options.maxAge ?? 60 * 60;\n const method = (init?.method ?? 'GET').toUpperCase();\n\n if (method !== 'GET' && !options.cacheKey) {\n return fetch(input, init);\n }\n\n const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);\n const filePath = getCacheFilePath(cacheKey);\n\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n\n if (!options.forceRefresh) {\n try {\n const raw = await fs.readFile(filePath, 'utf8');\n const cached = JSON.parse(raw) as CachedResponseRecord;\n\n const ageSeconds = (Date.now() - cached.timestamp) / 1000;\n\n if (ageSeconds <= maxAge) {\n return new Response(cached.body, {\n status: cached.status,\n statusText: cached.statusText,\n headers: cached.headers,\n });\n }\n } catch {\n // cache miss or invalid cache; fetch fresh\n }\n }\n\n const res = await fetch(input, init);\n const body = await res.text();\n\n const record: CachedResponseRecord = {\n timestamp: Date.now(),\n status: res.status,\n statusText: res.statusText,\n headers: [...res.headers.entries()],\n body,\n };\n\n await fs.writeFile(filePath, JSON.stringify(record), 'utf8');\n\n return new Response(body, {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n}\n"],"mappings":"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","names":["path","path","path","path","path","path","fs","path","createHash","createHash","path","fs"]}
|
package/package.json
CHANGED
package/src/dev-server.ts
CHANGED
|
@@ -3,34 +3,59 @@ import { renderPage } from './render-runtime';
|
|
|
3
3
|
import { routeMatch } from './route-utils';
|
|
4
4
|
import type { HtPageInfo, HtPageModule } from './types';
|
|
5
5
|
|
|
6
|
+
function isDynamicOnly(mod: HtPageModule): boolean {
|
|
7
|
+
return mod.dynamic === true || mod.prerender === false;
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export function installDevServer(args: {
|
|
7
11
|
server: ViteDevServer;
|
|
8
12
|
getPages: () => Promise<HtPageInfo[]>;
|
|
13
|
+
getEntries: () => Promise<HtPageInfo[]>;
|
|
9
14
|
}): void {
|
|
10
|
-
const { server, getPages } = args;
|
|
15
|
+
const { server, getPages, getEntries } = args;
|
|
11
16
|
|
|
12
17
|
server.middlewares.use(async (req, res, next) => {
|
|
13
18
|
try {
|
|
14
19
|
if (!req.url || req.method !== 'GET') return next();
|
|
15
20
|
|
|
16
21
|
const pathname = req.url.split('?')[0];
|
|
22
|
+
|
|
17
23
|
const pages = await getPages();
|
|
24
|
+
const staticPage = pages.find((p) => p.routePath === pathname);
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
26
|
+
if (staticPage) {
|
|
27
|
+
const mod = (await server.ssrLoadModule(
|
|
28
|
+
`/${staticPage.relativePath}`,
|
|
29
|
+
)) as HtPageModule;
|
|
30
|
+
|
|
31
|
+
const html = await renderPage(staticPage, mod, true);
|
|
32
|
+
|
|
33
|
+
res.statusCode = 200;
|
|
34
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
35
|
+
res.end(html);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
22
38
|
|
|
39
|
+
const entries = await getEntries();
|
|
40
|
+
|
|
41
|
+
for (const entry of entries) {
|
|
23
42
|
const mod = (await server.ssrLoadModule(
|
|
24
|
-
`/${
|
|
43
|
+
`/${entry.relativePath}`,
|
|
25
44
|
)) as HtPageModule;
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
if (!isDynamicOnly(mod)) continue;
|
|
47
|
+
|
|
48
|
+
const params = routeMatch(entry.routePattern, pathname);
|
|
49
|
+
if (!params) continue;
|
|
50
|
+
|
|
51
|
+
const page: HtPageInfo = {
|
|
52
|
+
...entry,
|
|
53
|
+
routePath: pathname,
|
|
54
|
+
fileName: '',
|
|
30
55
|
params,
|
|
31
56
|
};
|
|
32
57
|
|
|
33
|
-
const html = await renderPage(
|
|
58
|
+
const html = await renderPage(page, mod, true);
|
|
34
59
|
|
|
35
60
|
res.statusCode = 200;
|
|
36
61
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { CACHE_DIR_NAME } from './constants';
|
|
5
|
+
|
|
6
|
+
export interface FetchAndCacheOptions {
|
|
7
|
+
maxAge?: number;
|
|
8
|
+
cacheKey?: string;
|
|
9
|
+
forceRefresh?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type CachedResponseRecord = {
|
|
13
|
+
timestamp: number;
|
|
14
|
+
status: number;
|
|
15
|
+
statusText: string;
|
|
16
|
+
headers: [string, string][];
|
|
17
|
+
body: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function createDefaultCacheKey(
|
|
21
|
+
input: RequestInfo | URL,
|
|
22
|
+
init?: RequestInit,
|
|
23
|
+
): string {
|
|
24
|
+
const raw = JSON.stringify({
|
|
25
|
+
url: String(input),
|
|
26
|
+
method: init?.method ?? 'GET',
|
|
27
|
+
headers: init?.headers ?? {},
|
|
28
|
+
body: init?.body ?? null,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return createHash('sha256').update(raw).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getCacheFilePath(cacheKey: string): string {
|
|
35
|
+
return path.join(process.cwd(), CACHE_DIR_NAME, 'fetch', `${cacheKey}.json`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function fetchAndCache(
|
|
39
|
+
input: RequestInfo | URL,
|
|
40
|
+
init?: RequestInit,
|
|
41
|
+
options: FetchAndCacheOptions = {},
|
|
42
|
+
): Promise<Response> {
|
|
43
|
+
const maxAge = options.maxAge ?? 60 * 60;
|
|
44
|
+
const method = (init?.method ?? 'GET').toUpperCase();
|
|
45
|
+
|
|
46
|
+
if (method !== 'GET' && !options.cacheKey) {
|
|
47
|
+
return fetch(input, init);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);
|
|
51
|
+
const filePath = getCacheFilePath(cacheKey);
|
|
52
|
+
|
|
53
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
54
|
+
|
|
55
|
+
if (!options.forceRefresh) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
58
|
+
const cached = JSON.parse(raw) as CachedResponseRecord;
|
|
59
|
+
|
|
60
|
+
const ageSeconds = (Date.now() - cached.timestamp) / 1000;
|
|
61
|
+
|
|
62
|
+
if (ageSeconds <= maxAge) {
|
|
63
|
+
return new Response(cached.body, {
|
|
64
|
+
status: cached.status,
|
|
65
|
+
statusText: cached.statusText,
|
|
66
|
+
headers: cached.headers,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// cache miss or invalid cache; fetch fresh
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const res = await fetch(input, init);
|
|
75
|
+
const body = await res.text();
|
|
76
|
+
|
|
77
|
+
const record: CachedResponseRecord = {
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
status: res.status,
|
|
80
|
+
statusText: res.statusText,
|
|
81
|
+
headers: [...res.headers.entries()],
|
|
82
|
+
body,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
await fs.writeFile(filePath, JSON.stringify(record), 'utf8');
|
|
86
|
+
|
|
87
|
+
return new Response(body, {
|
|
88
|
+
status: res.status,
|
|
89
|
+
statusText: res.statusText,
|
|
90
|
+
headers: res.headers,
|
|
91
|
+
});
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export { htPages } from './plugin';
|
|
2
|
+
export { fetchAndCache } from './fetch-cache';
|
|
3
|
+
|
|
2
4
|
export type {
|
|
3
5
|
HtPageInfo,
|
|
4
6
|
HtPageModule,
|
|
5
7
|
HtPagesPluginOptions,
|
|
6
8
|
HtPageRenderContext,
|
|
7
9
|
StaticParamRecord,
|
|
8
|
-
} from './types';
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
export type { FetchAndCacheOptions } from './fetch-cache';
|
package/src/plugin.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
3
|
import pLimit from 'p-limit';
|
|
5
4
|
import type { Plugin, ViteDevServer } from 'vite';
|
|
6
5
|
|
|
@@ -11,34 +10,20 @@ import { buildRenderBundle } from './render-bundle';
|
|
|
11
10
|
import { renderPage } from './render-runtime';
|
|
12
11
|
|
|
13
12
|
import type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
PLUGIN_NAME,
|
|
15
|
+
VIRTUAL_BUILD_ENTRY_ID,
|
|
16
|
+
CACHE_DIR_NAME,
|
|
17
|
+
} from './constants';
|
|
15
18
|
|
|
16
19
|
function chunkArray<T>(items: T[], size: number): T[][] {
|
|
17
|
-
const safeSize = Math.max(1, Math.floor(size));
|
|
18
20
|
const out: T[][] = [];
|
|
19
|
-
for (let i = 0; i < items.length; i +=
|
|
20
|
-
out.push(items.slice(i, i +
|
|
21
|
+
for (let i = 0; i < items.length; i += size) {
|
|
22
|
+
out.push(items.slice(i, i + size));
|
|
21
23
|
}
|
|
22
24
|
return out;
|
|
23
25
|
}
|
|
24
26
|
|
|
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
|
-
|
|
34
|
-
function createEntriesKey(entries: HtPageInfo[]): string {
|
|
35
|
-
const raw = entries
|
|
36
|
-
.map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)
|
|
37
|
-
.join('\n');
|
|
38
|
-
|
|
39
|
-
return createHash('sha256').update(raw).digest('hex');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
27
|
async function importManifest(
|
|
43
28
|
bundlePath: string,
|
|
44
29
|
): Promise<Array<{ page: HtPageInfo; mod: HtPageModule }>> {
|
|
@@ -51,10 +36,6 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
51
36
|
let server: ViteDevServer | null = null;
|
|
52
37
|
let devPages: HtPageInfo[] = [];
|
|
53
38
|
|
|
54
|
-
let cachedManifestKey: string | null = null;
|
|
55
|
-
let cachedBundlePath: string | null = null;
|
|
56
|
-
let loadDevPagesInFlight: Promise<HtPageInfo[]> | null = null;
|
|
57
|
-
|
|
58
39
|
const cleanUrls = options.cleanUrls ?? true;
|
|
59
40
|
|
|
60
41
|
function logDebug(enabled: boolean | undefined, ...args: unknown[]) {
|
|
@@ -63,20 +44,14 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
63
44
|
}
|
|
64
45
|
|
|
65
46
|
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[]> {
|
|
76
47
|
const entries = await discoverEntryPages(root, options);
|
|
77
48
|
const modulesByEntry = new Map<string, HtPageModule>();
|
|
78
49
|
|
|
79
|
-
logDebug(
|
|
50
|
+
logDebug(
|
|
51
|
+
options.debug,
|
|
52
|
+
'discovered entries',
|
|
53
|
+
entries.map((e) => e.relativePath),
|
|
54
|
+
);
|
|
80
55
|
|
|
81
56
|
if (!server) return [];
|
|
82
57
|
|
|
@@ -107,20 +82,11 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
107
82
|
const entries = await discoverEntryPages(root, options);
|
|
108
83
|
const cacheDir = path.join(root, CACHE_DIR_NAME);
|
|
109
84
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} else {
|
|
116
|
-
bundlePath = await buildRenderBundle({
|
|
117
|
-
entries,
|
|
118
|
-
cacheDir,
|
|
119
|
-
ssrPlugins: options.ssrPlugins,
|
|
120
|
-
});
|
|
121
|
-
cachedManifestKey = entriesKey;
|
|
122
|
-
cachedBundlePath = bundlePath;
|
|
123
|
-
}
|
|
85
|
+
const bundlePath = await buildRenderBundle({
|
|
86
|
+
entries,
|
|
87
|
+
cacheDir,
|
|
88
|
+
ssrPlugins: options.ssrPlugins,
|
|
89
|
+
});
|
|
124
90
|
|
|
125
91
|
logDebug(options.debug, 'render bundle', bundlePath);
|
|
126
92
|
|
|
@@ -137,16 +103,6 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
137
103
|
cleanUrls,
|
|
138
104
|
});
|
|
139
105
|
|
|
140
|
-
// Ensure static hosts get a 404.html
|
|
141
|
-
const notFoundPage = pages.find((p) => p.routePath === '/404');
|
|
142
|
-
|
|
143
|
-
if (notFoundPage && !pages.some((p) => p.fileName === '404.html')) {
|
|
144
|
-
pages.push({
|
|
145
|
-
...notFoundPage,
|
|
146
|
-
fileName: '404.html',
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
106
|
return { entries, bundlePath, modulesByEntry, pages };
|
|
151
107
|
}
|
|
152
108
|
|
|
@@ -201,6 +157,7 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
201
157
|
if (devPages.length > 0) return devPages;
|
|
202
158
|
return loadDevPages();
|
|
203
159
|
},
|
|
160
|
+
getEntries: async () => discoverEntryPages(root, options),
|
|
204
161
|
});
|
|
205
162
|
|
|
206
163
|
loadDevPages().catch((error) => {
|
|
@@ -214,44 +171,44 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
214
171
|
|
|
215
172
|
async handleHotUpdate(ctx) {
|
|
216
173
|
if (!server) return;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
file.endsWith('.ht.js') ||
|
|
222
|
-
file.includes('/templates/')
|
|
223
|
-
) {
|
|
224
|
-
logDebug(options.debug, 'reindex triggered by', file);
|
|
225
|
-
await loadDevPages();
|
|
174
|
+
|
|
175
|
+
if (!ctx.file.endsWith('.ht.js')) {
|
|
176
|
+
return;
|
|
226
177
|
}
|
|
178
|
+
|
|
179
|
+
logDebug(options.debug, 'page updated', ctx.file);
|
|
180
|
+
|
|
181
|
+
await loadDevPages();
|
|
182
|
+
return undefined;
|
|
227
183
|
},
|
|
228
184
|
|
|
229
185
|
async generateBundle(_, bundle) {
|
|
230
186
|
const { modulesByEntry, pages } = await buildPagesPipeline();
|
|
231
|
-
|
|
232
|
-
logDebug(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const batchSize = Math.max(
|
|
237
|
-
1,
|
|
238
|
-
options.renderBatchSize ?? Math.max(concurrency, 32),
|
|
187
|
+
|
|
188
|
+
logDebug(
|
|
189
|
+
options.debug,
|
|
190
|
+
'emitting pages',
|
|
191
|
+
pages.map((p) => p.fileName),
|
|
239
192
|
);
|
|
240
|
-
|
|
193
|
+
|
|
194
|
+
const limit = pLimit(options.renderConcurrency ?? 8);
|
|
195
|
+
const batchSize =
|
|
196
|
+
options.renderBatchSize ??
|
|
197
|
+
Math.max(options.renderConcurrency ?? 8, 32);
|
|
198
|
+
|
|
241
199
|
for (const batch of chunkArray(pages, batchSize)) {
|
|
242
200
|
await Promise.all(
|
|
243
201
|
batch.map((page) =>
|
|
244
202
|
limit(async () => {
|
|
245
203
|
const mod = modulesByEntry.get(page.entryPath);
|
|
246
|
-
|
|
247
204
|
if (!mod) {
|
|
248
205
|
throw new Error(
|
|
249
206
|
`[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,
|
|
250
207
|
);
|
|
251
208
|
}
|
|
252
|
-
|
|
209
|
+
|
|
253
210
|
const html = await renderPage(page, mod, false);
|
|
254
|
-
|
|
211
|
+
|
|
255
212
|
this.emitFile({
|
|
256
213
|
type: 'asset',
|
|
257
214
|
fileName: options.mapOutputPath?.(page) ?? page.fileName,
|
|
@@ -261,66 +218,44 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
261
218
|
),
|
|
262
219
|
);
|
|
263
220
|
}
|
|
264
|
-
|
|
265
|
-
// Generate sitemap.xml
|
|
221
|
+
|
|
266
222
|
const sitemapBase = options.site ?? '';
|
|
267
|
-
const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))]
|
|
268
|
-
|
|
269
|
-
|
|
223
|
+
const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(
|
|
224
|
+
(route) => !route.includes(':') && !route.includes('*'),
|
|
225
|
+
);
|
|
226
|
+
|
|
270
227
|
if (sitemapRoutes.length > 0) {
|
|
271
|
-
const sitemap = `<?xml version="1.0" encoding="UTF-8"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
(route) =>
|
|
276
|
-
` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`,
|
|
277
|
-
)
|
|
278
|
-
.join('\n')}
|
|
279
|
-
</urlset>
|
|
280
|
-
`;
|
|
281
|
-
|
|
228
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${sitemapRoutes
|
|
229
|
+
.map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)
|
|
230
|
+
.join('\n')}\n</urlset>\n`;
|
|
231
|
+
|
|
282
232
|
this.emitFile({
|
|
283
233
|
type: 'asset',
|
|
284
234
|
fileName: 'sitemap.xml',
|
|
285
235
|
source: sitemap,
|
|
286
236
|
});
|
|
287
237
|
}
|
|
288
|
-
|
|
289
|
-
// Generate rss.xml
|
|
238
|
+
|
|
290
239
|
if (options.rss?.site) {
|
|
291
240
|
const routePrefix = options.rss.routePrefix ?? '/blog';
|
|
292
|
-
|
|
241
|
+
|
|
293
242
|
const rssItems = pages
|
|
294
243
|
.filter((page) => page.routePath.startsWith(routePrefix))
|
|
295
244
|
.map((page) => {
|
|
296
245
|
const url = `${options.rss!.site}${page.routePath}`;
|
|
297
|
-
return ` <item
|
|
298
|
-
<title>${escapeXml(page.routePath)}</title>
|
|
299
|
-
<link>${escapeXml(url)}</link>
|
|
300
|
-
<guid>${escapeXml(url)}</guid>
|
|
301
|
-
</item>`;
|
|
246
|
+
return ` <item>\n <title>${page.routePath}</title>\n <link>${url}</link>\n <guid>${url}</guid>\n </item>`;
|
|
302
247
|
})
|
|
303
248
|
.join('\n');
|
|
304
|
-
|
|
305
|
-
const rss = `<?xml version="1.0" encoding="UTF-8"
|
|
306
|
-
|
|
307
|
-
<channel>
|
|
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>
|
|
311
|
-
${rssItems}
|
|
312
|
-
</channel>
|
|
313
|
-
</rss>
|
|
314
|
-
`;
|
|
315
|
-
|
|
249
|
+
|
|
250
|
+
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`;
|
|
251
|
+
|
|
316
252
|
this.emitFile({
|
|
317
253
|
type: 'asset',
|
|
318
254
|
fileName: 'rss.xml',
|
|
319
255
|
source: rss,
|
|
320
256
|
});
|
|
321
257
|
}
|
|
322
|
-
|
|
323
|
-
// Remove the dummy virtual build entry chunk
|
|
258
|
+
|
|
324
259
|
for (const [fileName, output] of Object.entries(bundle)) {
|
|
325
260
|
if (
|
|
326
261
|
output.type === 'chunk' &&
|
|
@@ -329,7 +264,6 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
329
264
|
delete bundle[fileName];
|
|
330
265
|
}
|
|
331
266
|
}
|
|
332
|
-
}
|
|
333
|
-
|
|
267
|
+
},
|
|
334
268
|
};
|
|
335
269
|
}
|
package/src/render-bundle.ts
CHANGED
|
@@ -3,10 +3,26 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { rollup, type Plugin as RollupPlugin } from 'rollup';
|
|
5
5
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
6
|
+
|
|
6
7
|
import { createManifestModule } from './manifest';
|
|
7
8
|
import type { HtPageInfo } from './types';
|
|
8
9
|
import { PLUGIN_NAME, VIRTUAL_MANIFEST_ID } from './constants';
|
|
9
10
|
|
|
11
|
+
async function createRenderBundleHash(
|
|
12
|
+
entries: HtPageInfo[],
|
|
13
|
+
manifestSource: string,
|
|
14
|
+
): Promise<string> {
|
|
15
|
+
const hash = createHash('sha256');
|
|
16
|
+
hash.update(manifestSource);
|
|
17
|
+
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
hash.update(entry.entryPath);
|
|
20
|
+
const source = await fs.readFile(entry.entryPath, 'utf8');
|
|
21
|
+
hash.update(source);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return hash.digest('hex').slice(0, 12);
|
|
25
|
+
}
|
|
10
26
|
|
|
11
27
|
export async function buildRenderBundle(args: {
|
|
12
28
|
entries: HtPageInfo[];
|
|
@@ -15,8 +31,8 @@ export async function buildRenderBundle(args: {
|
|
|
15
31
|
}): Promise<string> {
|
|
16
32
|
const { entries, cacheDir, ssrPlugins = [] } = args;
|
|
17
33
|
|
|
18
|
-
const
|
|
19
|
-
const hash =
|
|
34
|
+
const manifestSource = createManifestModule(entries);
|
|
35
|
+
const hash = await createRenderBundleHash(entries, manifestSource);
|
|
20
36
|
const bundlePath = path.join(cacheDir, `render-${hash}.mjs`);
|
|
21
37
|
|
|
22
38
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
@@ -25,7 +41,7 @@ export async function buildRenderBundle(args: {
|
|
|
25
41
|
await fs.access(bundlePath);
|
|
26
42
|
return bundlePath;
|
|
27
43
|
} catch {
|
|
28
|
-
// cache miss
|
|
44
|
+
// cache miss
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
const bundle = await rollup({
|
|
@@ -37,7 +53,7 @@ export async function buildRenderBundle(args: {
|
|
|
37
53
|
return id === VIRTUAL_MANIFEST_ID ? id : null;
|
|
38
54
|
},
|
|
39
55
|
load(id) {
|
|
40
|
-
return id === VIRTUAL_MANIFEST_ID ?
|
|
56
|
+
return id === VIRTUAL_MANIFEST_ID ? manifestSource : null;
|
|
41
57
|
},
|
|
42
58
|
},
|
|
43
59
|
nodeResolve({
|
|
@@ -59,7 +75,9 @@ export async function buildRenderBundle(args: {
|
|
|
59
75
|
const chunk = output.find((item) => item.type === 'chunk');
|
|
60
76
|
|
|
61
77
|
if (!chunk || chunk.type !== 'chunk') {
|
|
62
|
-
throw new Error(
|
|
78
|
+
throw new Error(
|
|
79
|
+
`[${PLUGIN_NAME}] Failed to generate HT pages render bundle.`,
|
|
80
|
+
);
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
await fs.writeFile(bundlePath, chunk.code, 'utf8');
|
|
@@ -67,4 +85,4 @@ export async function buildRenderBundle(args: {
|
|
|
67
85
|
} finally {
|
|
68
86
|
await bundle.close();
|
|
69
87
|
}
|
|
70
|
-
}
|
|
88
|
+
}
|