vite-plugin-htjs-pages 1.3.6 → 1.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/TODO +0 -17
- package/dist/index.d.ts +2 -0
- package/dist/index.js +43 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/fetch-cache.ts +65 -16
package/README.md
CHANGED
|
@@ -81,6 +81,7 @@ Get:
|
|
|
81
81
|
- Route groups `(admin)/users.ht.js`
|
|
82
82
|
- Index routes `blog/[slug]/index.ht.js`
|
|
83
83
|
- Static params generation
|
|
84
|
+
- Fetch caching for data loading
|
|
84
85
|
- Dev server SSR rendering
|
|
85
86
|
- Parallel static generation
|
|
86
87
|
- Automatic `404.html`
|
|
@@ -359,6 +360,48 @@ export async function data({ params }) {
|
|
|
359
360
|
|
|
360
361
|
---
|
|
361
362
|
|
|
363
|
+
# Caching
|
|
364
|
+
|
|
365
|
+
Use `fetchAndCache` for HTTP requests during static generation. Responses are
|
|
366
|
+
cached to avoid repeated network calls across page builds.
|
|
367
|
+
|
|
368
|
+
```js
|
|
369
|
+
import { fetchAndCache } from 'vite-plugin-htjs-pages'
|
|
370
|
+
|
|
371
|
+
export async function data({ params }) {
|
|
372
|
+
const res = await fetchAndCache(
|
|
373
|
+
`https://api.example.com/posts/${params.slug}`,
|
|
374
|
+
{
|
|
375
|
+
// fetch API options
|
|
376
|
+
},
|
|
377
|
+
{ maxAge: 3600 }
|
|
378
|
+
)
|
|
379
|
+
const post = await res.json()
|
|
380
|
+
return { post }
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Options
|
|
385
|
+
|
|
386
|
+
| Option | Description |
|
|
387
|
+
|--------|-------------|
|
|
388
|
+
| `maxAge` | Cache TTL in seconds (default: 3600) |
|
|
389
|
+
| `cacheKey` | Custom cache key (default: hash of URL + method + headers) |
|
|
390
|
+
| `forceRefresh` | Bypass cache and fetch fresh |
|
|
391
|
+
| `cache` | `'auto'` \| `'memory'` \| `'fs'` \| `'none'` |
|
|
392
|
+
|
|
393
|
+
## Cache modes
|
|
394
|
+
|
|
395
|
+
- **`auto`** (default): memory in dev, filesystem in production
|
|
396
|
+
- **`memory`**: in-process cache, cleared when the build process exits
|
|
397
|
+
- **`fs`**: persisted under `node_modules/.cache/vite-plugin-htjs-pages/fetch/`
|
|
398
|
+
- **`none`**: no caching, always fetches
|
|
399
|
+
|
|
400
|
+
Only `GET` requests are cached by default. For other methods, provide a
|
|
401
|
+
`cacheKey` to enable caching.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
362
405
|
# Layouts
|
|
363
406
|
|
|
364
407
|
Reusable layout functions work naturally with HT.js.
|
package/TODO
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
- caching for API fetching inside generateStaticParams() and data() like getCachedData.js
|
|
2
|
-
- `export const dynamic = true` to disable static generation for specific routes
|
|
3
|
-
|
|
4
|
-
const cache = new Map();
|
|
5
|
-
const fetchWithCache = async (url, options = {}) => {
|
|
6
|
-
|
|
7
|
-
// try `node_modules/.cache/${cacheKey}`
|
|
8
|
-
|
|
9
|
-
const cacheKey = `${url}-${JSON.stringify(options)}`
|
|
10
|
-
if (cache.has(cacheKey)) {
|
|
11
|
-
return cache.get(cacheKey)
|
|
12
|
-
}
|
|
13
|
-
const response = await fetch(url, options)
|
|
14
|
-
const json = await response.json()
|
|
15
|
-
cache.set(cacheKey, json)
|
|
16
|
-
return json
|
|
17
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -51,10 +51,12 @@ interface HtPagesPluginOptions {
|
|
|
51
51
|
|
|
52
52
|
declare function htPages(options?: HtPagesPluginOptions): Plugin$1;
|
|
53
53
|
|
|
54
|
+
type FetchCacheMode = 'auto' | 'memory' | 'fs' | 'none';
|
|
54
55
|
interface FetchAndCacheOptions {
|
|
55
56
|
maxAge?: number;
|
|
56
57
|
cacheKey?: string;
|
|
57
58
|
forceRefresh?: boolean;
|
|
59
|
+
cache?: FetchCacheMode;
|
|
58
60
|
}
|
|
59
61
|
declare function fetchAndCache(input: RequestInfo | URL, init?: RequestInit, options?: FetchAndCacheOptions): Promise<Response>;
|
|
60
62
|
|
package/dist/index.js
CHANGED
|
@@ -552,6 +552,7 @@ ${rssItems}
|
|
|
552
552
|
import fs2 from "fs/promises";
|
|
553
553
|
import path4 from "path";
|
|
554
554
|
import { createHash } from "crypto";
|
|
555
|
+
var memoryCache = /* @__PURE__ */ new Map();
|
|
555
556
|
function createDefaultCacheKey(input, init) {
|
|
556
557
|
const raw = JSON.stringify({
|
|
557
558
|
url: String(input),
|
|
@@ -564,28 +565,52 @@ function createDefaultCacheKey(input, init) {
|
|
|
564
565
|
function getCacheFilePath(cacheKey) {
|
|
565
566
|
return path4.join(process.cwd(), CACHE_DIR_NAME, "fetch", `${cacheKey}.json`);
|
|
566
567
|
}
|
|
568
|
+
function getEffectiveCacheMode(mode) {
|
|
569
|
+
if (mode === "memory" || mode === "fs" || mode === "none") {
|
|
570
|
+
return mode;
|
|
571
|
+
}
|
|
572
|
+
return process.env.NODE_ENV === "production" ? "fs" : "memory";
|
|
573
|
+
}
|
|
574
|
+
function toResponse(cached) {
|
|
575
|
+
return new Response(cached.body, {
|
|
576
|
+
status: cached.status,
|
|
577
|
+
statusText: cached.statusText,
|
|
578
|
+
headers: cached.headers
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
function isFresh(cached, maxAgeSeconds) {
|
|
582
|
+
const ageSeconds = (Date.now() - cached.timestamp) / 1e3;
|
|
583
|
+
return ageSeconds <= maxAgeSeconds;
|
|
584
|
+
}
|
|
567
585
|
async function fetchAndCache(input, init, options = {}) {
|
|
568
586
|
const maxAge = options.maxAge ?? 60 * 60;
|
|
569
587
|
const method = (init?.method ?? "GET").toUpperCase();
|
|
570
588
|
if (method !== "GET" && !options.cacheKey) {
|
|
571
589
|
return fetch(input, init);
|
|
572
590
|
}
|
|
591
|
+
const cacheMode = getEffectiveCacheMode(options.cache);
|
|
573
592
|
const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);
|
|
593
|
+
if (cacheMode === "none") {
|
|
594
|
+
return fetch(input, init);
|
|
595
|
+
}
|
|
596
|
+
if (cacheMode === "memory" && !options.forceRefresh) {
|
|
597
|
+
const cached = memoryCache.get(cacheKey);
|
|
598
|
+
if (cached && isFresh(cached, maxAge)) {
|
|
599
|
+
return toResponse(cached);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
574
602
|
const filePath = getCacheFilePath(cacheKey);
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
headers: cached.headers
|
|
586
|
-
});
|
|
603
|
+
if (cacheMode === "fs") {
|
|
604
|
+
await fs2.mkdir(path4.dirname(filePath), { recursive: true });
|
|
605
|
+
if (!options.forceRefresh) {
|
|
606
|
+
try {
|
|
607
|
+
const raw = await fs2.readFile(filePath, "utf8");
|
|
608
|
+
const cached = JSON.parse(raw);
|
|
609
|
+
if (isFresh(cached, maxAge)) {
|
|
610
|
+
return toResponse(cached);
|
|
611
|
+
}
|
|
612
|
+
} catch {
|
|
587
613
|
}
|
|
588
|
-
} catch {
|
|
589
614
|
}
|
|
590
615
|
}
|
|
591
616
|
const res = await fetch(input, init);
|
|
@@ -597,7 +622,11 @@ async function fetchAndCache(input, init, options = {}) {
|
|
|
597
622
|
headers: [...res.headers.entries()],
|
|
598
623
|
body
|
|
599
624
|
};
|
|
600
|
-
|
|
625
|
+
if (cacheMode === "memory") {
|
|
626
|
+
memoryCache.set(cacheKey, record);
|
|
627
|
+
} else if (cacheMode === "fs") {
|
|
628
|
+
await fs2.writeFile(filePath, JSON.stringify(record), "utf8");
|
|
629
|
+
}
|
|
601
630
|
return new Response(body, {
|
|
602
631
|
status: res.status,
|
|
603
632
|
statusText: res.statusText,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts","../src/discover.ts","../src/path-utils.ts","../src/route-utils.ts","../src/constants.ts","../src/errors.ts","../src/render-runtime.ts","../src/dev-server.ts","../src/page-index.ts","../src/fetch-cache.ts"],"sourcesContent":["import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { pathToFileURL } from 'node:url';\nimport pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { buildPageIndex } from './page-index';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport {\n PLUGIN_NAME,\n VIRTUAL_BUILD_ENTRY_ID,\n} from './constants';\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n out.push(items.slice(i, i + size));\n }\n return out;\n}\n\nasync function importPageModule(entryPath: string): Promise<HtPageModule> {\n const mod = await import(pathToFileURL(entryPath).href + `?t=${Date.now()}`);\n return mod as HtPageModule;\n}\n\nasync function warnIfNotEsm(root: string): Promise<void> {\n try {\n const pkgPath = path.join(root, 'package.json');\n const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));\n\n if (pkg.type !== 'module') {\n console.warn(\n `[${PLUGIN_NAME}] Tip: add \"type\": \"module\" to package.json to avoid Node ESM warnings.`,\n );\n }\n } catch {\n // ignore\n }\n}\n\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n\n const cleanUrls = options.cleanUrls ?? true;\n\n function logDebug(enabled: boolean | undefined, ...args: unknown[]) {\n if (!enabled) return;\n console.log(`[${PLUGIN_NAME}]`, ...args);\n }\n\n async function loadDevPages(): Promise<HtPageInfo[]> {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n logDebug(\n options.debug,\n 'discovered entries',\n entries.map((e) => e.relativePath),\n );\n\n if (!server) return [];\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n devPages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n logDebug(\n options.debug,\n 'dev pages',\n devPages.map((p) => `${p.routePath} -> ${p.relativePath}`),\n );\n\n return devPages;\n }\n\n async function buildPagesPipeline() {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n for (const entry of entries) {\n const mod = await importPageModule(entry.entryPath);\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 void warnIfNotEsm(root);\n },\n\n async buildStart() {\n const entries = await discoverEntryPages(root, options);\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n },\n\n configureServer(_server) {\n server = _server;\n\n installDevServer({\n server,\n getPages: async () => {\n if (devPages.length > 0) return devPages;\n return loadDevPages();\n },\n getEntries: async () => discoverEntryPages(root, options),\n });\n\n loadDevPages().catch((error) => {\n server?.config.logger.error(\n `[${PLUGIN_NAME}] loadDevPages failed: ${\n error instanceof Error ? error.stack ?? error.message : String(error)\n }`,\n );\n });\n },\n\n async handleHotUpdate(ctx) {\n if (!server) return;\n\n if (!ctx.file.endsWith('.ht.js')) {\n return;\n }\n\n logDebug(options.debug, 'page updated', ctx.file);\n\n await loadDevPages();\n return undefined;\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n\n logDebug(\n options.debug,\n 'emitting pages',\n pages.map((p) => p.fileName),\n );\n\n const limit = pLimit(options.renderConcurrency ?? 8);\n const batchSize =\n options.renderBatchSize ??\n Math.max(options.renderConcurrency ?? 8, 32);\n\n for (const batch of chunkArray(pages, batchSize)) {\n await Promise.all(\n batch.map((page) =>\n limit(async () => {\n const mod = modulesByEntry.get(page.entryPath);\n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,\n );\n }\n\n const html = await renderPage(page, mod, false);\n\n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n\n const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(\n (route) => !route.includes(':') && !route.includes('*'),\n );\n\n if (sitemapRoutes.length > 0) {\n const sitemap = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${sitemapRoutes\n .map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)\n .join('\\n')}\\n</urlset>\\n`;\n\n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n });\n }\n\n if (options.rss?.site) {\n const routePrefix = options.rss.routePrefix ?? '/blog';\n\n const rssItems = pages\n .filter((page) => page.routePath.startsWith(routePrefix))\n .map((page) => {\n const url = `${options.rss!.site}${page.routePath}`;\n return ` <item>\\n <title>${page.routePath}</title>\\n <link>${url}</link>\\n <guid>${url}</guid>\\n </item>`;\n })\n .join('\\n');\n\n const rss = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<rss version=\"2.0\">\\n<channel>\\n <title>${options.rss.title ?? PLUGIN_NAME}</title>\\n <link>${options.rss.site}</link>\\n <description>${options.rss.description ?? 'RSS feed'}</description>\\n${rssItems}\\n</channel>\\n</rss>\\n`;\n\n this.emitFile({\n type: 'asset',\n fileName: 'rss.xml',\n source: rss,\n });\n }\n\n for (const [fileName, output] of Object.entries(bundle)) {\n if (\n output.type === 'chunk' &&\n output.facadeModuleId === VIRTUAL_BUILD_ENTRY_ID\n ) {\n delete bundle[fileName];\n }\n }\n },\n };\n}\n","import path from 'node:path';\nimport { normalizeFsPath, toPosix } from './path-utils';\nimport { getParamNames, isDynamicPage, toRoutePattern } from './route-utils';\nimport type { HtPageInfo, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME } from './constants';\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const fgModule = await import('fast-glob');\n const fg = (fgModule.default ?? fgModule) as typeof import('fast-glob');\n\n const include = Array.isArray(options.include)\n ? options.include\n : [options.include ?? 'src/**/*.ht.js'];\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesDir = options.pagesDir ?? 'src';\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg.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);\n\n return {\n id: entryPath,\n entryPath,\n absolutePath: entryPath,\n relativePath,\n routePattern,\n routePath: routePattern,\n fileName: '',\n dynamic,\n paramNames: getParamNames(relativeFromPagesDir),\n params: {},\n } satisfies HtPageInfo;\n });\n}","import path from 'node:path';\n\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\nexport function stripHtSuffix(file: string): string {\n return file.replace(/\\.ht\\.js$/i, '');\n}\n\nexport function normalizeRoutePath(p: string): string {\n let out = p.startsWith('/') ? p : `/${p}`;\n out = out.replace(/\\/+/g, '/');\n if (out !== '/' && out.endsWith('/')) out = out.slice(0, -1);\n return out;\n}\n\nexport function normalizeFsPath(p: string): string {\n return toPosix(path.resolve(p));\n}","import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';\nimport type { HtPageInfo, StaticParamRecord } from './types';\n\nfunction safeDecodeURIComponent(str: string): string {\n try {\n return decodeURIComponent(str);\n } catch {\n return str;\n }\n}\n\nconst DYNAMIC_SEGMENT_RE = /\\[([A-Za-z0-9_]+)\\]/g;\nconst CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]/g;\nconst OPTIONAL_CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]\\?/g;\nconst ANY_PARAM_RE = /\\[(?:\\.\\.\\.)?([A-Za-z0-9_]+)\\]\\??/g;\nconst ROUTE_GROUP_RE = /(^|\\/)\\(([^)]+)\\)(?=\\/|$)/g;\n\nexport function getParamNames(relativeFromPagesDir: string): string[] {\n return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);\n}\n\nexport function isDynamicPage(relativeFromPagesDir: string): boolean {\n return /\\[(?:\\.\\.\\.)?[A-Za-z0-9_]+\\]\\??/.test(relativeFromPagesDir);\n}\n\nexport function toRoutePattern(relativeFromPagesDir: string): string {\n const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));\n\n const withoutGroups = noExt.replace(ROUTE_GROUP_RE, '$1');\n const withoutIndex = withoutGroups.replace(/\\/index$/i, '').replace(/^index$/i, '');\n\n const raw = withoutIndex\n .replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, '*?:$1')\n .replace(CATCH_ALL_SEGMENT_RE, '*:$1')\n .replace(DYNAMIC_SEGMENT_RE, ':$1');\n\n return normalizeRoutePath(raw || '/');\n}\n\nexport function fillParams(\n pattern: string,\n params: Record<string, string>,\n): string {\n const result = pattern\n .replace(/\\*\\?:([A-Za-z0-9_]+)/g, (_, key) => {\n const value = params[key];\n if (value == null || value === '') {\n return '';\n }\n\n return String(value)\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/\\*:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing catch-all route param \"${key}\"`);\n }\n\n return String(params[key])\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing route param \"${key}\"`);\n }\n\n return encodeURIComponent(params[key]);\n });\n\n return normalizeRoutePath(result || '/');\n}\n\nexport function fileNameFromRoute(\n routePath: string,\n cleanUrls: boolean,\n): string {\n const normalized = normalizeRoutePath(routePath);\n\n if (normalized === '/') return 'index.html';\n\n const base = normalized.slice(1);\n return cleanUrls ? `${base}/index.html` : `${base}.html`;\n}\n\nexport function expandStaticPaths(\n basePage: Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows: StaticParamRecord[],\n cleanUrls: boolean,\n): HtPageInfo[] {\n return rows.map((row) => {\n const params = Object.fromEntries(\n Object.entries(row).map(([k, v]) => [k, String(v)]),\n );\n\n const routePath = fillParams(basePage.routePattern, params);\n\n return {\n ...basePage,\n routePath,\n fileName: fileNameFromRoute(routePath, cleanUrls),\n params,\n };\n });\n}\n\nexport function routeMatch(\n pattern: string,\n urlPath: string,\n): Record<string, string> | null {\n const a = normalizeRoutePath(pattern).split('/').filter(Boolean);\n const b = normalizeRoutePath(urlPath).split('/').filter(Boolean);\n const params: Record<string, string> = {};\n\n for (let i = 0; i < a.length; i++) {\n const patternSeg = a[i];\n const urlSeg = b[i];\n\n if (patternSeg.startsWith('*?:')) {\n params[patternSeg.slice(3)] =\n i < b.length ? b.slice(i).map(safeDecodeURIComponent).join('/') : '';\n return params;\n }\n\n if (patternSeg.startsWith('*:')) {\n const rest = b.slice(i);\n if (rest.length === 0) return null;\n\n params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join('/');\n return params;\n }\n\n if (!urlSeg) return null;\n\n if (patternSeg.startsWith(':')) {\n params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);\n continue;\n }\n\n if (patternSeg !== urlSeg) return null;\n }\n\n return a.length === b.length ? params : null;\n}\n\nexport function compareRoutePriority(a: string, b: string): number {\n const aSegs = normalizeRoutePath(a).split('/').filter(Boolean);\n const bSegs = normalizeRoutePath(b).split('/').filter(Boolean);\n const len = Math.max(aSegs.length, bSegs.length);\n\n for (let i = 0; i < len; i++) {\n const aa = aSegs[i];\n const bb = bSegs[i];\n\n if (aa == null) return 1;\n if (bb == null) return -1;\n\n const aOptionalCatchAll = aa.startsWith('*?:');\n const bOptionalCatchAll = bb.startsWith('*?:');\n if (aOptionalCatchAll !== bOptionalCatchAll) {\n return aOptionalCatchAll ? 1 : -1;\n }\n\n const aCatchAll = aa.startsWith('*:');\n const bCatchAll = bb.startsWith('*:');\n if (aCatchAll !== bCatchAll) {\n return aCatchAll ? 1 : -1;\n }\n\n const aDynamic = aa.startsWith(':');\n const bDynamic = bb.startsWith(':');\n if (aDynamic !== bDynamic) {\n return aDynamic ? 1 : -1;\n }\n }\n\n // More specific / longer routes first when otherwise equal\n return bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-htjs-pages';\nexport const VIRTUAL_BUILD_ENTRY_ID = `\\0${PLUGIN_NAME}:build-entry`;\nexport const VIRTUAL_MANIFEST_ID = `\\0virtual:${PLUGIN_NAME}-manifest`;\nexport const CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;","import type { HtPageInfo } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport function invalidHtmlReturn(\n page: HtPageInfo,\n value: unknown,\n): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" must resolve to an HTML string, got ${typeof value}`,\n );\n}\n\nexport function missingDefaultExport(page: HtPageInfo): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" does not export a default renderer`,\n );\n}\n\nexport function pageError(page: HtPageInfo, cause: unknown): Error {\n const message = `[${PLUGIN_NAME}] Failed to render \"${page.relativePath}\" at route \"${page.routePath}\"`;\n\n if (cause instanceof Error) {\n const err = new Error(`${message}: ${cause.message}`);\n\n if (cause.stack) {\n err.stack = `${err.stack}\\nCaused by:\\n${cause.stack}`;\n }\n\n return err;\n }\n\n return new Error(`${message}: ${String(cause)}`);\n}","import { invalidHtmlReturn, pageError, missingDefaultExport } from './errors';\nimport type { HtPageInfo, HtPageModule, HtPageRenderContext } from './types';\n\nexport async function renderPage(\n page: HtPageInfo,\n mod: HtPageModule,\n dev = false,\n): Promise<string> {\n const ctx: HtPageRenderContext = {\n page,\n params: page.params,\n dev,\n };\n\n try {\n if (typeof mod.data === 'function') {\n ctx.data = await mod.data(ctx);\n }\n\n const entry = mod.default;\n\n if (entry == null) {\n throw missingDefaultExport(page);\n }\n\n const html = typeof entry === 'function' ? await entry(ctx) : entry;\n\n if (typeof html !== 'string') {\n throw invalidHtmlReturn(page, html);\n }\n\n return html;\n } catch (error) {\n throw pageError(page, error);\n }\n}","import type { ViteDevServer } from 'vite';\nimport { renderPage } from './render-runtime';\nimport { routeMatch } from './route-utils';\nimport type { HtPageInfo, HtPageModule } from './types';\n\nfunction isDynamicOnly(mod: HtPageModule): boolean {\n return mod.dynamic === true || mod.prerender === false;\n}\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n getEntries: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages, getEntries } = args;\n\n server.middlewares.use(async (req, res, next) => {\n try {\n if (!req.url || req.method !== 'GET') return next();\n\n const pathname = req.url.split('?')[0];\n\n const pages = await getPages();\n const staticPage = pages.find((p) => p.routePath === pathname);\n\n if (staticPage) {\n const mod = (await server.ssrLoadModule(\n `/${staticPage.relativePath}`,\n )) as HtPageModule;\n\n const html = await renderPage(staticPage, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n const entries = await getEntries();\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n if (!isDynamicOnly(mod)) continue;\n\n const params = routeMatch(entry.routePattern, pathname);\n if (!params) continue;\n\n const page: HtPageInfo = {\n ...entry,\n routePath: pathname,\n fileName: '',\n params,\n };\n\n const html = await renderPage(page, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n });\n}","import {\n compareRoutePriority,\n expandStaticPaths,\n fileNameFromRoute,\n} from './route-utils';\nimport type { HtPageInfo, HtPageModule, StaticParamRecord } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport async function buildPageIndex(args: {\n entries: HtPageInfo[];\n modulesByEntry: Map<string, HtPageModule>;\n cleanUrls: boolean;\n}): Promise<HtPageInfo[]> {\n const { entries, modulesByEntry, cleanUrls } = args;\n const pages: HtPageInfo[] = [];\n\n for (const entry of entries) {\n const mod = modulesByEntry.get(entry.entryPath) ?? {};\n\n if (entry.dynamic) {\n const rows =\n (mod.generateStaticParams\n ? await mod.generateStaticParams()\n : []) ?? [];\n\n pages.push(\n ...expandStaticPaths(\n {\n id: entry.id,\n entryPath: entry.entryPath,\n absolutePath: entry.absolutePath,\n relativePath: entry.relativePath,\n routePattern: entry.routePattern,\n dynamic: entry.dynamic,\n paramNames: entry.paramNames,\n } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n Array.isArray(rows) ? rows : [],\n cleanUrls,\n ),\n );\n } else {\n pages.push({\n ...entry,\n routePath: entry.routePattern,\n fileName: fileNameFromRoute(entry.routePattern, cleanUrls),\n params: {},\n });\n }\n }\n\n pages.sort((a, b) => compareRoutePriority(a.routePattern, b.routePattern));\n\n const seenRoutes = new Map<string, HtPageInfo>();\n\n for (const page of pages) {\n const existing = seenRoutes.get(page.routePath);\n\n if (existing) {\n throw new Error(\n `[${PLUGIN_NAME}] Duplicate route generated: \"${page.routePath}\" from \"${existing.relativePath}\" and \"${page.relativePath}\"`,\n );\n }\n\n seenRoutes.set(page.routePath, page);\n }\n\n return pages;\n}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { CACHE_DIR_NAME } from './constants';\n\nexport interface FetchAndCacheOptions {\n maxAge?: number;\n cacheKey?: string;\n forceRefresh?: boolean;\n}\n\ntype CachedResponseRecord = {\n timestamp: number;\n status: number;\n statusText: string;\n headers: [string, string][];\n body: string;\n};\n\nfunction createDefaultCacheKey(\n input: RequestInfo | URL,\n init?: RequestInit,\n): string {\n const raw = JSON.stringify({\n url: String(input),\n method: init?.method ?? 'GET',\n headers: init?.headers ?? {},\n body: init?.body ?? null,\n });\n\n return createHash('sha256').update(raw).digest('hex');\n}\n\nfunction getCacheFilePath(cacheKey: string): string {\n return path.join(process.cwd(), CACHE_DIR_NAME, 'fetch', `${cacheKey}.json`);\n}\n\nexport async function fetchAndCache(\n input: RequestInfo | URL,\n init?: RequestInit,\n options: FetchAndCacheOptions = {},\n): Promise<Response> {\n const maxAge = options.maxAge ?? 60 * 60;\n const method = (init?.method ?? 'GET').toUpperCase();\n\n if (method !== 'GET' && !options.cacheKey) {\n return fetch(input, init);\n }\n\n const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);\n const filePath = getCacheFilePath(cacheKey);\n\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n\n if (!options.forceRefresh) {\n try {\n const raw = await fs.readFile(filePath, 'utf8');\n const cached = JSON.parse(raw) as CachedResponseRecord;\n\n const ageSeconds = (Date.now() - cached.timestamp) / 1000;\n\n if (ageSeconds <= maxAge) {\n return new Response(cached.body, {\n status: cached.status,\n statusText: cached.statusText,\n headers: cached.headers,\n });\n }\n } catch {\n // cache miss or invalid cache; fetch fresh\n }\n }\n\n const res = await fetch(input, init);\n const body = await res.text();\n\n const record: CachedResponseRecord = {\n timestamp: Date.now(),\n status: res.status,\n statusText: res.statusText,\n headers: [...res.headers.entries()],\n body,\n };\n\n await fs.writeFile(filePath, JSON.stringify(record), 'utf8');\n\n return new Response(body, {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n}\n"],"mappings":"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","names":["path","path","path","path","fs","path","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts","../src/discover.ts","../src/path-utils.ts","../src/route-utils.ts","../src/constants.ts","../src/errors.ts","../src/render-runtime.ts","../src/dev-server.ts","../src/page-index.ts","../src/fetch-cache.ts"],"sourcesContent":["import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { pathToFileURL } from 'node:url';\nimport pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { buildPageIndex } from './page-index';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport {\n PLUGIN_NAME,\n VIRTUAL_BUILD_ENTRY_ID,\n} from './constants';\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n out.push(items.slice(i, i + size));\n }\n return out;\n}\n\nasync function importPageModule(entryPath: string): Promise<HtPageModule> {\n const mod = await import(pathToFileURL(entryPath).href + `?t=${Date.now()}`);\n return mod as HtPageModule;\n}\n\nasync function warnIfNotEsm(root: string): Promise<void> {\n try {\n const pkgPath = path.join(root, 'package.json');\n const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));\n\n if (pkg.type !== 'module') {\n console.warn(\n `[${PLUGIN_NAME}] Tip: add \"type\": \"module\" to package.json to avoid Node ESM warnings.`,\n );\n }\n } catch {\n // ignore\n }\n}\n\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n\n const cleanUrls = options.cleanUrls ?? true;\n\n function logDebug(enabled: boolean | undefined, ...args: unknown[]) {\n if (!enabled) return;\n console.log(`[${PLUGIN_NAME}]`, ...args);\n }\n\n async function loadDevPages(): Promise<HtPageInfo[]> {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n logDebug(\n options.debug,\n 'discovered entries',\n entries.map((e) => e.relativePath),\n );\n\n if (!server) return [];\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n devPages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n logDebug(\n options.debug,\n 'dev pages',\n devPages.map((p) => `${p.routePath} -> ${p.relativePath}`),\n );\n\n return devPages;\n }\n\n async function buildPagesPipeline() {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n for (const entry of entries) {\n const mod = await importPageModule(entry.entryPath);\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 void warnIfNotEsm(root);\n },\n\n async buildStart() {\n const entries = await discoverEntryPages(root, options);\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n },\n\n configureServer(_server) {\n server = _server;\n\n installDevServer({\n server,\n getPages: async () => {\n if (devPages.length > 0) return devPages;\n return loadDevPages();\n },\n getEntries: async () => discoverEntryPages(root, options),\n });\n\n loadDevPages().catch((error) => {\n server?.config.logger.error(\n `[${PLUGIN_NAME}] loadDevPages failed: ${\n error instanceof Error ? error.stack ?? error.message : String(error)\n }`,\n );\n });\n },\n\n async handleHotUpdate(ctx) {\n if (!server) return;\n\n if (!ctx.file.endsWith('.ht.js')) {\n return;\n }\n\n logDebug(options.debug, 'page updated', ctx.file);\n\n await loadDevPages();\n return undefined;\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n\n logDebug(\n options.debug,\n 'emitting pages',\n pages.map((p) => p.fileName),\n );\n\n const limit = pLimit(options.renderConcurrency ?? 8);\n const batchSize =\n options.renderBatchSize ??\n Math.max(options.renderConcurrency ?? 8, 32);\n\n for (const batch of chunkArray(pages, batchSize)) {\n await Promise.all(\n batch.map((page) =>\n limit(async () => {\n const mod = modulesByEntry.get(page.entryPath);\n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,\n );\n }\n\n const html = await renderPage(page, mod, false);\n\n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n\n const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))].filter(\n (route) => !route.includes(':') && !route.includes('*'),\n );\n\n if (sitemapRoutes.length > 0) {\n const sitemap = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n${sitemapRoutes\n .map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)\n .join('\\n')}\\n</urlset>\\n`;\n\n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n });\n }\n\n if (options.rss?.site) {\n const routePrefix = options.rss.routePrefix ?? '/blog';\n\n const rssItems = pages\n .filter((page) => page.routePath.startsWith(routePrefix))\n .map((page) => {\n const url = `${options.rss!.site}${page.routePath}`;\n return ` <item>\\n <title>${page.routePath}</title>\\n <link>${url}</link>\\n <guid>${url}</guid>\\n </item>`;\n })\n .join('\\n');\n\n const rss = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<rss version=\"2.0\">\\n<channel>\\n <title>${options.rss.title ?? PLUGIN_NAME}</title>\\n <link>${options.rss.site}</link>\\n <description>${options.rss.description ?? 'RSS feed'}</description>\\n${rssItems}\\n</channel>\\n</rss>\\n`;\n\n this.emitFile({\n type: 'asset',\n fileName: 'rss.xml',\n source: rss,\n });\n }\n\n for (const [fileName, output] of Object.entries(bundle)) {\n if (\n output.type === 'chunk' &&\n output.facadeModuleId === VIRTUAL_BUILD_ENTRY_ID\n ) {\n delete bundle[fileName];\n }\n }\n },\n };\n}\n","import path from 'node:path';\nimport { normalizeFsPath, toPosix } from './path-utils';\nimport { getParamNames, isDynamicPage, toRoutePattern } from './route-utils';\nimport type { HtPageInfo, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME } from './constants';\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const fgModule = await import('fast-glob');\n const fg = (fgModule.default ?? fgModule) as typeof import('fast-glob');\n\n const include = Array.isArray(options.include)\n ? options.include\n : [options.include ?? 'src/**/*.ht.js'];\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesDir = options.pagesDir ?? 'src';\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg.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);\n\n return {\n id: entryPath,\n entryPath,\n absolutePath: entryPath,\n relativePath,\n routePattern,\n routePath: routePattern,\n fileName: '',\n dynamic,\n paramNames: getParamNames(relativeFromPagesDir),\n params: {},\n } satisfies HtPageInfo;\n });\n}","import path from 'node:path';\n\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\nexport function stripHtSuffix(file: string): string {\n return file.replace(/\\.ht\\.js$/i, '');\n}\n\nexport function normalizeRoutePath(p: string): string {\n let out = p.startsWith('/') ? p : `/${p}`;\n out = out.replace(/\\/+/g, '/');\n if (out !== '/' && out.endsWith('/')) out = out.slice(0, -1);\n return out;\n}\n\nexport function normalizeFsPath(p: string): string {\n return toPosix(path.resolve(p));\n}","import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';\nimport type { HtPageInfo, StaticParamRecord } from './types';\n\nfunction safeDecodeURIComponent(str: string): string {\n try {\n return decodeURIComponent(str);\n } catch {\n return str;\n }\n}\n\nconst DYNAMIC_SEGMENT_RE = /\\[([A-Za-z0-9_]+)\\]/g;\nconst CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]/g;\nconst OPTIONAL_CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]\\?/g;\nconst ANY_PARAM_RE = /\\[(?:\\.\\.\\.)?([A-Za-z0-9_]+)\\]\\??/g;\nconst ROUTE_GROUP_RE = /(^|\\/)\\(([^)]+)\\)(?=\\/|$)/g;\n\nexport function getParamNames(relativeFromPagesDir: string): string[] {\n return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);\n}\n\nexport function isDynamicPage(relativeFromPagesDir: string): boolean {\n return /\\[(?:\\.\\.\\.)?[A-Za-z0-9_]+\\]\\??/.test(relativeFromPagesDir);\n}\n\nexport function toRoutePattern(relativeFromPagesDir: string): string {\n const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));\n\n const withoutGroups = noExt.replace(ROUTE_GROUP_RE, '$1');\n const withoutIndex = withoutGroups.replace(/\\/index$/i, '').replace(/^index$/i, '');\n\n const raw = withoutIndex\n .replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, '*?:$1')\n .replace(CATCH_ALL_SEGMENT_RE, '*:$1')\n .replace(DYNAMIC_SEGMENT_RE, ':$1');\n\n return normalizeRoutePath(raw || '/');\n}\n\nexport function fillParams(\n pattern: string,\n params: Record<string, string>,\n): string {\n const result = pattern\n .replace(/\\*\\?:([A-Za-z0-9_]+)/g, (_, key) => {\n const value = params[key];\n if (value == null || value === '') {\n return '';\n }\n\n return String(value)\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/\\*:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing catch-all route param \"${key}\"`);\n }\n\n return String(params[key])\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing route param \"${key}\"`);\n }\n\n return encodeURIComponent(params[key]);\n });\n\n return normalizeRoutePath(result || '/');\n}\n\nexport function fileNameFromRoute(\n routePath: string,\n cleanUrls: boolean,\n): string {\n const normalized = normalizeRoutePath(routePath);\n\n if (normalized === '/') return 'index.html';\n\n const base = normalized.slice(1);\n return cleanUrls ? `${base}/index.html` : `${base}.html`;\n}\n\nexport function expandStaticPaths(\n basePage: Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows: StaticParamRecord[],\n cleanUrls: boolean,\n): HtPageInfo[] {\n return rows.map((row) => {\n const params = Object.fromEntries(\n Object.entries(row).map(([k, v]) => [k, String(v)]),\n );\n\n const routePath = fillParams(basePage.routePattern, params);\n\n return {\n ...basePage,\n routePath,\n fileName: fileNameFromRoute(routePath, cleanUrls),\n params,\n };\n });\n}\n\nexport function routeMatch(\n pattern: string,\n urlPath: string,\n): Record<string, string> | null {\n const a = normalizeRoutePath(pattern).split('/').filter(Boolean);\n const b = normalizeRoutePath(urlPath).split('/').filter(Boolean);\n const params: Record<string, string> = {};\n\n for (let i = 0; i < a.length; i++) {\n const patternSeg = a[i];\n const urlSeg = b[i];\n\n if (patternSeg.startsWith('*?:')) {\n params[patternSeg.slice(3)] =\n i < b.length ? b.slice(i).map(safeDecodeURIComponent).join('/') : '';\n return params;\n }\n\n if (patternSeg.startsWith('*:')) {\n const rest = b.slice(i);\n if (rest.length === 0) return null;\n\n params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join('/');\n return params;\n }\n\n if (!urlSeg) return null;\n\n if (patternSeg.startsWith(':')) {\n params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);\n continue;\n }\n\n if (patternSeg !== urlSeg) return null;\n }\n\n return a.length === b.length ? params : null;\n}\n\nexport function compareRoutePriority(a: string, b: string): number {\n const aSegs = normalizeRoutePath(a).split('/').filter(Boolean);\n const bSegs = normalizeRoutePath(b).split('/').filter(Boolean);\n const len = Math.max(aSegs.length, bSegs.length);\n\n for (let i = 0; i < len; i++) {\n const aa = aSegs[i];\n const bb = bSegs[i];\n\n if (aa == null) return 1;\n if (bb == null) return -1;\n\n const aOptionalCatchAll = aa.startsWith('*?:');\n const bOptionalCatchAll = bb.startsWith('*?:');\n if (aOptionalCatchAll !== bOptionalCatchAll) {\n return aOptionalCatchAll ? 1 : -1;\n }\n\n const aCatchAll = aa.startsWith('*:');\n const bCatchAll = bb.startsWith('*:');\n if (aCatchAll !== bCatchAll) {\n return aCatchAll ? 1 : -1;\n }\n\n const aDynamic = aa.startsWith(':');\n const bDynamic = bb.startsWith(':');\n if (aDynamic !== bDynamic) {\n return aDynamic ? 1 : -1;\n }\n }\n\n // More specific / longer routes first when otherwise equal\n return bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-htjs-pages';\nexport const VIRTUAL_BUILD_ENTRY_ID = `\\0${PLUGIN_NAME}:build-entry`;\nexport const VIRTUAL_MANIFEST_ID = `\\0virtual:${PLUGIN_NAME}-manifest`;\nexport const CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;","import type { HtPageInfo } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport function invalidHtmlReturn(\n page: HtPageInfo,\n value: unknown,\n): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" must resolve to an HTML string, got ${typeof value}`,\n );\n}\n\nexport function missingDefaultExport(page: HtPageInfo): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" does not export a default renderer`,\n );\n}\n\nexport function pageError(page: HtPageInfo, cause: unknown): Error {\n const message = `[${PLUGIN_NAME}] Failed to render \"${page.relativePath}\" at route \"${page.routePath}\"`;\n\n if (cause instanceof Error) {\n const err = new Error(`${message}: ${cause.message}`);\n\n if (cause.stack) {\n err.stack = `${err.stack}\\nCaused by:\\n${cause.stack}`;\n }\n\n return err;\n }\n\n return new Error(`${message}: ${String(cause)}`);\n}","import { invalidHtmlReturn, pageError, missingDefaultExport } from './errors';\nimport type { HtPageInfo, HtPageModule, HtPageRenderContext } from './types';\n\nexport async function renderPage(\n page: HtPageInfo,\n mod: HtPageModule,\n dev = false,\n): Promise<string> {\n const ctx: HtPageRenderContext = {\n page,\n params: page.params,\n dev,\n };\n\n try {\n if (typeof mod.data === 'function') {\n ctx.data = await mod.data(ctx);\n }\n\n const entry = mod.default;\n\n if (entry == null) {\n throw missingDefaultExport(page);\n }\n\n const html = typeof entry === 'function' ? await entry(ctx) : entry;\n\n if (typeof html !== 'string') {\n throw invalidHtmlReturn(page, html);\n }\n\n return html;\n } catch (error) {\n throw pageError(page, error);\n }\n}","import type { ViteDevServer } from 'vite';\nimport { renderPage } from './render-runtime';\nimport { routeMatch } from './route-utils';\nimport type { HtPageInfo, HtPageModule } from './types';\n\nfunction isDynamicOnly(mod: HtPageModule): boolean {\n return mod.dynamic === true || mod.prerender === false;\n}\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n getEntries: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages, getEntries } = args;\n\n server.middlewares.use(async (req, res, next) => {\n try {\n if (!req.url || req.method !== 'GET') return next();\n\n const pathname = req.url.split('?')[0];\n\n const pages = await getPages();\n const staticPage = pages.find((p) => p.routePath === pathname);\n\n if (staticPage) {\n const mod = (await server.ssrLoadModule(\n `/${staticPage.relativePath}`,\n )) as HtPageModule;\n\n const html = await renderPage(staticPage, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n const entries = await getEntries();\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n if (!isDynamicOnly(mod)) continue;\n\n const params = routeMatch(entry.routePattern, pathname);\n if (!params) continue;\n\n const page: HtPageInfo = {\n ...entry,\n routePath: pathname,\n fileName: '',\n params,\n };\n\n const html = await renderPage(page, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n });\n}","import {\n compareRoutePriority,\n expandStaticPaths,\n fileNameFromRoute,\n} from './route-utils';\nimport type { HtPageInfo, HtPageModule, StaticParamRecord } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport async function buildPageIndex(args: {\n entries: HtPageInfo[];\n modulesByEntry: Map<string, HtPageModule>;\n cleanUrls: boolean;\n}): Promise<HtPageInfo[]> {\n const { entries, modulesByEntry, cleanUrls } = args;\n const pages: HtPageInfo[] = [];\n\n for (const entry of entries) {\n const mod = modulesByEntry.get(entry.entryPath) ?? {};\n\n if (entry.dynamic) {\n const rows =\n (mod.generateStaticParams\n ? await mod.generateStaticParams()\n : []) ?? [];\n\n pages.push(\n ...expandStaticPaths(\n {\n id: entry.id,\n entryPath: entry.entryPath,\n absolutePath: entry.absolutePath,\n relativePath: entry.relativePath,\n routePattern: entry.routePattern,\n dynamic: entry.dynamic,\n paramNames: entry.paramNames,\n } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n Array.isArray(rows) ? rows : [],\n cleanUrls,\n ),\n );\n } else {\n pages.push({\n ...entry,\n routePath: entry.routePattern,\n fileName: fileNameFromRoute(entry.routePattern, cleanUrls),\n params: {},\n });\n }\n }\n\n pages.sort((a, b) => compareRoutePriority(a.routePattern, b.routePattern));\n\n const seenRoutes = new Map<string, HtPageInfo>();\n\n for (const page of pages) {\n const existing = seenRoutes.get(page.routePath);\n\n if (existing) {\n throw new Error(\n `[${PLUGIN_NAME}] Duplicate route generated: \"${page.routePath}\" from \"${existing.relativePath}\" and \"${page.relativePath}\"`,\n );\n }\n\n seenRoutes.set(page.routePath, page);\n }\n\n return pages;\n}","import 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 FetchAndCacheOptions {\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 fetchAndCache(\n input: RequestInfo | URL,\n init?: RequestInit,\n options: FetchAndCacheOptions = {},\n): Promise<Response> {\n const maxAge = options.maxAge ?? 60 * 60;\n const method = (init?.method ?? 'GET').toUpperCase();\n\n if (method !== 'GET' && !options.cacheKey) {\n return fetch(input, init);\n }\n\n const 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"]}
|
package/package.json
CHANGED
package/src/fetch-cache.ts
CHANGED
|
@@ -3,10 +3,12 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { CACHE_DIR_NAME } from './constants';
|
|
5
5
|
|
|
6
|
+
export type FetchCacheMode = 'auto' | 'memory' | 'fs' | 'none';
|
|
6
7
|
export interface FetchAndCacheOptions {
|
|
7
8
|
maxAge?: number;
|
|
8
9
|
cacheKey?: string;
|
|
9
10
|
forceRefresh?: boolean;
|
|
11
|
+
cache?: FetchCacheMode;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
type CachedResponseRecord = {
|
|
@@ -17,6 +19,8 @@ type CachedResponseRecord = {
|
|
|
17
19
|
body: string;
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
const memoryCache = new Map<string, CachedResponseRecord>();
|
|
23
|
+
|
|
20
24
|
function createDefaultCacheKey(
|
|
21
25
|
input: RequestInfo | URL,
|
|
22
26
|
init?: RequestInit,
|
|
@@ -35,6 +39,37 @@ function getCacheFilePath(cacheKey: string): string {
|
|
|
35
39
|
return path.join(process.cwd(), CACHE_DIR_NAME, 'fetch', `${cacheKey}.json`);
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
function getEffectiveCacheMode(
|
|
43
|
+
mode: FetchCacheMode | undefined,
|
|
44
|
+
): Exclude<FetchCacheMode, 'auto'> {
|
|
45
|
+
if (mode === 'memory' || mode === 'fs' || mode === 'none') {
|
|
46
|
+
return mode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return process.env.NODE_ENV === 'production' ? 'fs' : 'memory';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toResponse(cached: CachedResponseRecord): Response {
|
|
53
|
+
return new Response(cached.body, {
|
|
54
|
+
status: cached.status,
|
|
55
|
+
statusText: cached.statusText,
|
|
56
|
+
headers: cached.headers,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isFresh(cached: CachedResponseRecord, maxAgeSeconds: number): boolean {
|
|
61
|
+
const ageSeconds = (Date.now() - cached.timestamp) / 1000;
|
|
62
|
+
return ageSeconds <= maxAgeSeconds;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function clearMemoryFetchCache(): void {
|
|
66
|
+
memoryCache.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function deleteMemoryFetchCache(cacheKey: string): void {
|
|
70
|
+
memoryCache.delete(cacheKey);
|
|
71
|
+
}
|
|
72
|
+
|
|
38
73
|
export async function fetchAndCache(
|
|
39
74
|
input: RequestInfo | URL,
|
|
40
75
|
init?: RequestInit,
|
|
@@ -47,27 +82,37 @@ export async function fetchAndCache(
|
|
|
47
82
|
return fetch(input, init);
|
|
48
83
|
}
|
|
49
84
|
|
|
85
|
+
const cacheMode = getEffectiveCacheMode(options.cache);
|
|
50
86
|
const cacheKey = options.cacheKey ?? createDefaultCacheKey(input, init);
|
|
51
|
-
const filePath = getCacheFilePath(cacheKey);
|
|
52
87
|
|
|
53
|
-
|
|
88
|
+
if (cacheMode === 'none') {
|
|
89
|
+
return fetch(input, init);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (cacheMode === 'memory' && !options.forceRefresh) {
|
|
93
|
+
const cached = memoryCache.get(cacheKey);
|
|
54
94
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
95
|
+
if (cached && isFresh(cached, maxAge)) {
|
|
96
|
+
return toResponse(cached);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const filePath = getCacheFilePath(cacheKey);
|
|
101
|
+
|
|
102
|
+
if (cacheMode === 'fs') {
|
|
103
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
59
104
|
|
|
60
|
-
|
|
105
|
+
if (!options.forceRefresh) {
|
|
106
|
+
try {
|
|
107
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
108
|
+
const cached = JSON.parse(raw) as CachedResponseRecord;
|
|
61
109
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
110
|
+
if (isFresh(cached, maxAge)) {
|
|
111
|
+
return toResponse(cached);
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// cache miss or invalid cache; fetch fresh
|
|
68
115
|
}
|
|
69
|
-
} catch {
|
|
70
|
-
// cache miss or invalid cache; fetch fresh
|
|
71
116
|
}
|
|
72
117
|
}
|
|
73
118
|
|
|
@@ -82,7 +127,11 @@ export async function fetchAndCache(
|
|
|
82
127
|
body,
|
|
83
128
|
};
|
|
84
129
|
|
|
85
|
-
|
|
130
|
+
if (cacheMode === 'memory') {
|
|
131
|
+
memoryCache.set(cacheKey, record);
|
|
132
|
+
} else if (cacheMode === 'fs') {
|
|
133
|
+
await fs.writeFile(filePath, JSON.stringify(record), 'utf8');
|
|
134
|
+
}
|
|
86
135
|
|
|
87
136
|
return new Response(body, {
|
|
88
137
|
status: res.status,
|