vite-plugin-html-pages 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/TODO +1 -3
- package/dist/index.js +228 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/assets.ts +207 -0
- package/src/plugin.ts +129 -69
package/TODO
CHANGED
package/dist/index.js
CHANGED
|
@@ -374,15 +374,143 @@ async function buildPageIndex(args) {
|
|
|
374
374
|
return pages;
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
-
// src/
|
|
377
|
+
// src/assets.ts
|
|
378
378
|
import fs from "fs";
|
|
379
379
|
import path4 from "path";
|
|
380
|
+
var EXTERNAL_URL_RE = /^(?:[a-z]+:)?\/\//i;
|
|
381
|
+
function isLocalAssetUrl(url) {
|
|
382
|
+
return !!url && !url.startsWith("data:") && !url.startsWith("mailto:") && !url.startsWith("tel:") && !url.startsWith("#") && !EXTERNAL_URL_RE.test(url);
|
|
383
|
+
}
|
|
384
|
+
function stripQueryAndHash(url) {
|
|
385
|
+
return url.split("#")[0].split("?")[0];
|
|
386
|
+
}
|
|
387
|
+
function extractHtmlAssets(html) {
|
|
388
|
+
const assets = [];
|
|
389
|
+
for (const match of html.matchAll(
|
|
390
|
+
/<link\b[^>]*\brel=["']stylesheet["'][^>]*\bhref=["']([^"']+)["'][^>]*>/gi
|
|
391
|
+
)) {
|
|
392
|
+
assets.push({ kind: "css", url: match[1] });
|
|
393
|
+
}
|
|
394
|
+
for (const match of html.matchAll(
|
|
395
|
+
/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi
|
|
396
|
+
)) {
|
|
397
|
+
assets.push({ kind: "js", url: match[1] });
|
|
398
|
+
}
|
|
399
|
+
return dedupeExtractedAssets(assets);
|
|
400
|
+
}
|
|
401
|
+
function dedupeExtractedAssets(assets) {
|
|
402
|
+
const seen = /* @__PURE__ */ new Set();
|
|
403
|
+
const out = [];
|
|
404
|
+
for (const asset of assets) {
|
|
405
|
+
const key = `${asset.kind}:${asset.url}`;
|
|
406
|
+
if (seen.has(key)) continue;
|
|
407
|
+
seen.add(key);
|
|
408
|
+
out.push(asset);
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
function resolveLocalAssetPath(args) {
|
|
413
|
+
const { root, pagesDir, pageDir, url } = args;
|
|
414
|
+
if (!isLocalAssetUrl(url)) return null;
|
|
415
|
+
const cleanUrl = stripQueryAndHash(url);
|
|
416
|
+
let abs;
|
|
417
|
+
if (cleanUrl.startsWith("/")) {
|
|
418
|
+
abs = path4.join(root, pagesDir, cleanUrl.slice(1));
|
|
419
|
+
} else {
|
|
420
|
+
const baseDir = pageDir ?? path4.join(root, pagesDir);
|
|
421
|
+
abs = path4.resolve(baseDir, cleanUrl);
|
|
422
|
+
}
|
|
423
|
+
return fs.existsSync(abs) ? abs : null;
|
|
424
|
+
}
|
|
425
|
+
function emitHtmlAsset(args) {
|
|
426
|
+
const { ctx, kind, absolutePath } = args;
|
|
427
|
+
if (kind === "css" || kind === "js") {
|
|
428
|
+
return ctx.emitFile({
|
|
429
|
+
type: "chunk",
|
|
430
|
+
id: absolutePath,
|
|
431
|
+
name: path4.basename(absolutePath, path4.extname(absolutePath))
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
throw new Error(`[vite-plugin-html-pages] Unsupported asset kind: ${kind}`);
|
|
435
|
+
}
|
|
436
|
+
function replaceAllLiteral(input, search, replacement) {
|
|
437
|
+
return input.split(search).join(replacement);
|
|
438
|
+
}
|
|
439
|
+
function rewriteHtmlAssetUrls(html, replacements) {
|
|
440
|
+
let out = html;
|
|
441
|
+
for (const [originalUrl, builtUrl] of replacements) {
|
|
442
|
+
out = replaceAllLiteral(
|
|
443
|
+
out,
|
|
444
|
+
`href="${originalUrl}"`,
|
|
445
|
+
`href="${builtUrl}"`
|
|
446
|
+
);
|
|
447
|
+
out = replaceAllLiteral(
|
|
448
|
+
out,
|
|
449
|
+
`href='${originalUrl}'`,
|
|
450
|
+
`href='${builtUrl}'`
|
|
451
|
+
);
|
|
452
|
+
out = replaceAllLiteral(
|
|
453
|
+
out,
|
|
454
|
+
`src="${originalUrl}"`,
|
|
455
|
+
`src="${builtUrl}"`
|
|
456
|
+
);
|
|
457
|
+
out = replaceAllLiteral(
|
|
458
|
+
out,
|
|
459
|
+
`src='${originalUrl}'`,
|
|
460
|
+
`src='${builtUrl}'`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
return out;
|
|
464
|
+
}
|
|
465
|
+
async function collectHtmlAssetRefs(args) {
|
|
466
|
+
const { ctx, root, pagesDir, htmlByPageKey } = args;
|
|
467
|
+
const refs = /* @__PURE__ */ new Map();
|
|
468
|
+
for (const { html, pageDir } of htmlByPageKey.values()) {
|
|
469
|
+
const assets = extractHtmlAssets(html);
|
|
470
|
+
for (const asset of assets) {
|
|
471
|
+
const abs = resolveLocalAssetPath({
|
|
472
|
+
root,
|
|
473
|
+
pagesDir,
|
|
474
|
+
pageDir,
|
|
475
|
+
url: asset.url
|
|
476
|
+
});
|
|
477
|
+
if (!abs) continue;
|
|
478
|
+
const key = `${asset.kind}:${asset.url}`;
|
|
479
|
+
if (refs.has(key)) continue;
|
|
480
|
+
const refId = emitHtmlAsset({
|
|
481
|
+
ctx,
|
|
482
|
+
kind: asset.kind,
|
|
483
|
+
absolutePath: abs
|
|
484
|
+
});
|
|
485
|
+
refs.set(key, {
|
|
486
|
+
kind: asset.kind,
|
|
487
|
+
originalUrl: asset.url,
|
|
488
|
+
absolutePath: abs,
|
|
489
|
+
refId
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return refs;
|
|
494
|
+
}
|
|
495
|
+
function buildHtmlAssetReplacementMap(args) {
|
|
496
|
+
const { ctx, refs } = args;
|
|
497
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
498
|
+
for (const ref of refs.values()) {
|
|
499
|
+
const fileName = ctx.getFileName(ref.refId);
|
|
500
|
+
replacements.set(ref.originalUrl, `/${fileName}`);
|
|
501
|
+
}
|
|
502
|
+
return replacements;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/plugin.ts
|
|
506
|
+
import fs2 from "fs";
|
|
507
|
+
import path5 from "path";
|
|
380
508
|
var hasWarnedESM = false;
|
|
381
509
|
function warnIfNotESM(root) {
|
|
382
510
|
try {
|
|
383
|
-
const pkgPath =
|
|
384
|
-
if (!
|
|
385
|
-
const pkg = JSON.parse(
|
|
511
|
+
const pkgPath = path5.join(root, "package.json");
|
|
512
|
+
if (!fs2.existsSync(pkgPath)) return;
|
|
513
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
386
514
|
if (pkg.type !== "module") {
|
|
387
515
|
console.warn(
|
|
388
516
|
`[${PLUGIN_NAME}] \u26A0\uFE0F It is recommended to add "type": "module" to your package.json for optimal performance and to avoid Node ESM warnings.`
|
|
@@ -402,7 +530,9 @@ function htPages(options = {}) {
|
|
|
402
530
|
let root = process.cwd();
|
|
403
531
|
let server = null;
|
|
404
532
|
let devPages = [];
|
|
533
|
+
let htmlAssetRefs = /* @__PURE__ */ new Map();
|
|
405
534
|
const cleanUrls = options.cleanUrls ?? true;
|
|
535
|
+
const pagesDir = options.pagesDir ?? "src";
|
|
406
536
|
function logDebug(enabled, ...args) {
|
|
407
537
|
if (!enabled) return;
|
|
408
538
|
console.log(`[${PLUGIN_NAME}]`, ...args);
|
|
@@ -487,10 +617,40 @@ function htPages(options = {}) {
|
|
|
487
617
|
}
|
|
488
618
|
},
|
|
489
619
|
async buildStart() {
|
|
490
|
-
|
|
620
|
+
htmlAssetRefs.clear();
|
|
621
|
+
const { entries, modulesByEntry, pages } = await buildPagesPipeline();
|
|
491
622
|
for (const entry of entries) {
|
|
492
623
|
this.addWatchFile(entry.entryPath);
|
|
493
624
|
}
|
|
625
|
+
const htmlByPageKey = /* @__PURE__ */ new Map();
|
|
626
|
+
for (const page of pages) {
|
|
627
|
+
const mod = modulesByEntry.get(page.entryPath);
|
|
628
|
+
if (!mod) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
`[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
const html = await renderPage(page, mod, false);
|
|
634
|
+
htmlByPageKey.set(page.entryPath, {
|
|
635
|
+
html,
|
|
636
|
+
pageDir: path5.dirname(page.absolutePath)
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
htmlAssetRefs = await collectHtmlAssetRefs({
|
|
640
|
+
ctx: this,
|
|
641
|
+
root,
|
|
642
|
+
pagesDir,
|
|
643
|
+
htmlByPageKey
|
|
644
|
+
});
|
|
645
|
+
logDebug(
|
|
646
|
+
options.debug,
|
|
647
|
+
"collected html assets",
|
|
648
|
+
[...htmlAssetRefs.values()].map((ref) => ({
|
|
649
|
+
kind: ref.kind,
|
|
650
|
+
originalUrl: ref.originalUrl,
|
|
651
|
+
absolutePath: ref.absolutePath
|
|
652
|
+
}))
|
|
653
|
+
);
|
|
494
654
|
},
|
|
495
655
|
configureServer(_server) {
|
|
496
656
|
server = _server;
|
|
@@ -517,6 +677,15 @@ function htPages(options = {}) {
|
|
|
517
677
|
async generateBundle(_, bundle) {
|
|
518
678
|
try {
|
|
519
679
|
const { modulesByEntry, pages } = await buildPagesPipeline();
|
|
680
|
+
const assetReplacements = buildHtmlAssetReplacementMap({
|
|
681
|
+
ctx: this,
|
|
682
|
+
refs: htmlAssetRefs
|
|
683
|
+
});
|
|
684
|
+
logDebug(
|
|
685
|
+
options.debug,
|
|
686
|
+
"asset replacements",
|
|
687
|
+
[...assetReplacements.entries()]
|
|
688
|
+
);
|
|
520
689
|
logDebug(
|
|
521
690
|
options.debug,
|
|
522
691
|
"emitting pages",
|
|
@@ -534,7 +703,8 @@ function htPages(options = {}) {
|
|
|
534
703
|
`[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`
|
|
535
704
|
);
|
|
536
705
|
}
|
|
537
|
-
|
|
706
|
+
let html = await renderPage(page, mod, false);
|
|
707
|
+
html = rewriteHtmlAssetUrls(html, assetReplacements);
|
|
538
708
|
this.emitFile({
|
|
539
709
|
type: "asset",
|
|
540
710
|
fileName: options.mapOutputPath?.(page) ?? page.fileName,
|
|
@@ -552,7 +722,8 @@ function htPages(options = {}) {
|
|
|
552
722
|
`[${PLUGIN_NAME}] Missing module for 404 page entry: ${notFoundPage.entryPath}`
|
|
553
723
|
);
|
|
554
724
|
}
|
|
555
|
-
|
|
725
|
+
let html = await renderPage(notFoundPage, mod, false);
|
|
726
|
+
html = rewriteHtmlAssetUrls(html, assetReplacements);
|
|
556
727
|
this.emitFile({
|
|
557
728
|
type: "asset",
|
|
558
729
|
fileName: "404.html",
|
|
@@ -561,49 +732,49 @@ function htPages(options = {}) {
|
|
|
561
732
|
logDebug(options.debug, "generated 404.html from user page");
|
|
562
733
|
} else {
|
|
563
734
|
const default404 = `<!doctype html>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
735
|
+
<html lang="en">
|
|
736
|
+
<head>
|
|
737
|
+
<meta charset="UTF-8" />
|
|
738
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
739
|
+
<title>404 - Page Not Found</title>
|
|
740
|
+
<style>
|
|
741
|
+
:root {
|
|
742
|
+
color-scheme: light dark;
|
|
743
|
+
}
|
|
744
|
+
body {
|
|
745
|
+
margin: 0;
|
|
746
|
+
font-family: system-ui, sans-serif;
|
|
747
|
+
min-height: 100vh;
|
|
748
|
+
display: grid;
|
|
749
|
+
place-items: center;
|
|
750
|
+
padding: 2rem;
|
|
751
|
+
}
|
|
752
|
+
main {
|
|
753
|
+
max-width: 40rem;
|
|
754
|
+
text-align: center;
|
|
755
|
+
}
|
|
756
|
+
h1 {
|
|
757
|
+
font-size: 3rem;
|
|
758
|
+
margin: 0 0 1rem;
|
|
759
|
+
}
|
|
760
|
+
p {
|
|
761
|
+
margin: 0.5rem 0;
|
|
762
|
+
line-height: 1.5;
|
|
763
|
+
}
|
|
764
|
+
a {
|
|
765
|
+
color: inherit;
|
|
766
|
+
}
|
|
767
|
+
</style>
|
|
768
|
+
</head>
|
|
769
|
+
<body>
|
|
770
|
+
<main>
|
|
771
|
+
<h1>404</h1>
|
|
772
|
+
<p>Page not found.</p>
|
|
773
|
+
<p><a href="/">Go back home</a></p>
|
|
774
|
+
</main>
|
|
775
|
+
</body>
|
|
776
|
+
</html>
|
|
777
|
+
`;
|
|
607
778
|
this.emitFile({
|
|
608
779
|
type: "asset",
|
|
609
780
|
fileName: "404.html",
|
|
@@ -615,7 +786,7 @@ function htPages(options = {}) {
|
|
|
615
786
|
const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(
|
|
616
787
|
(route) => !route.includes(":") && !route.includes("*")
|
|
617
788
|
);
|
|
618
|
-
if (sitemapRoutes.length > 0) {
|
|
789
|
+
if (sitemapBase && sitemapRoutes.length > 0) {
|
|
619
790
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
620
791
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
621
792
|
${sitemapRoutes.map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`).join("\n")}
|
|
@@ -668,8 +839,8 @@ ${rssItems}
|
|
|
668
839
|
}
|
|
669
840
|
|
|
670
841
|
// src/fetch-cache.ts
|
|
671
|
-
import
|
|
672
|
-
import
|
|
842
|
+
import fs3 from "fs/promises";
|
|
843
|
+
import path6 from "path";
|
|
673
844
|
import { createHash } from "crypto";
|
|
674
845
|
var memoryCache = /* @__PURE__ */ new Map();
|
|
675
846
|
function createDefaultCacheKey(input, init) {
|
|
@@ -682,7 +853,7 @@ function createDefaultCacheKey(input, init) {
|
|
|
682
853
|
return createHash("sha256").update(raw).digest("hex");
|
|
683
854
|
}
|
|
684
855
|
function getCacheFilePath(cacheKey) {
|
|
685
|
-
return
|
|
856
|
+
return path6.join(process.cwd(), CACHE_DIR_NAME, "fetch", `${cacheKey}.json`);
|
|
686
857
|
}
|
|
687
858
|
function getEffectiveCacheMode(mode) {
|
|
688
859
|
if (mode === "memory" || mode === "fs" || mode === "none") {
|
|
@@ -720,10 +891,10 @@ async function fetchWithCache(input, init, options = {}) {
|
|
|
720
891
|
}
|
|
721
892
|
const filePath = getCacheFilePath(cacheKey);
|
|
722
893
|
if (cacheMode === "fs") {
|
|
723
|
-
await
|
|
894
|
+
await fs3.mkdir(path6.dirname(filePath), { recursive: true });
|
|
724
895
|
if (!options.forceRefresh) {
|
|
725
896
|
try {
|
|
726
|
-
const raw = await
|
|
897
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
727
898
|
const cached = JSON.parse(raw);
|
|
728
899
|
if (isFresh(cached, maxAge)) {
|
|
729
900
|
return toResponse(cached);
|
|
@@ -744,7 +915,7 @@ async function fetchWithCache(input, init, options = {}) {
|
|
|
744
915
|
if (cacheMode === "memory") {
|
|
745
916
|
memoryCache.set(cacheKey, record);
|
|
746
917
|
} else if (cacheMode === "fs") {
|
|
747
|
-
await
|
|
918
|
+
await fs3.writeFile(filePath, JSON.stringify(record), "utf8");
|
|
748
919
|
}
|
|
749
920
|
return new Response(body, {
|
|
750
921
|
status: res.status,
|
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/module-loader.ts","../src/page-index.ts","../src/fetch-cache.ts"],"sourcesContent":["import pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { createPageModuleLoader, closePageModuleLoader } from './module-loader';\nimport { buildPageIndex } from './page-index';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID } from './constants';\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nlet hasWarnedESM = false;\n\nfunction warnIfNotESM(root: string) {\n try {\n const pkgPath = path.join(root, 'package.json');\n\n if (!fs.existsSync(pkgPath)) return;\n\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\n\n if (pkg.type !== 'module') {\n console.warn(\n `[${PLUGIN_NAME}] ⚠️ It is recommended to add \"type\": \"module\" to your package.json for optimal performance and to avoid Node ESM warnings.`,\n );\n }\n } catch {\n // silent — never break build\n }\n}\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\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 const loadModule = await createPageModuleLoader({\n mode: 'dev',\n root,\n server,\n });\n\n for (const entry of entries) {\n const mod = await loadModule(entry.entryPath, entry.relativePath);\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 modulesByEntry = new Map<string, HtPageModule>();\n\n const loadModule = await createPageModuleLoader({\n mode: 'build',\n root,\n });\n\n for (const entry of entries) {\n const mod = await loadModule(entry.entryPath, entry.relativePath);\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n const pages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n return { entries, 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 if (!hasWarnedESM) {\n warnIfNotESM(root);\n hasWarnedESM = true;\n }\n\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 logDebug(options.debug, 'file changed', ctx.file);\n\n await loadDevPages();\n return undefined;\n },\n\n async generateBundle(_, bundle) {\n try {\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 // ---------------------------\n // 1. Render all pages\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 // ---------------------------\n // 2. 404.html\n // ---------------------------\n const notFoundPage = pages.find((p) => p.routePath === '/404');\n\n if (notFoundPage) {\n const mod = modulesByEntry.get(notFoundPage.entryPath);\n\n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for 404 page entry: ${notFoundPage.entryPath}`,\n );\n }\n\n const html = await renderPage(notFoundPage, mod, false);\n\n this.emitFile({\n type: 'asset',\n fileName: '404.html',\n source: html,\n });\n\n logDebug(options.debug, 'generated 404.html from user page');\n } else {\n const default404 = `<!doctype html>\n <html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>404 - Page Not Found</title>\n <style>\n :root {\n color-scheme: light dark;\n }\n body {\n margin: 0;\n font-family: system-ui, sans-serif;\n min-height: 100vh;\n display: grid;\n place-items: center;\n padding: 2rem;\n }\n main {\n max-width: 40rem;\n text-align: center;\n }\n h1 {\n font-size: 3rem;\n margin: 0 0 1rem;\n }\n p {\n margin: 0.5rem 0;\n line-height: 1.5;\n }\n a {\n color: inherit;\n }\n </style>\n </head>\n <body>\n <main>\n <h1>404</h1>\n <p>Page not found.</p>\n <p><a href=\"/\">Go back home</a></p>\n </main>\n </body>\n </html>\n `;\n\n this.emitFile({\n type: 'asset',\n fileName: '404.html',\n source: default404,\n });\n\n logDebug(options.debug, 'generated default 404.html');\n }\n \n // ---------------------------\n // 3. Sitemap\n // ---------------------------\n const sitemapBase = options.site ?? '';\n \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 logDebug(options.debug, 'generated sitemap.xml');\n }\n \n // ---------------------------\n // 4. RSS\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 \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 logDebug(options.debug, 'generated rss.xml');\n }\n \n // ---------------------------\n // 5. Remove virtual entry chunk\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 } finally {\n await closePageModuleLoader();\n }\n }\n };\n}","import path from 'node:path';\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\nfunction buildDefaultIncludeGlobs(\n pagesDir: string,\n pageExtensions: string[],\n): string[] {\n return pageExtensions.map((ext) => {\n const cleanExt = ext.startsWith('.') ? ext.slice(1) : ext;\n return `${pagesDir}/**/*.${cleanExt}`;\n });\n}\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const fgModule = await import('fast-glob');\n const fg = (fgModule.default ?? fgModule) as typeof import('fast-glob');\n\n const pagesDir = options.pagesDir ?? 'src';\n const pageExtensions = options.pageExtensions?.length\n ? options.pageExtensions\n : ['.ht.js', '.html.js', '.ht.ts', '.html.ts'];\n\n const include = Array.isArray(options.include)\n ? options.include\n : options.include\n ? [options.include]\n : buildDefaultIncludeGlobs(pagesDir, pageExtensions);\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg.glob(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, pageExtensions);\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(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\nexport function normalizeFsPath(value: string): string {\n return path.normalize(value);\n}\n\nexport function normalizeRoutePath(value: string): string {\n const normalized = toPosix(value).replace(/\\/+/g, '/');\n if (!normalized || normalized === '/') return '/';\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\nexport function stripPageSuffix(\n filePath: string,\n extensions: string[],\n): string {\n const normalized = toPosix(filePath);\n\n const match = [...extensions]\n .sort((a, b) => b.length - a.length)\n .find((ext) => normalized.endsWith(ext));\n\n if (!match) return normalized;\n\n return normalized.slice(0, -match.length);\n}","import { normalizeRoutePath, stripPageSuffix, 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;\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(\n relativeFromPagesDir: string,\n extensions: string[],\n): string {\n const noExt = stripPageSuffix(toPosix(relativeFromPagesDir), extensions);\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(decodeURIComponent).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(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 bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-html-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 path from 'node:path';\nimport { createServer, type InlineConfig, type ViteDevServer } from 'vite';\nimport type { HtPageModule } from './types';\n\nexport type PageModuleLoader = (\n entryPath: string,\n relativePath: string,\n) => Promise<HtPageModule>;\n\nlet buildServer: ViteDevServer | null = null;\n\nexport async function createPageModuleLoader(args: {\n mode: 'dev' | 'build';\n root: string;\n server?: ViteDevServer | null;\n}): Promise<PageModuleLoader> {\n const { mode, root, server } = args;\n\n if (mode === 'dev') {\n if (!server) {\n throw new Error('[vite-plugin-html-pages] dev server not available');\n }\n\n return async (_entryPath, relativePath) => {\n const mod = await server.ssrLoadModule(`/${relativePath}`);\n return mod as HtPageModule;\n };\n }\n\n if (!buildServer) {\n const config: InlineConfig = {\n root,\n configFile: false,\n logLevel: 'error',\n appType: 'custom',\n server: {\n middlewareMode: true,\n },\n };\n\n buildServer = await createServer(config);\n }\n\n return async (entryPath) => {\n const relativePath =\n '/' + path.relative(root, entryPath).replace(/\\\\/g, '/');\n\n const mod = await buildServer!.ssrLoadModule(relativePath);\n return mod as HtPageModule;\n };\n}\n\nexport async function closePageModuleLoader(): Promise<void> {\n if (buildServer) {\n await buildServer.close();\n buildServer = null;\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 fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { CACHE_DIR_NAME } from './constants';\n\nexport type FetchCacheMode = 'auto' | 'memory' | 'fs' | 'none';\nexport interface FetchWithCacheOptions {\n maxAge?: number;\n cacheKey?: string;\n forceRefresh?: boolean;\n cache?: FetchCacheMode;\n}\n\ntype CachedResponseRecord = {\n timestamp: number;\n status: number;\n statusText: string;\n headers: [string, string][];\n body: string;\n};\n\nconst memoryCache = new Map<string, CachedResponseRecord>();\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\nfunction getEffectiveCacheMode(\n mode: FetchCacheMode | undefined,\n): Exclude<FetchCacheMode, 'auto'> {\n if (mode === 'memory' || mode === 'fs' || mode === 'none') {\n return mode;\n }\n\n return process.env.NODE_ENV === 'production' ? 'fs' : 'memory';\n}\n\nfunction toResponse(cached: CachedResponseRecord): Response {\n return new Response(cached.body, {\n status: cached.status,\n statusText: cached.statusText,\n headers: cached.headers,\n });\n}\n\nfunction isFresh(cached: CachedResponseRecord, maxAgeSeconds: number): boolean {\n const ageSeconds = (Date.now() - cached.timestamp) / 1000;\n return ageSeconds <= maxAgeSeconds;\n}\n\nexport function clearMemoryFetchCache(): void {\n memoryCache.clear();\n}\n\nexport function deleteMemoryFetchCache(cacheKey: string): void {\n memoryCache.delete(cacheKey);\n}\n\nexport async function fetchWithCache(\n input: RequestInfo | URL,\n init?: RequestInit,\n options: FetchWithCacheOptions = {},\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 cacheMode = getEffectiveCacheMode(options.cache);\n const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);\n\n if (cacheMode === 'none') {\n return fetch(input, init);\n }\n\n if (cacheMode === 'memory' && !options.forceRefresh) {\n const cached = memoryCache.get(cacheKey);\n\n if (cached && isFresh(cached, maxAge)) {\n return toResponse(cached);\n }\n }\n\n const filePath = getCacheFilePath(cacheKey);\n\n if (cacheMode === 'fs') {\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 if (isFresh(cached, maxAge)) {\n return toResponse(cached);\n }\n } catch {\n // cache miss or invalid cache; fetch fresh\n }\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 if (cacheMode === 'memory') {\n memoryCache.set(cacheKey, record);\n } else if (cacheMode === 'fs') {\n await fs.writeFile(filePath, JSON.stringify(record), 'utf8');\n }\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","fs","path","path","fs","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/module-loader.ts","../src/page-index.ts","../src/assets.ts","../src/fetch-cache.ts"],"sourcesContent":["import pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { createPageModuleLoader, closePageModuleLoader } from './module-loader';\nimport { buildPageIndex } from './page-index';\nimport { renderPage } from './render-runtime';\nimport {\n buildHtmlAssetReplacementMap,\n collectHtmlAssetRefs,\n rewriteHtmlAssetUrls,\n} from './assets';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport type { HtmlAssetRef } from './assets';\nimport { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID } from './constants';\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nlet hasWarnedESM = false;\n\nfunction warnIfNotESM(root: string) {\n try {\n const pkgPath = path.join(root, 'package.json');\n\n if (!fs.existsSync(pkgPath)) return;\n\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));\n\n if (pkg.type !== 'module') {\n console.warn(\n `[${PLUGIN_NAME}] ⚠️ It is recommended to add \"type\": \"module\" to your package.json for optimal performance and to avoid Node ESM warnings.`,\n );\n }\n } catch {\n // silent — never break build\n }\n}\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\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n let htmlAssetRefs = new Map<string, HtmlAssetRef>();\n\n const cleanUrls = options.cleanUrls ?? true;\n const pagesDir = options.pagesDir ?? 'src';\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 const loadModule = await createPageModuleLoader({\n mode: 'dev',\n root,\n server,\n });\n\n for (const entry of entries) {\n const mod = await loadModule(entry.entryPath, entry.relativePath);\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 modulesByEntry = new Map<string, HtPageModule>();\n\n const loadModule = await createPageModuleLoader({\n mode: 'build',\n root,\n });\n\n for (const entry of entries) {\n const mod = await loadModule(entry.entryPath, entry.relativePath);\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n const pages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n return { entries, 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 if (!hasWarnedESM) {\n warnIfNotESM(root);\n hasWarnedESM = true;\n }\n },\n\n async buildStart() {\n htmlAssetRefs.clear();\n\n const { entries, modulesByEntry, pages } = await buildPagesPipeline();\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n\n const htmlByPageKey = new Map<string, { html: string; pageDir?: string }>();\n\n for (const page of pages) {\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 htmlByPageKey.set(page.entryPath, {\n html,\n pageDir: path.dirname(page.absolutePath),\n });\n }\n\n htmlAssetRefs = await collectHtmlAssetRefs({\n ctx: this,\n root,\n pagesDir,\n htmlByPageKey,\n });\n\n logDebug(\n options.debug,\n 'collected html assets',\n [...htmlAssetRefs.values()].map((ref) => ({\n kind: ref.kind,\n originalUrl: ref.originalUrl,\n absolutePath: ref.absolutePath,\n })),\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 logDebug(options.debug, 'file changed', ctx.file);\n\n await loadDevPages();\n return undefined;\n },\n\n async generateBundle(_, bundle) {\n try {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n\n const assetReplacements = buildHtmlAssetReplacementMap({\n ctx: this,\n refs: htmlAssetRefs,\n });\n\n logDebug(\n options.debug,\n 'asset replacements',\n [...assetReplacements.entries()],\n );\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 // ---------------------------\n // 1. Render all pages\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 let html = await renderPage(page, mod, false);\n html = rewriteHtmlAssetUrls(html, assetReplacements);\n\n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n\n // ---------------------------\n // 2. 404.html\n // ---------------------------\n const notFoundPage = pages.find((p) => p.routePath === '/404');\n\n if (notFoundPage) {\n const mod = modulesByEntry.get(notFoundPage.entryPath);\n\n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for 404 page entry: ${notFoundPage.entryPath}`,\n );\n }\n\n let html = await renderPage(notFoundPage, mod, false);\n html = rewriteHtmlAssetUrls(html, assetReplacements);\n\n this.emitFile({\n type: 'asset',\n fileName: '404.html',\n source: html,\n });\n\n logDebug(options.debug, 'generated 404.html from user page');\n } else {\n const default404 = `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>404 - Page Not Found</title>\n <style>\n :root {\n color-scheme: light dark;\n }\n body {\n margin: 0;\n font-family: system-ui, sans-serif;\n min-height: 100vh;\n display: grid;\n place-items: center;\n padding: 2rem;\n }\n main {\n max-width: 40rem;\n text-align: center;\n }\n h1 {\n font-size: 3rem;\n margin: 0 0 1rem;\n }\n p {\n margin: 0.5rem 0;\n line-height: 1.5;\n }\n a {\n color: inherit;\n }\n </style>\n </head>\n <body>\n <main>\n <h1>404</h1>\n <p>Page not found.</p>\n <p><a href=\"/\">Go back home</a></p>\n </main>\n </body>\n</html>\n`;\n\n this.emitFile({\n type: 'asset',\n fileName: '404.html',\n source: default404,\n });\n\n logDebug(options.debug, 'generated default 404.html');\n }\n\n // ---------------------------\n // 3. Sitemap\n // ---------------------------\n const sitemapBase = options.site ?? '';\n\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(\n (route) => !route.includes(':') && !route.includes('*'),\n );\n\n if (sitemapBase && 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 logDebug(options.debug, 'generated sitemap.xml');\n }\n\n // ---------------------------\n // 4. RSS\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\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 logDebug(options.debug, 'generated rss.xml');\n }\n\n // ---------------------------\n // 5. Remove virtual entry chunk\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 } finally {\n await closePageModuleLoader();\n }\n },\n };\n}\n\nexport default htPages;","import path from 'node:path';\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\nfunction buildDefaultIncludeGlobs(\n pagesDir: string,\n pageExtensions: string[],\n): string[] {\n return pageExtensions.map((ext) => {\n const cleanExt = ext.startsWith('.') ? ext.slice(1) : ext;\n return `${pagesDir}/**/*.${cleanExt}`;\n });\n}\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const fgModule = await import('fast-glob');\n const fg = (fgModule.default ?? fgModule) as typeof import('fast-glob');\n\n const pagesDir = options.pagesDir ?? 'src';\n const pageExtensions = options.pageExtensions?.length\n ? options.pageExtensions\n : ['.ht.js', '.html.js', '.ht.ts', '.html.ts'];\n\n const include = Array.isArray(options.include)\n ? options.include\n : options.include\n ? [options.include]\n : buildDefaultIncludeGlobs(pagesDir, pageExtensions);\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg.glob(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, pageExtensions);\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(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\nexport function normalizeFsPath(value: string): string {\n return path.normalize(value);\n}\n\nexport function normalizeRoutePath(value: string): string {\n const normalized = toPosix(value).replace(/\\/+/g, '/');\n if (!normalized || normalized === '/') return '/';\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\nexport function stripPageSuffix(\n filePath: string,\n extensions: string[],\n): string {\n const normalized = toPosix(filePath);\n\n const match = [...extensions]\n .sort((a, b) => b.length - a.length)\n .find((ext) => normalized.endsWith(ext));\n\n if (!match) return normalized;\n\n return normalized.slice(0, -match.length);\n}","import { normalizeRoutePath, stripPageSuffix, 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;\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(\n relativeFromPagesDir: string,\n extensions: string[],\n): string {\n const noExt = stripPageSuffix(toPosix(relativeFromPagesDir), extensions);\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(decodeURIComponent).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(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 bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-html-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 path from 'node:path';\nimport { createServer, type InlineConfig, type ViteDevServer } from 'vite';\nimport type { HtPageModule } from './types';\n\nexport type PageModuleLoader = (\n entryPath: string,\n relativePath: string,\n) => Promise<HtPageModule>;\n\nlet buildServer: ViteDevServer | null = null;\n\nexport async function createPageModuleLoader(args: {\n mode: 'dev' | 'build';\n root: string;\n server?: ViteDevServer | null;\n}): Promise<PageModuleLoader> {\n const { mode, root, server } = args;\n\n if (mode === 'dev') {\n if (!server) {\n throw new Error('[vite-plugin-html-pages] dev server not available');\n }\n\n return async (_entryPath, relativePath) => {\n const mod = await server.ssrLoadModule(`/${relativePath}`);\n return mod as HtPageModule;\n };\n }\n\n if (!buildServer) {\n const config: InlineConfig = {\n root,\n configFile: false,\n logLevel: 'error',\n appType: 'custom',\n server: {\n middlewareMode: true,\n },\n };\n\n buildServer = await createServer(config);\n }\n\n return async (entryPath) => {\n const relativePath =\n '/' + path.relative(root, entryPath).replace(/\\\\/g, '/');\n\n const mod = await buildServer!.ssrLoadModule(relativePath);\n return mod as HtPageModule;\n };\n}\n\nexport async function closePageModuleLoader(): Promise<void> {\n if (buildServer) {\n await buildServer.close();\n buildServer = null;\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 fs from 'node:fs';\nimport path from 'node:path';\nimport type { PluginContext } from 'rollup';\n\nexport type HtmlAssetKind = 'css' | 'js';\n\nexport interface HtmlAssetRef {\n kind: HtmlAssetKind;\n originalUrl: string;\n absolutePath: string;\n refId: string;\n}\n\nexport interface ExtractedHtmlAsset {\n kind: HtmlAssetKind;\n url: string;\n}\n\nconst EXTERNAL_URL_RE = /^(?:[a-z]+:)?\\/\\//i;\n\nexport function isLocalAssetUrl(url: string): boolean {\n return (\n !!url &&\n !url.startsWith('data:') &&\n !url.startsWith('mailto:') &&\n !url.startsWith('tel:') &&\n !url.startsWith('#') &&\n !EXTERNAL_URL_RE.test(url)\n );\n}\n\nexport function stripQueryAndHash(url: string): string {\n return url.split('#')[0].split('?')[0];\n}\n\nexport function extractHtmlAssets(html: string): ExtractedHtmlAsset[] {\n const assets: ExtractedHtmlAsset[] = [];\n\n for (const match of html.matchAll(\n /<link\\b[^>]*\\brel=[\"']stylesheet[\"'][^>]*\\bhref=[\"']([^\"']+)[\"'][^>]*>/gi,\n )) {\n assets.push({ kind: 'css', url: match[1] });\n }\n\n for (const match of html.matchAll(\n /<script\\b[^>]*\\bsrc=[\"']([^\"']+)[\"'][^>]*>/gi,\n )) {\n assets.push({ kind: 'js', url: match[1] });\n }\n\n return dedupeExtractedAssets(assets);\n}\n\nfunction dedupeExtractedAssets(\n assets: ExtractedHtmlAsset[],\n): ExtractedHtmlAsset[] {\n const seen = new Set<string>();\n const out: ExtractedHtmlAsset[] = [];\n\n for (const asset of assets) {\n const key = `${asset.kind}:${asset.url}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(asset);\n }\n\n return out;\n}\n\nexport function resolveLocalAssetPath(args: {\n root: string;\n pagesDir: string;\n pageDir?: string;\n url: string;\n}): string | null {\n const { root, pagesDir, pageDir, url } = args;\n\n if (!isLocalAssetUrl(url)) return null;\n\n const cleanUrl = stripQueryAndHash(url);\n\n let abs: string;\n if (cleanUrl.startsWith('/')) {\n abs = path.join(root, pagesDir, cleanUrl.slice(1));\n } else {\n const baseDir = pageDir ?? path.join(root, pagesDir);\n abs = path.resolve(baseDir, cleanUrl);\n }\n\n return fs.existsSync(abs) ? abs : null;\n}\n\nexport function emitHtmlAsset(args: {\n ctx: PluginContext;\n kind: HtmlAssetKind;\n absolutePath: string;\n}): string {\n const { ctx, kind, absolutePath } = args;\n\n if (kind === 'css' || kind === 'js') {\n return ctx.emitFile({\n type: 'chunk',\n id: absolutePath,\n name: path.basename(absolutePath, path.extname(absolutePath)),\n });\n }\n\n throw new Error(`[vite-plugin-html-pages] Unsupported asset kind: ${kind}`);\n}\n\nfunction replaceAllLiteral(\n input: string,\n search: string,\n replacement: string,\n ): string {\n return input.split(search).join(replacement);\n}\n \nexport function rewriteHtmlAssetUrls(\n html: string,\n replacements: Map<string, string>,\n): string {\n let out = html;\n \n for (const [originalUrl, builtUrl] of replacements) {\n out = replaceAllLiteral(\n out,\n `href=\"${originalUrl}\"`,\n `href=\"${builtUrl}\"`,\n );\n out = replaceAllLiteral(\n out,\n `href='${originalUrl}'`,\n `href='${builtUrl}'`,\n );\n out = replaceAllLiteral(\n out,\n `src=\"${originalUrl}\"`,\n `src=\"${builtUrl}\"`,\n );\n out = replaceAllLiteral(\n out,\n `src='${originalUrl}'`,\n `src='${builtUrl}'`,\n );\n }\n \n return out;\n}\n\nexport async function collectHtmlAssetRefs(args: {\n ctx: PluginContext;\n root: string;\n pagesDir: string;\n htmlByPageKey: Map<string, { html: string; pageDir?: string }>;\n}): Promise<Map<string, HtmlAssetRef>> {\n const { ctx, root, pagesDir, htmlByPageKey } = args;\n const refs = new Map<string, HtmlAssetRef>();\n\n for (const { html, pageDir } of htmlByPageKey.values()) {\n const assets = extractHtmlAssets(html);\n\n for (const asset of assets) {\n const abs = resolveLocalAssetPath({\n root,\n pagesDir,\n pageDir,\n url: asset.url,\n });\n\n if (!abs) continue;\n\n const key = `${asset.kind}:${asset.url}`;\n if (refs.has(key)) continue;\n\n const refId = emitHtmlAsset({\n ctx,\n kind: asset.kind,\n absolutePath: abs,\n });\n\n refs.set(key, {\n kind: asset.kind,\n originalUrl: asset.url,\n absolutePath: abs,\n refId,\n });\n }\n }\n\n return refs;\n}\n\nexport function buildHtmlAssetReplacementMap(args: {\n ctx: PluginContext;\n refs: Map<string, HtmlAssetRef>;\n}): Map<string, string> {\n const { ctx, refs } = args;\n const replacements = new Map<string, string>();\n\n for (const ref of refs.values()) {\n const fileName = ctx.getFileName(ref.refId);\n replacements.set(ref.originalUrl, `/${fileName}`);\n }\n\n return replacements;\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 type FetchCacheMode = 'auto' | 'memory' | 'fs' | 'none';\nexport interface FetchWithCacheOptions {\n maxAge?: number;\n cacheKey?: string;\n forceRefresh?: boolean;\n cache?: FetchCacheMode;\n}\n\ntype CachedResponseRecord = {\n timestamp: number;\n status: number;\n statusText: string;\n headers: [string, string][];\n body: string;\n};\n\nconst memoryCache = new Map<string, CachedResponseRecord>();\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\nfunction getEffectiveCacheMode(\n mode: FetchCacheMode | undefined,\n): Exclude<FetchCacheMode, 'auto'> {\n if (mode === 'memory' || mode === 'fs' || mode === 'none') {\n return mode;\n }\n\n return process.env.NODE_ENV === 'production' ? 'fs' : 'memory';\n}\n\nfunction toResponse(cached: CachedResponseRecord): Response {\n return new Response(cached.body, {\n status: cached.status,\n statusText: cached.statusText,\n headers: cached.headers,\n });\n}\n\nfunction isFresh(cached: CachedResponseRecord, maxAgeSeconds: number): boolean {\n const ageSeconds = (Date.now() - cached.timestamp) / 1000;\n return ageSeconds <= maxAgeSeconds;\n}\n\nexport function clearMemoryFetchCache(): void {\n memoryCache.clear();\n}\n\nexport function deleteMemoryFetchCache(cacheKey: string): void {\n memoryCache.delete(cacheKey);\n}\n\nexport async function fetchWithCache(\n input: RequestInfo | URL,\n init?: RequestInit,\n options: FetchWithCacheOptions = {},\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 cacheMode = getEffectiveCacheMode(options.cache);\n const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);\n\n if (cacheMode === 'none') {\n return fetch(input, init);\n }\n\n if (cacheMode === 'memory' && !options.forceRefresh) {\n const cached = memoryCache.get(cacheKey);\n\n if (cached && isFresh(cached, maxAge)) {\n return toResponse(cached);\n }\n }\n\n const filePath = getCacheFilePath(cacheKey);\n\n if (cacheMode === 'fs') {\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 if (isFresh(cached, maxAge)) {\n return toResponse(cached);\n }\n } catch {\n // cache miss or invalid cache; fetch fresh\n }\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 if (cacheMode === 'memory') {\n memoryCache.set(cacheKey, record);\n } else if (cacheMode === 'fs') {\n await fs.writeFile(filePath, JSON.stringify(record), 'utf8');\n }\n\n return new Response(body, {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n}\n"],"mappings":";AAAA,OAAO,YAAY;;;ACAnB,OAAOA,WAAU;;;ACAjB,OAAO,UAAU;AAEV,SAAS,QAAQ,OAAuB;AAC7C,SAAO,MAAM,QAAQ,OAAO,GAAG;AACjC;AAEO,SAAS,gBAAgB,OAAuB;AACrD,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEO,SAAS,mBAAmB,OAAuB;AACxD,QAAM,aAAa,QAAQ,KAAK,EAAE,QAAQ,QAAQ,GAAG;AACrD,MAAI,CAAC,cAAc,eAAe,IAAK,QAAO;AAC9C,SAAO,WAAW,WAAW,GAAG,IAAI,aAAa,IAAI,UAAU;AACjE;AAEO,SAAS,gBACd,UACA,YACQ;AACR,QAAM,aAAa,QAAQ,QAAQ;AAEnC,QAAM,QAAQ,CAAC,GAAG,UAAU,EACzB,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,KAAK,CAAC,QAAQ,WAAW,SAAS,GAAG,CAAC;AAEzC,MAAI,CAAC,MAAO,QAAO;AAEnB,SAAO,WAAW,MAAM,GAAG,CAAC,MAAM,MAAM;AAC1C;;;AC1BA,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,gCAAgC;AACtC,IAAM,eAAe;AACrB,IAAM,iBAAiB;AAEhB,SAAS,cAAc,sBAAwC;AACpE,SAAO,CAAC,GAAG,qBAAqB,SAAS,YAAY,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AACzE;AAEO,SAAS,cAAc,sBAAuC;AACnE,SAAO,kCAAkC,KAAK,oBAAoB;AACpE;AAEO,SAAS,eACd,sBACA,YACQ;AACR,QAAM,QAAQ,gBAAgB,QAAQ,oBAAoB,GAAG,UAAU;AAEvE,QAAM,gBAAgB,MAAM,QAAQ,gBAAgB,IAAI;AACxD,QAAM,eAAe,cAAc,QAAQ,aAAa,EAAE,EAAE,QAAQ,YAAY,EAAE;AAElF,QAAM,MAAM,aACT,QAAQ,+BAA+B,OAAO,EAC9C,QAAQ,sBAAsB,MAAM,EACpC,QAAQ,oBAAoB,KAAK;AAEpC,SAAO,mBAAmB,OAAO,GAAG;AACtC;AAEO,SAAS,WACd,SACA,QACQ;AACR,QAAM,SAAS,QACZ,QAAQ,yBAAyB,CAAC,GAAG,QAAQ;AAC5C,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,SAAS,QAAQ,UAAU,IAAI;AACjC,aAAO;AAAA,IACT;AAEA,WAAO,OAAO,KAAK,EAChB,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,mBAAmB,IAAI,CAAC,EACtC,KAAK,GAAG;AAAA,EACb,CAAC,EACA,QAAQ,uBAAuB,CAAC,GAAG,QAAQ;AAC1C,QAAI,EAAE,OAAO,SAAS;AACpB,YAAM,IAAI,MAAM,kCAAkC,GAAG,GAAG;AAAA,IAC1D;AAEA,WAAO,OAAO,OAAO,GAAG,CAAC,EACtB,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,mBAAmB,IAAI,CAAC,EACtC,KAAK,GAAG;AAAA,EACb,CAAC,EACA,QAAQ,qBAAqB,CAAC,GAAG,QAAQ;AACxC,QAAI,EAAE,OAAO,SAAS;AACpB,YAAM,IAAI,MAAM,wBAAwB,GAAG,GAAG;AAAA,IAChD;AAEA,WAAO,mBAAmB,OAAO,GAAG,CAAC;AAAA,EACvC,CAAC;AAEH,SAAO,mBAAmB,UAAU,GAAG;AACzC;AAEO,SAAS,kBACd,WACA,WACQ;AACR,QAAM,aAAa,mBAAmB,SAAS;AAE/C,MAAI,eAAe,IAAK,QAAO;AAE/B,QAAM,OAAO,WAAW,MAAM,CAAC;AAC/B,SAAO,YAAY,GAAG,IAAI,gBAAgB,GAAG,IAAI;AACnD;AAEO,SAAS,kBACd,UACA,MACA,WACc;AACd,SAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,UAAM,SAAS,OAAO;AAAA,MACpB,OAAO,QAAQ,GAAG,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AAAA,IACpD;AAEA,UAAM,YAAY,WAAW,SAAS,cAAc,MAAM;AAE1D,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA,UAAU,kBAAkB,WAAW,SAAS;AAAA,MAChD;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEO,SAAS,WACd,SACA,SAC+B;AAC/B,QAAM,IAAI,mBAAmB,OAAO,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/D,QAAM,IAAI,mBAAmB,OAAO,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/D,QAAM,SAAiC,CAAC;AAExC,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,UAAM,aAAa,EAAE,CAAC;AACtB,UAAM,SAAS,EAAE,CAAC;AAElB,QAAI,WAAW,WAAW,KAAK,GAAG;AAChC,aAAO,WAAW,MAAM,CAAC,CAAC,IACxB,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,IAAI,kBAAkB,EAAE,KAAK,GAAG,IAAI;AAChE,aAAO;AAAA,IACT;AAEA,QAAI,WAAW,WAAW,IAAI,GAAG;AAC/B,YAAM,OAAO,EAAE,MAAM,CAAC;AACtB,UAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,aAAO,WAAW,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,kBAAkB,EAAE,KAAK,GAAG;AACnE,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,OAAQ,QAAO;AAEpB,QAAI,WAAW,WAAW,GAAG,GAAG;AAC9B,aAAO,WAAW,MAAM,CAAC,CAAC,IAAI,mBAAmB,MAAM;AACvD;AAAA,IACF;AAEA,QAAI,eAAe,OAAQ,QAAO;AAAA,EACpC;AAEA,SAAO,EAAE,WAAW,EAAE,SAAS,SAAS;AAC1C;AAEO,SAAS,qBAAqB,GAAW,GAAmB;AACjE,QAAM,QAAQ,mBAAmB,CAAC,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAC7D,QAAM,QAAQ,mBAAmB,CAAC,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO;AAC7D,QAAM,MAAM,KAAK,IAAI,MAAM,QAAQ,MAAM,MAAM;AAE/C,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,UAAM,KAAK,MAAM,CAAC;AAClB,UAAM,KAAK,MAAM,CAAC;AAElB,QAAI,MAAM,KAAM,QAAO;AACvB,QAAI,MAAM,KAAM,QAAO;AAEvB,UAAM,oBAAoB,GAAG,WAAW,KAAK;AAC7C,UAAM,oBAAoB,GAAG,WAAW,KAAK;AAC7C,QAAI,sBAAsB,mBAAmB;AAC3C,aAAO,oBAAoB,IAAI;AAAA,IACjC;AAEA,UAAM,YAAY,GAAG,WAAW,IAAI;AACpC,UAAM,YAAY,GAAG,WAAW,IAAI;AACpC,QAAI,cAAc,WAAW;AAC3B,aAAO,YAAY,IAAI;AAAA,IACzB;AAEA,UAAM,WAAW,GAAG,WAAW,GAAG;AAClC,UAAM,WAAW,GAAG,WAAW,GAAG;AAClC,QAAI,aAAa,UAAU;AACzB,aAAO,WAAW,IAAI;AAAA,IACxB;AAAA,EACF;AAEA,SAAO,MAAM,SAAS,MAAM;AAC9B;;;AC/KO,IAAM,cAAc;AACpB,IAAM,yBAAyB,KAAK,WAAW;AAC/C,IAAM,sBAAsB,aAAa,WAAW;AACpD,IAAM,iBAAiB,uBAAuB,WAAW;;;AHGhE,SAAS,yBACP,UACA,gBACU;AACV,SAAO,eAAe,IAAI,CAAC,QAAQ;AACjC,UAAM,WAAW,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AACtD,WAAO,GAAG,QAAQ,SAAS,QAAQ;AAAA,EACrC,CAAC;AACH;AAEA,eAAsB,mBACpB,MACA,SACuB;AACvB,QAAM,WAAW,MAAM,OAAO,WAAW;AACzC,QAAM,KAAM,SAAS,WAAW;AAEhC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,iBAAiB,QAAQ,gBAAgB,SAC3C,QAAQ,iBACR,CAAC,UAAU,YAAY,UAAU,UAAU;AAE/C,QAAM,UAAU,MAAM,QAAQ,QAAQ,OAAO,IACzC,QAAQ,UACR,QAAQ,UACN,CAAC,QAAQ,OAAO,IAChB,yBAAyB,UAAU,cAAc;AAEvD,QAAM,UAAU,MAAM,QAAQ,QAAQ,OAAO,IACzC,QAAQ,UACR,QAAQ,UACN,CAAC,QAAQ,OAAO,IAChB,CAAC;AAEP,QAAM,YAAY,gBAAgBC,MAAK,KAAK,MAAM,QAAQ,CAAC;AAE3D,QAAM,QAAQ,MAAM,GAAG,KAAK,SAAS;AAAA,IACnC,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ,CAAC;AAED,SAAO,MACJ,KAAK,EACL,IAAI,CAAC,iBAAiB;AACrB,UAAM,YAAY,gBAAgB,YAAY;AAC9C,UAAM,eAAe,QAAQA,MAAK,SAAS,MAAM,SAAS,CAAC;AAC3D,UAAM,uBAAuB,QAAQA,MAAK,SAAS,WAAW,SAAS,CAAC;AAExE,QACE,qBAAqB,WAAW,KAAK,KACrC,yBAAyB,MACzB;AACA,YAAM,IAAI;AAAA,QACR,IAAI,WAAW,+BAA+B,SAAS,eAAe,QAAQ;AAAA,MAChF;AAAA,IACF;AAEA,UAAM,UAAU,cAAc,oBAAoB;AAClD,UAAM,eAAe,eAAe,sBAAsB,cAAc;AAExE,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,WAAW;AAAA,MACX,UAAU;AAAA,MACV;AAAA,MACA,YAAY,cAAc,oBAAoB;AAAA,MAC9C,QAAQ,CAAC;AAAA,IACX;AAAA,EACF,CAAC;AACL;;;AI9EO,SAAS,kBACd,MACA,OACO;AACP,SAAO,IAAI;AAAA,IACT,IAAI,WAAW,WAAW,KAAK,YAAY,yCAAyC,OAAO,KAAK;AAAA,EAClG;AACF;AAEO,SAAS,qBAAqB,MAAyB;AAC5D,SAAO,IAAI;AAAA,IACT,IAAI,WAAW,WAAW,KAAK,YAAY;AAAA,EAC7C;AACF;AAEO,SAAS,UAAU,MAAkB,OAAuB;AACjE,QAAM,UAAU,IAAI,WAAW,uBAAuB,KAAK,YAAY,eAAe,KAAK,SAAS;AAEpG,MAAI,iBAAiB,OAAO;AAC1B,UAAM,MAAM,IAAI,MAAM,GAAG,OAAO,KAAK,MAAM,OAAO,EAAE;AAEpD,QAAI,MAAM,OAAO;AACf,UAAI,QAAQ,GAAG,IAAI,KAAK;AAAA;AAAA,EAAiB,MAAM,KAAK;AAAA,IACtD;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,IAAI,MAAM,GAAG,OAAO,KAAK,OAAO,KAAK,CAAC,EAAE;AACjD;;;AC5BA,eAAsB,WACpB,MACA,KACA,MAAM,OACW;AACjB,QAAM,MAA2B;AAAA,IAC/B;AAAA,IACA,QAAQ,KAAK;AAAA,IACb;AAAA,EACF;AAEA,MAAI;AACF,QAAI,OAAO,IAAI,SAAS,YAAY;AAClC,UAAI,OAAO,MAAM,IAAI,KAAK,GAAG;AAAA,IAC/B;AAEA,UAAM,QAAQ,IAAI;AAElB,QAAI,SAAS,MAAM;AACjB,YAAM,qBAAqB,IAAI;AAAA,IACjC;AAEA,UAAM,OAAO,OAAO,UAAU,aAAa,MAAM,MAAM,GAAG,IAAI;AAE9D,QAAI,OAAO,SAAS,UAAU;AAC5B,YAAM,kBAAkB,MAAM,IAAI;AAAA,IACpC;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,UAAU,MAAM,KAAK;AAAA,EAC7B;AACF;;;AC9BA,SAAS,cAAc,KAA4B;AACjD,SAAO,IAAI,YAAY,QAAQ,IAAI,cAAc;AACnD;AAEO,SAAS,iBAAiB,MAIxB;AACP,QAAM,EAAE,QAAQ,UAAU,WAAW,IAAI;AAEzC,SAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,QAAI;AACF,UAAI,CAAC,IAAI,OAAO,IAAI,WAAW,MAAO,QAAO,KAAK;AAElD,YAAM,WAAW,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC;AAErC,YAAM,QAAQ,MAAM,SAAS;AAC7B,YAAM,aAAa,MAAM,KAAK,CAAC,MAAM,EAAE,cAAc,QAAQ;AAE7D,UAAI,YAAY;AACd,cAAM,MAAO,MAAM,OAAO;AAAA,UACxB,IAAI,WAAW,YAAY;AAAA,QAC7B;AAEA,cAAM,OAAO,MAAM,WAAW,YAAY,KAAK,IAAI;AAEnD,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,0BAA0B;AACxD,YAAI,IAAI,IAAI;AACZ;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,WAAW;AAEjC,iBAAW,SAAS,SAAS;AAC3B,cAAM,MAAO,MAAM,OAAO;AAAA,UACxB,IAAI,MAAM,YAAY;AAAA,QACxB;AAEA,YAAI,CAAC,cAAc,GAAG,EAAG;AAEzB,cAAM,SAAS,WAAW,MAAM,cAAc,QAAQ;AACtD,YAAI,CAAC,OAAQ;AAEb,cAAM,OAAmB;AAAA,UACvB,GAAG;AAAA,UACH,WAAW;AAAA,UACX,UAAU;AAAA,UACV;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,WAAW,MAAM,KAAK,IAAI;AAE7C,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,0BAA0B;AACxD,YAAI,IAAI,IAAI;AACZ;AAAA,MACF;AAEA,WAAK;AAAA,IACP,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF,CAAC;AACH;;;ACtEA,OAAOC,WAAU;AACjB,SAAS,oBAA2D;AAQpE,IAAI,cAAoC;AAExC,eAAsB,uBAAuB,MAIf;AAC5B,QAAM,EAAE,MAAM,MAAM,OAAO,IAAI;AAE/B,MAAI,SAAS,OAAO;AAClB,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AAEA,WAAO,OAAO,YAAY,iBAAiB;AACzC,YAAM,MAAM,MAAM,OAAO,cAAc,IAAI,YAAY,EAAE;AACzD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,CAAC,aAAa;AAChB,UAAM,SAAuB;AAAA,MAC3B;AAAA,MACA,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,gBAAgB;AAAA,MAClB;AAAA,IACF;AAEA,kBAAc,MAAM,aAAa,MAAM;AAAA,EACzC;AAEA,SAAO,OAAO,cAAc;AAC1B,UAAM,eACJ,MAAMA,MAAK,SAAS,MAAM,SAAS,EAAE,QAAQ,OAAO,GAAG;AAEzD,UAAM,MAAM,MAAM,YAAa,cAAc,YAAY;AACzD,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,wBAAuC;AAC3D,MAAI,aAAa;AACf,UAAM,YAAY,MAAM;AACxB,kBAAc;AAAA,EAChB;AACF;;;AClDA,eAAsB,eAAe,MAIX;AACxB,QAAM,EAAE,SAAS,gBAAgB,UAAU,IAAI;AAC/C,QAAM,QAAsB,CAAC;AAE7B,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,eAAe,IAAI,MAAM,SAAS,KAAK,CAAC;AAEpD,QAAI,MAAM,SAAS;AACjB,YAAM,QACH,IAAI,uBACD,MAAM,IAAI,qBAAqB,IAC/B,CAAC,MAAM,CAAC;AAEd,YAAM;AAAA,QACJ,GAAG;AAAA,UACD;AAAA,YACE,IAAI,MAAM;AAAA,YACV,WAAW,MAAM;AAAA,YACjB,cAAc,MAAM;AAAA,YACpB,cAAc,MAAM;AAAA,YACpB,cAAc,MAAM;AAAA,YACpB,SAAS,MAAM;AAAA,YACf,YAAY,MAAM;AAAA,UACpB;AAAA,UACA,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;AAAA,UAC9B;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,KAAK;AAAA,QACT,GAAG;AAAA,QACH,WAAW,MAAM;AAAA,QACjB,UAAU,kBAAkB,MAAM,cAAc,SAAS;AAAA,QACzD,QAAQ,CAAC;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,KAAK,CAAC,GAAG,MAAM,qBAAqB,EAAE,cAAc,EAAE,YAAY,CAAC;AAEzE,QAAM,aAAa,oBAAI,IAAwB;AAE/C,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,WAAW,IAAI,KAAK,SAAS;AAE9C,QAAI,UAAU;AACZ,YAAM,IAAI;AAAA,QACR,IAAI,WAAW,iCAAiC,KAAK,SAAS,WAAW,SAAS,YAAY,UAAU,KAAK,YAAY;AAAA,MAC3H;AAAA,IACF;AAEA,eAAW,IAAI,KAAK,WAAW,IAAI;AAAA,EACrC;AAEA,SAAO;AACT;;;AClEA,OAAO,QAAQ;AACf,OAAOC,WAAU;AAiBjB,IAAM,kBAAkB;AAEjB,SAAS,gBAAgB,KAAsB;AACpD,SACE,CAAC,CAAC,OACF,CAAC,IAAI,WAAW,OAAO,KACvB,CAAC,IAAI,WAAW,SAAS,KACzB,CAAC,IAAI,WAAW,MAAM,KACtB,CAAC,IAAI,WAAW,GAAG,KACnB,CAAC,gBAAgB,KAAK,GAAG;AAE7B;AAEO,SAAS,kBAAkB,KAAqB;AACrD,SAAO,IAAI,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AACvC;AAEO,SAAS,kBAAkB,MAAoC;AACpE,QAAM,SAA+B,CAAC;AAEtC,aAAW,SAAS,KAAK;AAAA,IACvB;AAAA,EACF,GAAG;AACD,WAAO,KAAK,EAAE,MAAM,OAAO,KAAK,MAAM,CAAC,EAAE,CAAC;AAAA,EAC5C;AAEA,aAAW,SAAS,KAAK;AAAA,IACvB;AAAA,EACF,GAAG;AACD,WAAO,KAAK,EAAE,MAAM,MAAM,KAAK,MAAM,CAAC,EAAE,CAAC;AAAA,EAC3C;AAEA,SAAO,sBAAsB,MAAM;AACrC;AAEA,SAAS,sBACP,QACsB;AACtB,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAA4B,CAAC;AAEnC,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,GAAG,MAAM,IAAI,IAAI,MAAM,GAAG;AACtC,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK,KAAK;AAAA,EAChB;AAEA,SAAO;AACT;AAEO,SAAS,sBAAsB,MAKpB;AAChB,QAAM,EAAE,MAAM,UAAU,SAAS,IAAI,IAAI;AAEzC,MAAI,CAAC,gBAAgB,GAAG,EAAG,QAAO;AAElC,QAAM,WAAW,kBAAkB,GAAG;AAEtC,MAAI;AACJ,MAAI,SAAS,WAAW,GAAG,GAAG;AAC5B,UAAMA,MAAK,KAAK,MAAM,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EACnD,OAAO;AACL,UAAM,UAAU,WAAWA,MAAK,KAAK,MAAM,QAAQ;AACnD,UAAMA,MAAK,QAAQ,SAAS,QAAQ;AAAA,EACtC;AAEA,SAAO,GAAG,WAAW,GAAG,IAAI,MAAM;AACpC;AAEO,SAAS,cAAc,MAInB;AACT,QAAM,EAAE,KAAK,MAAM,aAAa,IAAI;AAEpC,MAAI,SAAS,SAAS,SAAS,MAAM;AACnC,WAAO,IAAI,SAAS;AAAA,MAClB,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,MAAMA,MAAK,SAAS,cAAcA,MAAK,QAAQ,YAAY,CAAC;AAAA,IAC9D,CAAC;AAAA,EACH;AAEA,QAAM,IAAI,MAAM,oDAAoD,IAAI,EAAE;AAC5E;AAEA,SAAS,kBACL,OACA,QACA,aACQ;AACR,SAAO,MAAM,MAAM,MAAM,EAAE,KAAK,WAAW;AAC/C;AAEO,SAAS,qBACZ,MACA,cACM;AACN,MAAI,MAAM;AAEV,aAAW,CAAC,aAAa,QAAQ,KAAK,cAAc;AAClD,UAAM;AAAA,MACJ;AAAA,MACA,SAAS,WAAW;AAAA,MACpB,SAAS,QAAQ;AAAA,IACnB;AACA,UAAM;AAAA,MACJ;AAAA,MACA,SAAS,WAAW;AAAA,MACpB,SAAS,QAAQ;AAAA,IACnB;AACA,UAAM;AAAA,MACJ;AAAA,MACA,QAAQ,WAAW;AAAA,MACnB,QAAQ,QAAQ;AAAA,IAClB;AACA,UAAM;AAAA,MACJ;AAAA,MACA,QAAQ,WAAW;AAAA,MACnB,QAAQ,QAAQ;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACX;AAEA,eAAsB,qBAAqB,MAKJ;AACrC,QAAM,EAAE,KAAK,MAAM,UAAU,cAAc,IAAI;AAC/C,QAAM,OAAO,oBAAI,IAA0B;AAE3C,aAAW,EAAE,MAAM,QAAQ,KAAK,cAAc,OAAO,GAAG;AACtD,UAAM,SAAS,kBAAkB,IAAI;AAErC,eAAW,SAAS,QAAQ;AAC1B,YAAM,MAAM,sBAAsB;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,MAAM;AAAA,MACb,CAAC;AAED,UAAI,CAAC,IAAK;AAEV,YAAM,MAAM,GAAG,MAAM,IAAI,IAAI,MAAM,GAAG;AACtC,UAAI,KAAK,IAAI,GAAG,EAAG;AAEnB,YAAM,QAAQ,cAAc;AAAA,QAC1B;AAAA,QACA,MAAM,MAAM;AAAA,QACZ,cAAc;AAAA,MAChB,CAAC;AAED,WAAK,IAAI,KAAK;AAAA,QACZ,MAAM,MAAM;AAAA,QACZ,aAAa,MAAM;AAAA,QACnB,cAAc;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,6BAA6B,MAGrB;AACtB,QAAM,EAAE,KAAK,KAAK,IAAI;AACtB,QAAM,eAAe,oBAAI,IAAoB;AAE7C,aAAW,OAAO,KAAK,OAAO,GAAG;AAC/B,UAAM,WAAW,IAAI,YAAY,IAAI,KAAK;AAC1C,iBAAa,IAAI,IAAI,aAAa,IAAI,QAAQ,EAAE;AAAA,EAClD;AAEA,SAAO;AACT;;;AV5LA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAEjB,IAAI,eAAe;AAEnB,SAAS,aAAa,MAAc;AAClC,MAAI;AACF,UAAM,UAAUA,MAAK,KAAK,MAAM,cAAc;AAE9C,QAAI,CAACD,IAAG,WAAW,OAAO,EAAG;AAE7B,UAAM,MAAM,KAAK,MAAMA,IAAG,aAAa,SAAS,MAAM,CAAC;AAEvD,QAAI,IAAI,SAAS,UAAU;AACzB,cAAQ;AAAA,QACN,IAAI,WAAW;AAAA,MACjB;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,WAAc,OAAY,MAAqB;AACtD,QAAM,MAAa,CAAC;AACpB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM;AAC3C,QAAI,KAAK,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC;AAAA,EACnC;AACA,SAAO;AACT;AAEO,SAAS,QAAQ,UAAgC,CAAC,GAAW;AAClE,MAAI,OAAO,QAAQ,IAAI;AACvB,MAAI,SAA+B;AACnC,MAAI,WAAyB,CAAC;AAC9B,MAAI,gBAAgB,oBAAI,IAA0B;AAElD,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,WAAW,QAAQ,YAAY;AAErC,WAAS,SAAS,YAAiC,MAAiB;AAClE,QAAI,CAAC,QAAS;AACd,YAAQ,IAAI,IAAI,WAAW,KAAK,GAAG,IAAI;AAAA,EACzC;AAEA,iBAAe,eAAsC;AACnD,UAAM,UAAU,MAAM,mBAAmB,MAAM,OAAO;AACtD,UAAM,iBAAiB,oBAAI,IAA0B;AAErD;AAAA,MACE,QAAQ;AAAA,MACR;AAAA,MACA,QAAQ,IAAI,CAAC,MAAM,EAAE,YAAY;AAAA,IACnC;AAEA,QAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,UAAM,aAAa,MAAM,uBAAuB;AAAA,MAC9C,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF,CAAC;AAED,eAAW,SAAS,SAAS;AAC3B,YAAM,MAAM,MAAM,WAAW,MAAM,WAAW,MAAM,YAAY;AAChE,qBAAe,IAAI,MAAM,WAAW,GAAG;AAAA,IACzC;AAEA,eAAW,MAAM,eAAe;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED;AAAA,MACE,QAAQ;AAAA,MACR;AAAA,MACA,SAAS,IAAI,CAAC,MAAM,GAAG,EAAE,SAAS,OAAO,EAAE,YAAY,EAAE;AAAA,IAC3D;AAEA,WAAO;AAAA,EACT;AAEA,iBAAe,qBAAqB;AAClC,UAAM,UAAU,MAAM,mBAAmB,MAAM,OAAO;AACtD,UAAM,iBAAiB,oBAAI,IAA0B;AAErD,UAAM,aAAa,MAAM,uBAAuB;AAAA,MAC9C,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAED,eAAW,SAAS,SAAS;AAC3B,YAAM,MAAM,MAAM,WAAW,MAAM,WAAW,MAAM,YAAY;AAChE,qBAAe,IAAI,MAAM,WAAW,GAAG;AAAA,IACzC;AAEA,UAAM,QAAQ,MAAM,eAAe;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,EAAE,SAAS,gBAAgB,MAAM;AAAA,EAC1C;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,OAAO,YAAY,KAAK;AACtB,UAAI,IAAI,YAAY,QAAS;AAE7B,YAAM,mBAAmB,WAAW,OAAO,eAAe,SAAS;AACnE,UAAI,iBAAkB;AAEtB,aAAO;AAAA,QACL,OAAO;AAAA,UACL,eAAe;AAAA,YACb,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,UAAU,IAAI;AACZ,UAAI,OAAO,uBAAwB,QAAO;AAC1C,aAAO;AAAA,IACT;AAAA,IAEA,KAAK,IAAI;AACP,UAAI,OAAO,wBAAwB;AACjC,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,IAEA,eAAe,UAAU;AACvB,aAAO,SAAS;AAEhB,UAAI,CAAC,cAAc;AACjB,qBAAa,IAAI;AACjB,uBAAe;AAAA,MACjB;AAAA,IACF;AAAA,IAEA,MAAM,aAAa;AACjB,oBAAc,MAAM;AAEpB,YAAM,EAAE,SAAS,gBAAgB,MAAM,IAAI,MAAM,mBAAmB;AAEpE,iBAAW,SAAS,SAAS;AAC3B,aAAK,aAAa,MAAM,SAAS;AAAA,MACnC;AAEA,YAAM,gBAAgB,oBAAI,IAAgD;AAE1E,iBAAW,QAAQ,OAAO;AACxB,cAAM,MAAM,eAAe,IAAI,KAAK,SAAS;AAE7C,YAAI,CAAC,KAAK;AACR,gBAAM,IAAI;AAAA,YACR,IAAI,WAAW,oCAAoC,KAAK,SAAS;AAAA,UACnE;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,WAAW,MAAM,KAAK,KAAK;AAE9C,sBAAc,IAAI,KAAK,WAAW;AAAA,UAChC;AAAA,UACA,SAASC,MAAK,QAAQ,KAAK,YAAY;AAAA,QACzC,CAAC;AAAA,MACH;AAEA,sBAAgB,MAAM,qBAAqB;AAAA,QACzC,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED;AAAA,QACE,QAAQ;AAAA,QACR;AAAA,QACA,CAAC,GAAG,cAAc,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS;AAAA,UACxC,MAAM,IAAI;AAAA,UACV,aAAa,IAAI;AAAA,UACjB,cAAc,IAAI;AAAA,QACpB,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IAEA,gBAAgB,SAAS;AACvB,eAAS;AAET,uBAAiB;AAAA,QACf;AAAA,QACA,UAAU,YAAY;AACpB,cAAI,SAAS,SAAS,EAAG,QAAO;AAChC,iBAAO,aAAa;AAAA,QACtB;AAAA,QACA,YAAY,YAAY,mBAAmB,MAAM,OAAO;AAAA,MAC1D,CAAC;AAED,mBAAa,EAAE,MAAM,CAAC,UAAU;AAC9B,gBAAQ,OAAO,OAAO;AAAA,UACpB,IAAI,WAAW,0BACb,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,KAAK,CACtE;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,gBAAgB,KAAK;AACzB,UAAI,CAAC,OAAQ;AAEb,eAAS,QAAQ,OAAO,gBAAgB,IAAI,IAAI;AAEhD,YAAM,aAAa;AACnB,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,eAAe,GAAG,QAAQ;AAC9B,UAAI;AACF,cAAM,EAAE,gBAAgB,MAAM,IAAI,MAAM,mBAAmB;AAE3D,cAAM,oBAAoB,6BAA6B;AAAA,UACrD,KAAK;AAAA,UACL,MAAM;AAAA,QACR,CAAC;AAED;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,UACA,CAAC,GAAG,kBAAkB,QAAQ,CAAC;AAAA,QACjC;AAEA;AAAA,UACE,QAAQ;AAAA,UACR;AAAA,UACA,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ;AAAA,QAC7B;AAEA,cAAM,QAAQ,OAAO,QAAQ,qBAAqB,CAAC;AACnD,cAAM,YACJ,QAAQ,mBACR,KAAK,IAAI,QAAQ,qBAAqB,GAAG,EAAE;AAK7C,mBAAW,SAAS,WAAW,OAAO,SAAS,GAAG;AAChD,gBAAM,QAAQ;AAAA,YACZ,MAAM;AAAA,cAAI,CAAC,SACT,MAAM,YAAY;AAChB,sBAAM,MAAM,eAAe,IAAI,KAAK,SAAS;AAE7C,oBAAI,CAAC,KAAK;AACR,wBAAM,IAAI;AAAA,oBACR,IAAI,WAAW,oCAAoC,KAAK,SAAS;AAAA,kBACnE;AAAA,gBACF;AAEA,oBAAI,OAAO,MAAM,WAAW,MAAM,KAAK,KAAK;AAC5C,uBAAO,qBAAqB,MAAM,iBAAiB;AAEnD,qBAAK,SAAS;AAAA,kBACZ,MAAM;AAAA,kBACN,UAAU,QAAQ,gBAAgB,IAAI,KAAK,KAAK;AAAA,kBAChD,QAAQ;AAAA,gBACV,CAAC;AAAA,cACH,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAKA,cAAM,eAAe,MAAM,KAAK,CAAC,MAAM,EAAE,cAAc,MAAM;AAE7D,YAAI,cAAc;AAChB,gBAAM,MAAM,eAAe,IAAI,aAAa,SAAS;AAErD,cAAI,CAAC,KAAK;AACR,kBAAM,IAAI;AAAA,cACR,IAAI,WAAW,wCAAwC,aAAa,SAAS;AAAA,YAC/E;AAAA,UACF;AAEA,cAAI,OAAO,MAAM,WAAW,cAAc,KAAK,KAAK;AACpD,iBAAO,qBAAqB,MAAM,iBAAiB;AAEnD,eAAK,SAAS;AAAA,YACZ,MAAM;AAAA,YACN,UAAU;AAAA,YACV,QAAQ;AAAA,UACV,CAAC;AAED,mBAAS,QAAQ,OAAO,mCAAmC;AAAA,QAC7D,OAAO;AACL,gBAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CnB,eAAK,SAAS;AAAA,YACZ,MAAM;AAAA,YACN,UAAU;AAAA,YACV,QAAQ;AAAA,UACV,CAAC;AAED,mBAAS,QAAQ,OAAO,4BAA4B;AAAA,QACtD;AAKA,cAAM,cAAc,QAAQ,QAAQ;AAEpC,cAAM,gBAAgB,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE;AAAA,UAChE,CAAC,UAAU,CAAC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,SAAS,GAAG;AAAA,QACxD;AAEA,YAAI,eAAe,cAAc,SAAS,GAAG;AAC3C,gBAAM,UAAU;AAAA;AAAA,EAAyG,cACtH,IAAI,CAAC,UAAU,eAAe,WAAW,GAAG,KAAK,cAAc,EAC/D,KAAK,IAAI,CAAC;AAAA;AAAA;AAEb,eAAK,SAAS;AAAA,YACZ,MAAM;AAAA,YACN,UAAU;AAAA,YACV,QAAQ;AAAA,UACV,CAAC;AAED,mBAAS,QAAQ,OAAO,uBAAuB;AAAA,QACjD;AAKA,YAAI,QAAQ,KAAK,MAAM;AACrB,gBAAM,cAAc,QAAQ,IAAI,eAAe;AAE/C,gBAAM,WAAW,MACd,OAAO,CAAC,SAAS,KAAK,UAAU,WAAW,WAAW,CAAC,EACvD,IAAI,CAAC,SAAS;AACb,kBAAM,MAAM,GAAG,QAAQ,IAAK,IAAI,GAAG,KAAK,SAAS;AAEjD,mBAAO;AAAA,aAAwB,KAAK,SAAS;AAAA,YAAuB,GAAG;AAAA,YAAsB,GAAG;AAAA;AAAA,UAClG,CAAC,EACA,KAAK,IAAI;AAEZ,gBAAM,MAAM;AAAA;AAAA;AAAA,WAAoF,QAAQ,IAAI,SAAS,WAAW;AAAA,UAAqB,QAAQ,IAAI,IAAI;AAAA,iBAA2B,QAAQ,IAAI,eAAe,UAAU;AAAA,EAAmB,QAAQ;AAAA;AAAA;AAAA;AAEhQ,eAAK,SAAS;AAAA,YACZ,MAAM;AAAA,YACN,UAAU;AAAA,YACV,QAAQ;AAAA,UACV,CAAC;AAED,mBAAS,QAAQ,OAAO,mBAAmB;AAAA,QAC7C;AAKA,mBAAW,CAAC,UAAU,MAAM,KAAK,OAAO,QAAQ,MAAM,GAAG;AACvD,cACE,OAAO,SAAS,WAChB,OAAO,mBAAmB,wBAC1B;AACA,mBAAO,OAAO,QAAQ;AAAA,UACxB;AAAA,QACF;AAAA,MACF,UAAE;AACA,cAAM,sBAAsB;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AACF;;;AWrbA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,kBAAkB;AAmB3B,IAAM,cAAc,oBAAI,IAAkC;AAE1D,SAAS,sBACP,OACA,MACQ;AACR,QAAM,MAAM,KAAK,UAAU;AAAA,IACzB,KAAK,OAAO,KAAK;AAAA,IACjB,QAAQ,MAAM,UAAU;AAAA,IACxB,SAAS,MAAM,WAAW,CAAC;AAAA,IAC3B,MAAM,MAAM,QAAQ;AAAA,EACtB,CAAC;AAED,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AACtD;AAEA,SAAS,iBAAiB,UAA0B;AAClD,SAAOC,MAAK,KAAK,QAAQ,IAAI,GAAG,gBAAgB,SAAS,GAAG,QAAQ,OAAO;AAC7E;AAEA,SAAS,sBACP,MACiC;AACjC,MAAI,SAAS,YAAY,SAAS,QAAQ,SAAS,QAAQ;AACzD,WAAO;AAAA,EACT;AAEA,SAAO,QAAQ,IAAI,aAAa,eAAe,OAAO;AACxD;AAEA,SAAS,WAAW,QAAwC;AAC1D,SAAO,IAAI,SAAS,OAAO,MAAM;AAAA,IAC/B,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,IACnB,SAAS,OAAO;AAAA,EAClB,CAAC;AACH;AAEA,SAAS,QAAQ,QAA8B,eAAgC;AAC7E,QAAM,cAAc,KAAK,IAAI,IAAI,OAAO,aAAa;AACrD,SAAO,cAAc;AACvB;AAUA,eAAsB,eACpB,OACA,MACA,UAAiC,CAAC,GACf;AACnB,QAAM,SAAS,QAAQ,UAAU,KAAK;AACtC,QAAM,UAAU,MAAM,UAAU,OAAO,YAAY;AAEnD,MAAI,WAAW,SAAS,CAAC,QAAQ,UAAU;AACzC,WAAO,MAAM,OAAO,IAAI;AAAA,EAC1B;AAEA,QAAM,YAAY,sBAAsB,QAAQ,KAAK;AACrD,QAAM,WAAW,QAAQ,YAAY,sBAAsB,OAAO,IAAI;AAEtE,MAAI,cAAc,QAAQ;AACxB,WAAO,MAAM,OAAO,IAAI;AAAA,EAC1B;AAEA,MAAI,cAAc,YAAY,CAAC,QAAQ,cAAc;AACnD,UAAM,SAAS,YAAY,IAAI,QAAQ;AAEvC,QAAI,UAAU,QAAQ,QAAQ,MAAM,GAAG;AACrC,aAAO,WAAW,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,WAAW,iBAAiB,QAAQ;AAE1C,MAAI,cAAc,MAAM;AACtB,UAAMC,IAAG,MAAMC,MAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAE1D,QAAI,CAAC,QAAQ,cAAc;AACzB,UAAI;AACF,cAAM,MAAM,MAAMD,IAAG,SAAS,UAAU,MAAM;AAC9C,cAAM,SAAS,KAAK,MAAM,GAAG;AAE7B,YAAI,QAAQ,QAAQ,MAAM,GAAG;AAC3B,iBAAO,WAAW,MAAM;AAAA,QAC1B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,MAAM,MAAM,OAAO,IAAI;AACnC,QAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,QAAM,SAA+B;AAAA,IACnC,WAAW,KAAK,IAAI;AAAA,IACpB,QAAQ,IAAI;AAAA,IACZ,YAAY,IAAI;AAAA,IAChB,SAAS,CAAC,GAAG,IAAI,QAAQ,QAAQ,CAAC;AAAA,IAClC;AAAA,EACF;AAEA,MAAI,cAAc,UAAU;AAC1B,gBAAY,IAAI,UAAU,MAAM;AAAA,EAClC,WAAW,cAAc,MAAM;AAC7B,UAAMA,IAAG,UAAU,UAAU,KAAK,UAAU,MAAM,GAAG,MAAM;AAAA,EAC7D;AAEA,SAAO,IAAI,SAAS,MAAM;AAAA,IACxB,QAAQ,IAAI;AAAA,IACZ,YAAY,IAAI;AAAA,IAChB,SAAS,IAAI;AAAA,EACf,CAAC;AACH;","names":["path","path","path","path","fs","path","fs","path","path","fs","path"]}
|
package/package.json
CHANGED
package/src/assets.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { PluginContext } from 'rollup';
|
|
4
|
+
|
|
5
|
+
export type HtmlAssetKind = 'css' | 'js';
|
|
6
|
+
|
|
7
|
+
export interface HtmlAssetRef {
|
|
8
|
+
kind: HtmlAssetKind;
|
|
9
|
+
originalUrl: string;
|
|
10
|
+
absolutePath: string;
|
|
11
|
+
refId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ExtractedHtmlAsset {
|
|
15
|
+
kind: HtmlAssetKind;
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const EXTERNAL_URL_RE = /^(?:[a-z]+:)?\/\//i;
|
|
20
|
+
|
|
21
|
+
export function isLocalAssetUrl(url: string): boolean {
|
|
22
|
+
return (
|
|
23
|
+
!!url &&
|
|
24
|
+
!url.startsWith('data:') &&
|
|
25
|
+
!url.startsWith('mailto:') &&
|
|
26
|
+
!url.startsWith('tel:') &&
|
|
27
|
+
!url.startsWith('#') &&
|
|
28
|
+
!EXTERNAL_URL_RE.test(url)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function stripQueryAndHash(url: string): string {
|
|
33
|
+
return url.split('#')[0].split('?')[0];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractHtmlAssets(html: string): ExtractedHtmlAsset[] {
|
|
37
|
+
const assets: ExtractedHtmlAsset[] = [];
|
|
38
|
+
|
|
39
|
+
for (const match of html.matchAll(
|
|
40
|
+
/<link\b[^>]*\brel=["']stylesheet["'][^>]*\bhref=["']([^"']+)["'][^>]*>/gi,
|
|
41
|
+
)) {
|
|
42
|
+
assets.push({ kind: 'css', url: match[1] });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const match of html.matchAll(
|
|
46
|
+
/<script\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi,
|
|
47
|
+
)) {
|
|
48
|
+
assets.push({ kind: 'js', url: match[1] });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return dedupeExtractedAssets(assets);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dedupeExtractedAssets(
|
|
55
|
+
assets: ExtractedHtmlAsset[],
|
|
56
|
+
): ExtractedHtmlAsset[] {
|
|
57
|
+
const seen = new Set<string>();
|
|
58
|
+
const out: ExtractedHtmlAsset[] = [];
|
|
59
|
+
|
|
60
|
+
for (const asset of assets) {
|
|
61
|
+
const key = `${asset.kind}:${asset.url}`;
|
|
62
|
+
if (seen.has(key)) continue;
|
|
63
|
+
seen.add(key);
|
|
64
|
+
out.push(asset);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveLocalAssetPath(args: {
|
|
71
|
+
root: string;
|
|
72
|
+
pagesDir: string;
|
|
73
|
+
pageDir?: string;
|
|
74
|
+
url: string;
|
|
75
|
+
}): string | null {
|
|
76
|
+
const { root, pagesDir, pageDir, url } = args;
|
|
77
|
+
|
|
78
|
+
if (!isLocalAssetUrl(url)) return null;
|
|
79
|
+
|
|
80
|
+
const cleanUrl = stripQueryAndHash(url);
|
|
81
|
+
|
|
82
|
+
let abs: string;
|
|
83
|
+
if (cleanUrl.startsWith('/')) {
|
|
84
|
+
abs = path.join(root, pagesDir, cleanUrl.slice(1));
|
|
85
|
+
} else {
|
|
86
|
+
const baseDir = pageDir ?? path.join(root, pagesDir);
|
|
87
|
+
abs = path.resolve(baseDir, cleanUrl);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return fs.existsSync(abs) ? abs : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function emitHtmlAsset(args: {
|
|
94
|
+
ctx: PluginContext;
|
|
95
|
+
kind: HtmlAssetKind;
|
|
96
|
+
absolutePath: string;
|
|
97
|
+
}): string {
|
|
98
|
+
const { ctx, kind, absolutePath } = args;
|
|
99
|
+
|
|
100
|
+
if (kind === 'css' || kind === 'js') {
|
|
101
|
+
return ctx.emitFile({
|
|
102
|
+
type: 'chunk',
|
|
103
|
+
id: absolutePath,
|
|
104
|
+
name: path.basename(absolutePath, path.extname(absolutePath)),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
throw new Error(`[vite-plugin-html-pages] Unsupported asset kind: ${kind}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function replaceAllLiteral(
|
|
112
|
+
input: string,
|
|
113
|
+
search: string,
|
|
114
|
+
replacement: string,
|
|
115
|
+
): string {
|
|
116
|
+
return input.split(search).join(replacement);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function rewriteHtmlAssetUrls(
|
|
120
|
+
html: string,
|
|
121
|
+
replacements: Map<string, string>,
|
|
122
|
+
): string {
|
|
123
|
+
let out = html;
|
|
124
|
+
|
|
125
|
+
for (const [originalUrl, builtUrl] of replacements) {
|
|
126
|
+
out = replaceAllLiteral(
|
|
127
|
+
out,
|
|
128
|
+
`href="${originalUrl}"`,
|
|
129
|
+
`href="${builtUrl}"`,
|
|
130
|
+
);
|
|
131
|
+
out = replaceAllLiteral(
|
|
132
|
+
out,
|
|
133
|
+
`href='${originalUrl}'`,
|
|
134
|
+
`href='${builtUrl}'`,
|
|
135
|
+
);
|
|
136
|
+
out = replaceAllLiteral(
|
|
137
|
+
out,
|
|
138
|
+
`src="${originalUrl}"`,
|
|
139
|
+
`src="${builtUrl}"`,
|
|
140
|
+
);
|
|
141
|
+
out = replaceAllLiteral(
|
|
142
|
+
out,
|
|
143
|
+
`src='${originalUrl}'`,
|
|
144
|
+
`src='${builtUrl}'`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function collectHtmlAssetRefs(args: {
|
|
152
|
+
ctx: PluginContext;
|
|
153
|
+
root: string;
|
|
154
|
+
pagesDir: string;
|
|
155
|
+
htmlByPageKey: Map<string, { html: string; pageDir?: string }>;
|
|
156
|
+
}): Promise<Map<string, HtmlAssetRef>> {
|
|
157
|
+
const { ctx, root, pagesDir, htmlByPageKey } = args;
|
|
158
|
+
const refs = new Map<string, HtmlAssetRef>();
|
|
159
|
+
|
|
160
|
+
for (const { html, pageDir } of htmlByPageKey.values()) {
|
|
161
|
+
const assets = extractHtmlAssets(html);
|
|
162
|
+
|
|
163
|
+
for (const asset of assets) {
|
|
164
|
+
const abs = resolveLocalAssetPath({
|
|
165
|
+
root,
|
|
166
|
+
pagesDir,
|
|
167
|
+
pageDir,
|
|
168
|
+
url: asset.url,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!abs) continue;
|
|
172
|
+
|
|
173
|
+
const key = `${asset.kind}:${asset.url}`;
|
|
174
|
+
if (refs.has(key)) continue;
|
|
175
|
+
|
|
176
|
+
const refId = emitHtmlAsset({
|
|
177
|
+
ctx,
|
|
178
|
+
kind: asset.kind,
|
|
179
|
+
absolutePath: abs,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
refs.set(key, {
|
|
183
|
+
kind: asset.kind,
|
|
184
|
+
originalUrl: asset.url,
|
|
185
|
+
absolutePath: abs,
|
|
186
|
+
refId,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return refs;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function buildHtmlAssetReplacementMap(args: {
|
|
195
|
+
ctx: PluginContext;
|
|
196
|
+
refs: Map<string, HtmlAssetRef>;
|
|
197
|
+
}): Map<string, string> {
|
|
198
|
+
const { ctx, refs } = args;
|
|
199
|
+
const replacements = new Map<string, string>();
|
|
200
|
+
|
|
201
|
+
for (const ref of refs.values()) {
|
|
202
|
+
const fileName = ctx.getFileName(ref.refId);
|
|
203
|
+
replacements.set(ref.originalUrl, `/${fileName}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return replacements;
|
|
207
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -6,8 +6,14 @@ import { installDevServer } from './dev-server';
|
|
|
6
6
|
import { createPageModuleLoader, closePageModuleLoader } from './module-loader';
|
|
7
7
|
import { buildPageIndex } from './page-index';
|
|
8
8
|
import { renderPage } from './render-runtime';
|
|
9
|
+
import {
|
|
10
|
+
buildHtmlAssetReplacementMap,
|
|
11
|
+
collectHtmlAssetRefs,
|
|
12
|
+
rewriteHtmlAssetUrls,
|
|
13
|
+
} from './assets';
|
|
9
14
|
|
|
10
15
|
import type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';
|
|
16
|
+
import type { HtmlAssetRef } from './assets';
|
|
11
17
|
import { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID } from './constants';
|
|
12
18
|
|
|
13
19
|
import fs from 'node:fs';
|
|
@@ -45,8 +51,10 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
45
51
|
let root = process.cwd();
|
|
46
52
|
let server: ViteDevServer | null = null;
|
|
47
53
|
let devPages: HtPageInfo[] = [];
|
|
54
|
+
let htmlAssetRefs = new Map<string, HtmlAssetRef>();
|
|
48
55
|
|
|
49
56
|
const cleanUrls = options.cleanUrls ?? true;
|
|
57
|
+
const pagesDir = options.pagesDir ?? 'src';
|
|
50
58
|
|
|
51
59
|
function logDebug(enabled: boolean | undefined, ...args: unknown[]) {
|
|
52
60
|
if (!enabled) return;
|
|
@@ -151,15 +159,52 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
151
159
|
warnIfNotESM(root);
|
|
152
160
|
hasWarnedESM = true;
|
|
153
161
|
}
|
|
154
|
-
|
|
155
162
|
},
|
|
156
163
|
|
|
157
164
|
async buildStart() {
|
|
158
|
-
|
|
165
|
+
htmlAssetRefs.clear();
|
|
166
|
+
|
|
167
|
+
const { entries, modulesByEntry, pages } = await buildPagesPipeline();
|
|
159
168
|
|
|
160
169
|
for (const entry of entries) {
|
|
161
170
|
this.addWatchFile(entry.entryPath);
|
|
162
171
|
}
|
|
172
|
+
|
|
173
|
+
const htmlByPageKey = new Map<string, { html: string; pageDir?: string }>();
|
|
174
|
+
|
|
175
|
+
for (const page of pages) {
|
|
176
|
+
const mod = modulesByEntry.get(page.entryPath);
|
|
177
|
+
|
|
178
|
+
if (!mod) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const html = await renderPage(page, mod, false);
|
|
185
|
+
|
|
186
|
+
htmlByPageKey.set(page.entryPath, {
|
|
187
|
+
html,
|
|
188
|
+
pageDir: path.dirname(page.absolutePath),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
htmlAssetRefs = await collectHtmlAssetRefs({
|
|
193
|
+
ctx: this,
|
|
194
|
+
root,
|
|
195
|
+
pagesDir,
|
|
196
|
+
htmlByPageKey,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
logDebug(
|
|
200
|
+
options.debug,
|
|
201
|
+
'collected html assets',
|
|
202
|
+
[...htmlAssetRefs.values()].map((ref) => ({
|
|
203
|
+
kind: ref.kind,
|
|
204
|
+
originalUrl: ref.originalUrl,
|
|
205
|
+
absolutePath: ref.absolutePath,
|
|
206
|
+
})),
|
|
207
|
+
);
|
|
163
208
|
},
|
|
164
209
|
|
|
165
210
|
configureServer(_server) {
|
|
@@ -195,18 +240,29 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
195
240
|
async generateBundle(_, bundle) {
|
|
196
241
|
try {
|
|
197
242
|
const { modulesByEntry, pages } = await buildPagesPipeline();
|
|
198
|
-
|
|
243
|
+
|
|
244
|
+
const assetReplacements = buildHtmlAssetReplacementMap({
|
|
245
|
+
ctx: this,
|
|
246
|
+
refs: htmlAssetRefs,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
logDebug(
|
|
250
|
+
options.debug,
|
|
251
|
+
'asset replacements',
|
|
252
|
+
[...assetReplacements.entries()],
|
|
253
|
+
);
|
|
254
|
+
|
|
199
255
|
logDebug(
|
|
200
256
|
options.debug,
|
|
201
257
|
'emitting pages',
|
|
202
258
|
pages.map((p) => p.fileName),
|
|
203
259
|
);
|
|
204
|
-
|
|
260
|
+
|
|
205
261
|
const limit = pLimit(options.renderConcurrency ?? 8);
|
|
206
262
|
const batchSize =
|
|
207
263
|
options.renderBatchSize ??
|
|
208
264
|
Math.max(options.renderConcurrency ?? 8, 32);
|
|
209
|
-
|
|
265
|
+
|
|
210
266
|
// ---------------------------
|
|
211
267
|
// 1. Render all pages
|
|
212
268
|
// ---------------------------
|
|
@@ -215,15 +271,16 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
215
271
|
batch.map((page) =>
|
|
216
272
|
limit(async () => {
|
|
217
273
|
const mod = modulesByEntry.get(page.entryPath);
|
|
218
|
-
|
|
274
|
+
|
|
219
275
|
if (!mod) {
|
|
220
276
|
throw new Error(
|
|
221
277
|
`[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,
|
|
222
278
|
);
|
|
223
279
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
280
|
+
|
|
281
|
+
let html = await renderPage(page, mod, false);
|
|
282
|
+
html = rewriteHtmlAssetUrls(html, assetReplacements);
|
|
283
|
+
|
|
227
284
|
this.emitFile({
|
|
228
285
|
type: 'asset',
|
|
229
286
|
fileName: options.mapOutputPath?.(page) ?? page.fileName,
|
|
@@ -233,7 +290,7 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
233
290
|
),
|
|
234
291
|
);
|
|
235
292
|
}
|
|
236
|
-
|
|
293
|
+
|
|
237
294
|
// ---------------------------
|
|
238
295
|
// 2. 404.html
|
|
239
296
|
// ---------------------------
|
|
@@ -248,7 +305,8 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
248
305
|
);
|
|
249
306
|
}
|
|
250
307
|
|
|
251
|
-
|
|
308
|
+
let html = await renderPage(notFoundPage, mod, false);
|
|
309
|
+
html = rewriteHtmlAssetUrls(html, assetReplacements);
|
|
252
310
|
|
|
253
311
|
this.emitFile({
|
|
254
312
|
type: 'asset',
|
|
@@ -259,49 +317,49 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
259
317
|
logDebug(options.debug, 'generated 404.html from user page');
|
|
260
318
|
} else {
|
|
261
319
|
const default404 = `<!doctype html>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
320
|
+
<html lang="en">
|
|
321
|
+
<head>
|
|
322
|
+
<meta charset="UTF-8" />
|
|
323
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
324
|
+
<title>404 - Page Not Found</title>
|
|
325
|
+
<style>
|
|
326
|
+
:root {
|
|
327
|
+
color-scheme: light dark;
|
|
328
|
+
}
|
|
329
|
+
body {
|
|
330
|
+
margin: 0;
|
|
331
|
+
font-family: system-ui, sans-serif;
|
|
332
|
+
min-height: 100vh;
|
|
333
|
+
display: grid;
|
|
334
|
+
place-items: center;
|
|
335
|
+
padding: 2rem;
|
|
336
|
+
}
|
|
337
|
+
main {
|
|
338
|
+
max-width: 40rem;
|
|
339
|
+
text-align: center;
|
|
340
|
+
}
|
|
341
|
+
h1 {
|
|
342
|
+
font-size: 3rem;
|
|
343
|
+
margin: 0 0 1rem;
|
|
344
|
+
}
|
|
345
|
+
p {
|
|
346
|
+
margin: 0.5rem 0;
|
|
347
|
+
line-height: 1.5;
|
|
348
|
+
}
|
|
349
|
+
a {
|
|
350
|
+
color: inherit;
|
|
351
|
+
}
|
|
352
|
+
</style>
|
|
353
|
+
</head>
|
|
354
|
+
<body>
|
|
355
|
+
<main>
|
|
356
|
+
<h1>404</h1>
|
|
357
|
+
<p>Page not found.</p>
|
|
358
|
+
<p><a href="/">Go back home</a></p>
|
|
359
|
+
</main>
|
|
360
|
+
</body>
|
|
361
|
+
</html>
|
|
362
|
+
`;
|
|
305
363
|
|
|
306
364
|
this.emitFile({
|
|
307
365
|
type: 'asset',
|
|
@@ -311,56 +369,56 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
311
369
|
|
|
312
370
|
logDebug(options.debug, 'generated default 404.html');
|
|
313
371
|
}
|
|
314
|
-
|
|
372
|
+
|
|
315
373
|
// ---------------------------
|
|
316
374
|
// 3. Sitemap
|
|
317
375
|
// ---------------------------
|
|
318
376
|
const sitemapBase = options.site ?? '';
|
|
319
|
-
|
|
377
|
+
|
|
320
378
|
const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(
|
|
321
379
|
(route) => !route.includes(':') && !route.includes('*'),
|
|
322
380
|
);
|
|
323
|
-
|
|
324
|
-
if (sitemapRoutes.length > 0) {
|
|
381
|
+
|
|
382
|
+
if (sitemapBase && sitemapRoutes.length > 0) {
|
|
325
383
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${sitemapRoutes
|
|
326
384
|
.map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)
|
|
327
385
|
.join('\n')}\n</urlset>\n`;
|
|
328
|
-
|
|
386
|
+
|
|
329
387
|
this.emitFile({
|
|
330
388
|
type: 'asset',
|
|
331
389
|
fileName: 'sitemap.xml',
|
|
332
390
|
source: sitemap,
|
|
333
391
|
});
|
|
334
|
-
|
|
392
|
+
|
|
335
393
|
logDebug(options.debug, 'generated sitemap.xml');
|
|
336
394
|
}
|
|
337
|
-
|
|
395
|
+
|
|
338
396
|
// ---------------------------
|
|
339
397
|
// 4. RSS
|
|
340
398
|
// ---------------------------
|
|
341
399
|
if (options.rss?.site) {
|
|
342
400
|
const routePrefix = options.rss.routePrefix ?? '/blog';
|
|
343
|
-
|
|
401
|
+
|
|
344
402
|
const rssItems = pages
|
|
345
403
|
.filter((page) => page.routePath.startsWith(routePrefix))
|
|
346
404
|
.map((page) => {
|
|
347
405
|
const url = `${options.rss!.site}${page.routePath}`;
|
|
348
|
-
|
|
406
|
+
|
|
349
407
|
return ` <item>\n <title>${page.routePath}</title>\n <link>${url}</link>\n <guid>${url}</guid>\n </item>`;
|
|
350
408
|
})
|
|
351
409
|
.join('\n');
|
|
352
|
-
|
|
410
|
+
|
|
353
411
|
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`;
|
|
354
|
-
|
|
412
|
+
|
|
355
413
|
this.emitFile({
|
|
356
414
|
type: 'asset',
|
|
357
415
|
fileName: 'rss.xml',
|
|
358
416
|
source: rss,
|
|
359
417
|
});
|
|
360
|
-
|
|
418
|
+
|
|
361
419
|
logDebug(options.debug, 'generated rss.xml');
|
|
362
420
|
}
|
|
363
|
-
|
|
421
|
+
|
|
364
422
|
// ---------------------------
|
|
365
423
|
// 5. Remove virtual entry chunk
|
|
366
424
|
// ---------------------------
|
|
@@ -375,6 +433,8 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
|
|
|
375
433
|
} finally {
|
|
376
434
|
await closePageModuleLoader();
|
|
377
435
|
}
|
|
378
|
-
}
|
|
436
|
+
},
|
|
379
437
|
};
|
|
380
|
-
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export default htPages;
|