vite-plugin-htjs-pages 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,32 +27,46 @@ JavaScript functions returning HTML.
27
27
 
28
28
  # TL;DR
29
29
 
30
- Write this:
30
+ Write:
31
31
 
32
32
  ```js
33
33
  // src/index.ht.js
34
- import { fragment, html, body, h1 } from "javascript-to-html"
34
+
35
+ import { fragment, html, body, head, title, h1 } from 'javascript-to-html'
35
36
 
36
37
  export default () => fragment(
37
- "<!doctype html>",
38
- html(
38
+ '<!doctype html>',
39
+ html({lang: "en"},
40
+ head(
41
+ title("My website")
42
+ ),
39
43
  body(
40
- h1("Hello world")
44
+ h1('Hello world')
41
45
  )
42
46
  )
43
47
  )
44
48
  ```
45
49
 
46
- Run:
50
+ Run:
47
51
 
48
- ```bash
52
+ ``` bash
49
53
  vite build
50
54
  ```
51
55
 
52
56
  Get:
53
57
 
54
- ```
55
- dist/index.html
58
+ ```html
59
+ <!-- dist/index.html -->
60
+
61
+ <!doctype html>
62
+ <html lang="en">
63
+ <head>
64
+ <title>My website</title>
65
+ </head>
66
+ <body>
67
+ <h1>Hello world</h1>
68
+ </body>
69
+ </html>
56
70
  ```
57
71
 
58
72
  ---
@@ -153,15 +167,16 @@ src/
153
167
 
154
168
  Routes are generated directly from the filesystem.
155
169
 
156
- | File | URL |
157
- |-----|-----|
158
- | `index.ht.js` | `/` |
159
- | `about.ht.js` | `/about` |
160
- | `blog/[slug].ht.js` | `/blog/my-post` |
161
- | `blog/[year]/[slug].ht.js` | `/blog/2026/my-post` |
162
- | `docs/[...slug].ht.js` | `/docs/api/auth/login` |
163
- | `docs/[...slug]?.ht.js` | `/docs` or `/docs/getting-started` |
164
- | `(admin)/users.ht.js` | `/users` |
170
+
171
+ | Feature | File | URL |
172
+ |-----|-----|-----|
173
+ | static routes | `index.ht.js` | `/` |
174
+ | dynamic routes | `blog/[slug].ht.js` | `/blog/my-post` |
175
+ | multiple params | `blog/[year]/[slug].ht.js` | `/blog/2026/my-post` |
176
+ | catch-all | `docs/[...slug].ht.js` | `/docs/api/auth/login` |
177
+ | optional catch-all | `docs/[...slug]?.ht.js` | `/docs` or `/docs/getting-started` |
178
+ | index routes | `products/[product]/index.ht.js` | `/products/iphone-18` |
179
+ | route groups | `(admin)/users.ht.js` | `/users` |
165
180
 
166
181
  ---
167
182
 
@@ -212,13 +227,35 @@ Matches:
212
227
 
213
228
  ```
214
229
  /blog/2026/vite-routing
230
+ /blog/2025/my-first-post
215
231
  ```
216
232
 
217
- Params:
233
+ Example:
218
234
 
219
- ```
220
- params.year
221
- params.slug
235
+ ``` js
236
+ import { fragment, html, body, h1 } from 'javascript-to-html'
237
+
238
+ export function generateStaticParams() {
239
+ return [
240
+ {
241
+ year: 2026,
242
+ slug: "vite-routing"
243
+ },
244
+ {
245
+ year: 2025,
246
+ slug: 'my-first-post'
247
+ }
248
+ ]
249
+ }
250
+
251
+ export default ({ params }) => fragment(
252
+ '<!doctype html>',
253
+ html(
254
+ body(
255
+ h1(params.slug)
256
+ )
257
+ )
258
+ )
222
259
  ```
223
260
 
224
261
  ---
package/TODO ADDED
@@ -0,0 +1,2 @@
1
+ - caching for API fetching inside generateStaticParams() and data() like getCachedData.js
2
+ - `export const dynamic = true` to disable static generation for specific routes
package/dist/index.js CHANGED
@@ -27,6 +27,13 @@ function normalizeFsPath(p) {
27
27
  }
28
28
 
29
29
  // src/route-utils.ts
30
+ function safeDecodeURIComponent(str) {
31
+ try {
32
+ return decodeURIComponent(str);
33
+ } catch {
34
+ return str;
35
+ }
36
+ }
30
37
  var DYNAMIC_SEGMENT_RE = /\[([A-Za-z0-9_]+)\]/g;
31
38
  var CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]/g;
32
39
  var OPTIONAL_CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]\?/g;
@@ -93,18 +100,18 @@ function routeMatch(pattern, urlPath) {
93
100
  const patternSeg = a[i];
94
101
  const urlSeg = b[i];
95
102
  if (patternSeg.startsWith("*?:")) {
96
- params[patternSeg.slice(3)] = i < b.length ? b.slice(i).map(decodeURIComponent).join("/") : "";
103
+ params[patternSeg.slice(3)] = i < b.length ? b.slice(i).map(safeDecodeURIComponent).join("/") : "";
97
104
  return params;
98
105
  }
99
106
  if (patternSeg.startsWith("*:")) {
100
107
  const rest = b.slice(i);
101
108
  if (rest.length === 0) return null;
102
- params[patternSeg.slice(2)] = rest.map(decodeURIComponent).join("/");
109
+ params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join("/");
103
110
  return params;
104
111
  }
105
112
  if (!urlSeg) return null;
106
113
  if (patternSeg.startsWith(":")) {
107
- params[patternSeg.slice(1)] = decodeURIComponent(urlSeg);
114
+ params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);
108
115
  continue;
109
116
  }
110
117
  if (patternSeg !== urlSeg) return null;
@@ -147,7 +154,11 @@ var CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;
147
154
 
148
155
  // src/discover.ts
149
156
  async function discoverEntryPages(root, options) {
150
- const include = Array.isArray(options.include) ? options.include : [options.include ?? "src/**/*.ht.js"];
157
+ const rawInclude = Array.isArray(options.include) ? options.include : [options.include ?? "src/**/*.ht.js"];
158
+ let include = rawInclude.filter((p) => typeof p === "string" && p.length > 0);
159
+ if (include.length === 0) {
160
+ include = ["src/**/*.ht.js"];
161
+ }
151
162
  const exclude = Array.isArray(options.exclude) ? options.exclude : options.exclude ? [options.exclude] : [];
152
163
  const pagesDir = options.pagesDir ?? "src";
153
164
  const pagesRoot = normalizeFsPath(path2.join(root, pagesDir));
@@ -271,7 +282,7 @@ async function buildPageIndex(args) {
271
282
  for (const entry of entries) {
272
283
  const mod = modulesByEntry.get(entry.entryPath) ?? {};
273
284
  if (entry.dynamic) {
274
- const rows = mod.generateStaticParams ? await mod.generateStaticParams() : [];
285
+ const rows = (mod.generateStaticParams ? await mod.generateStaticParams() : []) ?? [];
275
286
  pages.push(
276
287
  ...expandStaticPaths(
277
288
  {
@@ -283,7 +294,7 @@ async function buildPageIndex(args) {
283
294
  dynamic: entry.dynamic,
284
295
  paramNames: entry.paramNames
285
296
  },
286
- rows,
297
+ Array.isArray(rows) ? rows : [],
287
298
  cleanUrls
288
299
  )
289
300
  );
@@ -388,12 +399,16 @@ async function buildRenderBundle(args) {
388
399
 
389
400
  // src/plugin.ts
390
401
  function chunkArray(items, size) {
402
+ const safeSize = Math.max(1, Math.floor(size));
391
403
  const out = [];
392
- for (let i = 0; i < items.length; i += size) {
393
- out.push(items.slice(i, i + size));
404
+ for (let i = 0; i < items.length; i += safeSize) {
405
+ out.push(items.slice(i, i + safeSize));
394
406
  }
395
407
  return out;
396
408
  }
409
+ function escapeXml(text) {
410
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
411
+ }
397
412
  function createEntriesKey(entries) {
398
413
  const raw = entries.map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`).join("\n");
399
414
  return createHash2("sha256").update(raw).digest("hex");
@@ -408,12 +423,22 @@ function htPages(options = {}) {
408
423
  let devPages = [];
409
424
  let cachedManifestKey = null;
410
425
  let cachedBundlePath = null;
426
+ let loadDevPagesInFlight = null;
411
427
  const cleanUrls = options.cleanUrls ?? true;
412
428
  function logDebug(enabled, ...args) {
413
429
  if (!enabled) return;
414
430
  console.log(`[${PLUGIN_NAME}]`, ...args);
415
431
  }
416
432
  async function loadDevPages() {
433
+ if (loadDevPagesInFlight) return loadDevPagesInFlight;
434
+ loadDevPagesInFlight = doLoadDevPages();
435
+ try {
436
+ return await loadDevPagesInFlight;
437
+ } finally {
438
+ loadDevPagesInFlight = null;
439
+ }
440
+ }
441
+ async function doLoadDevPages() {
417
442
  const entries = await discoverEntryPages(root, options);
418
443
  const modulesByEntry = /* @__PURE__ */ new Map();
419
444
  logDebug(options.debug, "discovered entries", entries.map((e) => e.relativePath));
@@ -531,8 +556,12 @@ function htPages(options = {}) {
531
556
  async generateBundle(_, bundle) {
532
557
  const { modulesByEntry, pages } = await buildPagesPipeline();
533
558
  logDebug(options.debug, "emitting pages", pages.map((p) => p.fileName));
534
- const limit = pLimit(options.renderConcurrency ?? 8);
535
- const batchSize = options.renderBatchSize ?? Math.max(options.renderConcurrency ?? 8, 32);
559
+ const concurrency = Math.max(1, options.renderConcurrency ?? 8);
560
+ const limit = pLimit(concurrency);
561
+ const batchSize = Math.max(
562
+ 1,
563
+ options.renderBatchSize ?? Math.max(concurrency, 32)
564
+ );
536
565
  for (const batch of chunkArray(pages, batchSize)) {
537
566
  await Promise.all(
538
567
  batch.map(
@@ -558,7 +587,9 @@ function htPages(options = {}) {
558
587
  if (sitemapRoutes.length > 0) {
559
588
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
560
589
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
561
- ${sitemapRoutes.map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`).join("\n")}
590
+ ${sitemapRoutes.map(
591
+ (route) => ` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`
592
+ ).join("\n")}
562
593
  </urlset>
563
594
  `;
564
595
  this.emitFile({
@@ -572,17 +603,17 @@ function htPages(options = {}) {
572
603
  const rssItems = pages.filter((page) => page.routePath.startsWith(routePrefix)).map((page) => {
573
604
  const url = `${options.rss.site}${page.routePath}`;
574
605
  return ` <item>
575
- <title>${page.routePath}</title>
576
- <link>${url}</link>
577
- <guid>${url}</guid>
606
+ <title>${escapeXml(page.routePath)}</title>
607
+ <link>${escapeXml(url)}</link>
608
+ <guid>${escapeXml(url)}</guid>
578
609
  </item>`;
579
610
  }).join("\n");
580
611
  const rss = `<?xml version="1.0" encoding="UTF-8"?>
581
612
  <rss version="2.0">
582
613
  <channel>
583
- <title>${options.rss.title ?? PLUGIN_NAME}</title>
584
- <link>${options.rss.site}</link>
585
- <description>${options.rss.description ?? "RSS feed"}</description>
614
+ <title>${escapeXml(options.rss.title ?? PLUGIN_NAME)}</title>
615
+ <link>${escapeXml(options.rss.site)}</link>
616
+ <description>${escapeXml(options.rss.description ?? "RSS feed")}</description>
586
617
  ${rssItems}
587
618
  </channel>
588
619
  </rss>
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts","../src/discover.ts","../src/path-utils.ts","../src/route-utils.ts","../src/constants.ts","../src/errors.ts","../src/render-runtime.ts","../src/dev-server.ts","../src/page-index.ts","../src/render-bundle.ts","../src/manifest.ts"],"sourcesContent":["import path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { createHash } from 'node:crypto';\nimport pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { buildPageIndex } from './page-index';\nimport { buildRenderBundle } from './render-bundle';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID, CACHE_DIR_NAME } from './constants';\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += size) {\n out.push(items.slice(i, i + size));\n }\n return out;\n}\n\nfunction createEntriesKey(entries: HtPageInfo[]): string {\n const raw = entries\n .map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)\n .join('\\n');\n\n return createHash('sha256').update(raw).digest('hex');\n}\n\nasync function importManifest(\n bundlePath: string,\n): Promise<Array<{ page: HtPageInfo; mod: HtPageModule }>> {\n const mod = await import(pathToFileURL(bundlePath).href + `?t=${Date.now()}`);\n return mod.manifest as Array<{ page: HtPageInfo; mod: HtPageModule }>;\n}\n\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n\n let cachedManifestKey: string | null = null;\n let cachedBundlePath: string | null = null;\n\n const cleanUrls = options.cleanUrls ?? true;\n\n function logDebug(enabled: boolean | undefined, ...args: unknown[]) {\n if (!enabled) return;\n console.log(`[${PLUGIN_NAME}]`, ...args);\n }\n\n async function loadDevPages(): Promise<HtPageInfo[]> {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n logDebug(options.debug, 'discovered entries', entries.map((e) => e.relativePath));\n\n if (!server) return [];\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n devPages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n logDebug(\n options.debug,\n 'dev pages',\n devPages.map((p) => `${p.routePath} -> ${p.relativePath}`),\n );\n\n return devPages;\n }\n\n async function buildPagesPipeline() {\n const entries = await discoverEntryPages(root, options);\n const cacheDir = path.join(root, CACHE_DIR_NAME);\n\n const entriesKey = createEntriesKey(entries);\n\n let bundlePath: string;\n if (cachedBundlePath && cachedManifestKey === entriesKey) {\n bundlePath = cachedBundlePath;\n } else {\n bundlePath = await buildRenderBundle({\n entries,\n cacheDir,\n ssrPlugins: options.ssrPlugins,\n });\n cachedManifestKey = entriesKey;\n cachedBundlePath = bundlePath;\n }\n\n logDebug(options.debug, 'render bundle', bundlePath);\n\n const manifest = await importManifest(bundlePath);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n for (const rec of manifest) {\n modulesByEntry.set(rec.page.entryPath, rec.mod);\n }\n\n const pages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n // Ensure static hosts get a 404.html\n const notFoundPage = pages.find((p) => p.routePath === '/404');\n\n if (notFoundPage && !pages.some((p) => p.fileName === '404.html')) {\n pages.push({\n ...notFoundPage,\n fileName: '404.html',\n });\n } \n\n return { entries, bundlePath, modulesByEntry, pages };\n }\n\n return {\n name: PLUGIN_NAME,\n\n config(userConfig, env) {\n if (env.command !== 'build') return;\n\n const hasExplicitInput = userConfig.build?.rollupOptions?.input != null;\n if (hasExplicitInput) return;\n\n return {\n build: {\n rollupOptions: {\n input: VIRTUAL_BUILD_ENTRY_ID,\n },\n },\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) return id;\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) {\n return 'export default {};';\n }\n return null;\n },\n\n configResolved(resolved) {\n root = resolved.root;\n },\n\n async buildStart() {\n const entries = await discoverEntryPages(root, options);\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n },\n\n configureServer(_server) {\n server = _server;\n\n installDevServer({\n server,\n getPages: async () => {\n if (devPages.length > 0) return devPages;\n return loadDevPages();\n },\n });\n\n loadDevPages().catch((error) => {\n server?.config.logger.error(\n `[${PLUGIN_NAME}] loadDevPages failed: ${\n error instanceof Error ? error.stack ?? error.message : String(error)\n }`,\n );\n });\n },\n\n async handleHotUpdate(ctx) {\n if (!server) return;\n \n const file = ctx.file;\n \n if (\n file.endsWith('.ht.js') ||\n file.includes('/templates/')\n ) {\n logDebug(options.debug, 'reindex triggered by', file);\n await loadDevPages();\n }\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n \n logDebug(options.debug, 'emitting pages', pages.map((p) => p.fileName));\n \n const limit = pLimit(options.renderConcurrency ?? 8);\n const batchSize =\n options.renderBatchSize ??\n Math.max(options.renderConcurrency ?? 8, 32);\n \n for (const batch of chunkArray(pages, batchSize)) {\n await Promise.all(\n batch.map((page) =>\n limit(async () => {\n const mod = modulesByEntry.get(page.entryPath);\n \n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,\n );\n }\n \n const html = await renderPage(page, mod, false);\n \n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n \n // Generate sitemap.xml\n const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))]\n .filter((route) => !route.includes(':') && !route.includes('*'));\n \n if (sitemapRoutes.length > 0) {\n const sitemap = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n ${sitemapRoutes\n .map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)\n .join('\\n')}\n </urlset>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n });\n }\n \n // Generate rss.xml\n if (options.rss?.site) {\n const routePrefix = options.rss.routePrefix ?? '/blog';\n \n const rssItems = pages\n .filter((page) => page.routePath.startsWith(routePrefix))\n .map((page) => {\n const url = `${options.rss!.site}${page.routePath}`;\n return ` <item>\n <title>${page.routePath}</title>\n <link>${url}</link>\n <guid>${url}</guid>\n </item>`;\n })\n .join('\\n');\n \n const rss = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <rss version=\"2.0\">\n <channel>\n <title>${options.rss.title ?? PLUGIN_NAME}</title>\n <link>${options.rss.site}</link>\n <description>${options.rss.description ?? 'RSS feed'}</description>\n ${rssItems}\n </channel>\n </rss>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'rss.xml',\n source: rss,\n });\n }\n \n // Remove the dummy virtual build entry chunk\n for (const [fileName, output] of Object.entries(bundle)) {\n if (\n output.type === 'chunk' &&\n output.facadeModuleId === VIRTUAL_BUILD_ENTRY_ID\n ) {\n delete bundle[fileName];\n }\n }\n } \n\n };\n}\n","import path from 'node:path';\nimport fg from 'fast-glob';\nimport { normalizeFsPath, toPosix } from './path-utils';\nimport { getParamNames, isDynamicPage, toRoutePattern } from './route-utils';\nimport type { HtPageInfo, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME } from './constants';\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const include = Array.isArray(options.include)\n ? options.include\n : [options.include ?? 'src/**/*.ht.js'];\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesDir = options.pagesDir ?? 'src';\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg(include, {\n cwd: root,\n ignore: exclude,\n absolute: true,\n });\n\n return files\n .sort()\n .map((absolutePath) => {\n const entryPath = normalizeFsPath(absolutePath);\n const relativePath = toPosix(path.relative(root, entryPath));\n const relativeFromPagesDir = toPosix(path.relative(pagesRoot, entryPath));\n\n if (\n relativeFromPagesDir.startsWith('../') ||\n relativeFromPagesDir === '..'\n ) {\n throw new Error(\n `[${PLUGIN_NAME}] Page is outside pagesDir: ${entryPath} (pagesDir: ${pagesDir})`,\n );\n }\n\n const dynamic = isDynamicPage(relativeFromPagesDir);\n const routePattern = toRoutePattern(relativeFromPagesDir);\n\n return {\n id: entryPath,\n entryPath,\n absolutePath: entryPath,\n relativePath,\n routePattern,\n routePath: routePattern,\n fileName: '',\n dynamic,\n paramNames: getParamNames(relativeFromPagesDir),\n params: {},\n } satisfies HtPageInfo;\n });\n}","import path from 'node:path';\n\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\nexport function stripHtSuffix(file: string): string {\n return file.replace(/\\.ht\\.js$/i, '');\n}\n\nexport function normalizeRoutePath(p: string): string {\n let out = p.startsWith('/') ? p : `/${p}`;\n out = out.replace(/\\/+/g, '/');\n if (out !== '/' && out.endsWith('/')) out = out.slice(0, -1);\n return out;\n}\n\nexport function normalizeFsPath(p: string): string {\n return toPosix(path.resolve(p));\n}","import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';\nimport type { HtPageInfo, StaticParamRecord } from './types';\n\nconst DYNAMIC_SEGMENT_RE = /\\[([A-Za-z0-9_]+)\\]/g;\nconst CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]/g;\nconst OPTIONAL_CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]\\?/g;\nconst ANY_PARAM_RE = /\\[(?:\\.\\.\\.)?([A-Za-z0-9_]+)\\]\\??/g;\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(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 // More specific / longer routes first when otherwise equal\n return bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-htjs-pages';\nexport const VIRTUAL_BUILD_ENTRY_ID = `\\0${PLUGIN_NAME}:build-entry`;\nexport const VIRTUAL_MANIFEST_ID = `\\0virtual:${PLUGIN_NAME}-manifest`;\nexport const CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;","import type { HtPageInfo } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport function invalidHtmlReturn(\n page: HtPageInfo,\n value: unknown,\n): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" must resolve to an HTML string, got ${typeof value}`,\n );\n}\n\nexport function missingDefaultExport(page: HtPageInfo): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" does not export a default renderer`,\n );\n}\n\nexport function pageError(page: HtPageInfo, cause: unknown): Error {\n const message = `[${PLUGIN_NAME}] Failed to render \"${page.relativePath}\" at route \"${page.routePath}\"`;\n\n if (cause instanceof Error) {\n const err = new Error(`${message}: ${cause.message}`);\n\n if (cause.stack) {\n err.stack = `${err.stack}\\nCaused by:\\n${cause.stack}`;\n }\n\n return err;\n }\n\n return new Error(`${message}: ${String(cause)}`);\n}","import { invalidHtmlReturn, pageError, missingDefaultExport } from './errors';\nimport type { HtPageInfo, HtPageModule, HtPageRenderContext } from './types';\n\nexport async function renderPage(\n page: HtPageInfo,\n mod: HtPageModule,\n dev = false,\n): Promise<string> {\n const ctx: HtPageRenderContext = {\n page,\n params: page.params,\n dev,\n };\n\n try {\n if (typeof mod.data === 'function') {\n ctx.data = await mod.data(ctx);\n }\n\n const entry = mod.default;\n\n if (entry == null) {\n throw missingDefaultExport(page);\n }\n\n const html = typeof entry === 'function' ? await entry(ctx) : entry;\n\n if (typeof html !== 'string') {\n throw invalidHtmlReturn(page, html);\n }\n\n return html;\n } catch (error) {\n throw pageError(page, error);\n }\n}","import type { ViteDevServer } from 'vite';\nimport { renderPage } from './render-runtime';\nimport { routeMatch } from './route-utils';\nimport type { HtPageInfo, HtPageModule } from './types';\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages } = args;\n\n server.middlewares.use(async (req, res, next) => {\n try {\n if (!req.url || req.method !== 'GET') return next();\n\n const pathname = req.url.split('?')[0];\n const pages = await getPages();\n\n for (const page of pages) {\n const params = routeMatch(page.routePattern, pathname);\n if (!params) continue;\n\n const mod = (await server.ssrLoadModule(\n `/${page.relativePath}`,\n )) as HtPageModule;\n\n const resolvedPage = {\n ...page,\n routePath: pathname || '/',\n params,\n };\n\n const html = await renderPage(resolvedPage, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n });\n}","import {\n compareRoutePriority,\n expandStaticPaths,\n fileNameFromRoute,\n} from './route-utils';\nimport type { HtPageInfo, HtPageModule, StaticParamRecord } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport async function buildPageIndex(args: {\n entries: HtPageInfo[];\n modulesByEntry: Map<string, HtPageModule>;\n cleanUrls: boolean;\n}): Promise<HtPageInfo[]> {\n const { entries, modulesByEntry, cleanUrls } = args;\n const pages: HtPageInfo[] = [];\n\n for (const entry of entries) {\n const mod = modulesByEntry.get(entry.entryPath) ?? {};\n\n if (entry.dynamic) {\n const rows = mod.generateStaticParams\n ? await mod.generateStaticParams()\n : [];\n\n pages.push(\n ...expandStaticPaths(\n {\n id: entry.id,\n entryPath: entry.entryPath,\n absolutePath: entry.absolutePath,\n relativePath: entry.relativePath,\n routePattern: entry.routePattern,\n dynamic: entry.dynamic,\n paramNames: entry.paramNames,\n } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows as StaticParamRecord[],\n cleanUrls,\n ),\n );\n } else {\n pages.push({\n ...entry,\n routePath: entry.routePattern,\n fileName: fileNameFromRoute(entry.routePattern, cleanUrls),\n params: {},\n });\n }\n }\n\n pages.sort((a, b) => compareRoutePriority(a.routePattern, b.routePattern));\n\n const seenRoutes = new Map<string, HtPageInfo>();\n\n for (const page of pages) {\n const existing = seenRoutes.get(page.routePath);\n\n if (existing) {\n throw new Error(\n `[${PLUGIN_NAME}] Duplicate route generated: \"${page.routePath}\" from \"${existing.relativePath}\" and \"${page.relativePath}\"`,\n );\n }\n\n seenRoutes.set(page.routePath, page);\n }\n\n return pages;\n}","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { rollup, type Plugin as RollupPlugin } from 'rollup';\nimport { nodeResolve } from '@rollup/plugin-node-resolve';\nimport { createManifestModule } from './manifest';\nimport type { HtPageInfo } from './types';\nimport { PLUGIN_NAME, VIRTUAL_MANIFEST_ID } from './constants';\n\n\nexport async function buildRenderBundle(args: {\n entries: HtPageInfo[];\n cacheDir: string;\n ssrPlugins?: RollupPlugin[];\n}): Promise<string> {\n const { entries, cacheDir, ssrPlugins = [] } = args;\n\n const source = createManifestModule(entries);\n const hash = createHash('sha256').update(source).digest('hex').slice(0, 12);\n const bundlePath = path.join(cacheDir, `render-${hash}.mjs`);\n\n await fs.mkdir(cacheDir, { recursive: true });\n\n try {\n await fs.access(bundlePath);\n return bundlePath;\n } catch {\n // cache miss, continue\n }\n\n const bundle = await rollup({\n input: VIRTUAL_MANIFEST_ID,\n plugins: [\n {\n name: `${PLUGIN_NAME}:virtual-manifest`,\n resolveId(id) {\n return id === VIRTUAL_MANIFEST_ID ? id : null;\n },\n load(id) {\n return id === VIRTUAL_MANIFEST_ID ? source : null;\n },\n },\n nodeResolve({\n preferBuiltins: true,\n exportConditions: ['node'],\n }),\n ...ssrPlugins,\n ],\n treeshake: true,\n });\n\n try {\n const { output } = await bundle.generate({\n format: 'esm',\n exports: 'named',\n inlineDynamicImports: true,\n });\n\n const chunk = output.find((item) => item.type === 'chunk');\n\n if (!chunk || chunk.type !== 'chunk') {\n throw new Error(`[${PLUGIN_NAME}] Failed to generate HT.js pages render bundle.`);\n }\n\n await fs.writeFile(bundlePath, chunk.code, 'utf8');\n return bundlePath;\n } finally {\n await bundle.close();\n }\n}","import type { HtPageInfo } from './types';\n\nfunction js(value: unknown): string {\n return JSON.stringify(value);\n}\n\nexport function createManifestModule(entries: HtPageInfo[]): string {\n const imports = entries\n .map((page, i) => `import * as page${i} from ${js(page.entryPath)};`)\n .join('\\n');\n\n const records = entries\n .map(\n (page, i) => `{\n page: ${js(page)},\n mod: page${i}\n}`,\n )\n .join(',\\n');\n\n return `${imports}\n\nexport const manifest = [\n${records}\n];\n`;\n}"],"mappings":"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","names":["path","createHash","path","path","path","path","createHash","path"]}
1
+ {"version":3,"sources":["../src/plugin.ts","../src/discover.ts","../src/path-utils.ts","../src/route-utils.ts","../src/constants.ts","../src/errors.ts","../src/render-runtime.ts","../src/dev-server.ts","../src/page-index.ts","../src/render-bundle.ts","../src/manifest.ts"],"sourcesContent":["import path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { createHash } from 'node:crypto';\nimport pLimit from 'p-limit';\nimport type { Plugin, ViteDevServer } from 'vite';\n\nimport { discoverEntryPages } from './discover';\nimport { installDevServer } from './dev-server';\nimport { buildPageIndex } from './page-index';\nimport { buildRenderBundle } from './render-bundle';\nimport { renderPage } from './render-runtime';\n\nimport type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID, CACHE_DIR_NAME } from './constants';\n\nfunction chunkArray<T>(items: T[], size: number): T[][] {\n const safeSize = Math.max(1, Math.floor(size));\n const out: T[][] = [];\n for (let i = 0; i < items.length; i += safeSize) {\n out.push(items.slice(i, i + safeSize));\n }\n return out;\n}\n\nfunction escapeXml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\nfunction createEntriesKey(entries: HtPageInfo[]): string {\n const raw = entries\n .map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)\n .join('\\n');\n\n return createHash('sha256').update(raw).digest('hex');\n}\n\nasync function importManifest(\n bundlePath: string,\n): Promise<Array<{ page: HtPageInfo; mod: HtPageModule }>> {\n const mod = await import(pathToFileURL(bundlePath).href + `?t=${Date.now()}`);\n return mod.manifest as Array<{ page: HtPageInfo; mod: HtPageModule }>;\n}\n\nexport function htPages(options: HtPagesPluginOptions = {}): Plugin {\n let root = process.cwd();\n let server: ViteDevServer | null = null;\n let devPages: HtPageInfo[] = [];\n\n let cachedManifestKey: string | null = null;\n let cachedBundlePath: string | null = null;\n let loadDevPagesInFlight: Promise<HtPageInfo[]> | null = null;\n\n const cleanUrls = options.cleanUrls ?? true;\n\n function logDebug(enabled: boolean | undefined, ...args: unknown[]) {\n if (!enabled) return;\n console.log(`[${PLUGIN_NAME}]`, ...args);\n }\n\n async function loadDevPages(): Promise<HtPageInfo[]> {\n if (loadDevPagesInFlight) return loadDevPagesInFlight;\n loadDevPagesInFlight = doLoadDevPages();\n try {\n return await loadDevPagesInFlight;\n } finally {\n loadDevPagesInFlight = null;\n }\n }\n\n async function doLoadDevPages(): Promise<HtPageInfo[]> {\n const entries = await discoverEntryPages(root, options);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n logDebug(options.debug, 'discovered entries', entries.map((e) => e.relativePath));\n\n if (!server) return [];\n\n for (const entry of entries) {\n const mod = (await server.ssrLoadModule(\n `/${entry.relativePath}`,\n )) as HtPageModule;\n\n modulesByEntry.set(entry.entryPath, mod);\n }\n\n devPages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n logDebug(\n options.debug,\n 'dev pages',\n devPages.map((p) => `${p.routePath} -> ${p.relativePath}`),\n );\n\n return devPages;\n }\n\n async function buildPagesPipeline() {\n const entries = await discoverEntryPages(root, options);\n const cacheDir = path.join(root, CACHE_DIR_NAME);\n\n const entriesKey = createEntriesKey(entries);\n\n let bundlePath: string;\n if (cachedBundlePath && cachedManifestKey === entriesKey) {\n bundlePath = cachedBundlePath;\n } else {\n bundlePath = await buildRenderBundle({\n entries,\n cacheDir,\n ssrPlugins: options.ssrPlugins,\n });\n cachedManifestKey = entriesKey;\n cachedBundlePath = bundlePath;\n }\n\n logDebug(options.debug, 'render bundle', bundlePath);\n\n const manifest = await importManifest(bundlePath);\n const modulesByEntry = new Map<string, HtPageModule>();\n\n for (const rec of manifest) {\n modulesByEntry.set(rec.page.entryPath, rec.mod);\n }\n\n const pages = await buildPageIndex({\n entries,\n modulesByEntry,\n cleanUrls,\n });\n\n // Ensure static hosts get a 404.html\n const notFoundPage = pages.find((p) => p.routePath === '/404');\n\n if (notFoundPage && !pages.some((p) => p.fileName === '404.html')) {\n pages.push({\n ...notFoundPage,\n fileName: '404.html',\n });\n } \n\n return { entries, bundlePath, modulesByEntry, pages };\n }\n\n return {\n name: PLUGIN_NAME,\n\n config(userConfig, env) {\n if (env.command !== 'build') return;\n\n const hasExplicitInput = userConfig.build?.rollupOptions?.input != null;\n if (hasExplicitInput) return;\n\n return {\n build: {\n rollupOptions: {\n input: VIRTUAL_BUILD_ENTRY_ID,\n },\n },\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) return id;\n return null;\n },\n\n load(id) {\n if (id === VIRTUAL_BUILD_ENTRY_ID) {\n return 'export default {};';\n }\n return null;\n },\n\n configResolved(resolved) {\n root = resolved.root;\n },\n\n async buildStart() {\n const entries = await discoverEntryPages(root, options);\n\n for (const entry of entries) {\n this.addWatchFile(entry.entryPath);\n }\n },\n\n configureServer(_server) {\n server = _server;\n\n installDevServer({\n server,\n getPages: async () => {\n if (devPages.length > 0) return devPages;\n return loadDevPages();\n },\n });\n\n loadDevPages().catch((error) => {\n server?.config.logger.error(\n `[${PLUGIN_NAME}] loadDevPages failed: ${\n error instanceof Error ? error.stack ?? error.message : String(error)\n }`,\n );\n });\n },\n\n async handleHotUpdate(ctx) {\n if (!server) return;\n \n const file = ctx.file;\n \n if (\n file.endsWith('.ht.js') ||\n file.includes('/templates/')\n ) {\n logDebug(options.debug, 'reindex triggered by', file);\n await loadDevPages();\n }\n },\n\n async generateBundle(_, bundle) {\n const { modulesByEntry, pages } = await buildPagesPipeline();\n \n logDebug(options.debug, 'emitting pages', pages.map((p) => p.fileName));\n \n const concurrency = Math.max(1, options.renderConcurrency ?? 8);\n const limit = pLimit(concurrency);\n const batchSize = Math.max(\n 1,\n options.renderBatchSize ?? Math.max(concurrency, 32),\n );\n \n for (const batch of chunkArray(pages, batchSize)) {\n await Promise.all(\n batch.map((page) =>\n limit(async () => {\n const mod = modulesByEntry.get(page.entryPath);\n \n if (!mod) {\n throw new Error(\n `[${PLUGIN_NAME}] Missing module for page entry: ${page.entryPath}`,\n );\n }\n \n const html = await renderPage(page, mod, false);\n \n this.emitFile({\n type: 'asset',\n fileName: options.mapOutputPath?.(page) ?? page.fileName,\n source: html,\n });\n }),\n ),\n );\n }\n \n // Generate sitemap.xml\n const sitemapBase = options.site ?? '';\n const sitemapRoutes = [...new Set(pages.map((p) => p.routePath))]\n .filter((route) => !route.includes(':') && !route.includes('*'));\n \n if (sitemapRoutes.length > 0) {\n const sitemap = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n ${sitemapRoutes\n .map(\n (route) =>\n ` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`,\n )\n .join('\\n')}\n </urlset>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'sitemap.xml',\n source: sitemap,\n });\n }\n \n // Generate rss.xml\n if (options.rss?.site) {\n const routePrefix = options.rss.routePrefix ?? '/blog';\n \n const rssItems = pages\n .filter((page) => page.routePath.startsWith(routePrefix))\n .map((page) => {\n const url = `${options.rss!.site}${page.routePath}`;\n return ` <item>\n <title>${escapeXml(page.routePath)}</title>\n <link>${escapeXml(url)}</link>\n <guid>${escapeXml(url)}</guid>\n </item>`;\n })\n .join('\\n');\n \n const rss = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n <rss version=\"2.0\">\n <channel>\n <title>${escapeXml(options.rss.title ?? PLUGIN_NAME)}</title>\n <link>${escapeXml(options.rss.site)}</link>\n <description>${escapeXml(options.rss.description ?? 'RSS feed')}</description>\n ${rssItems}\n </channel>\n </rss>\n `;\n \n this.emitFile({\n type: 'asset',\n fileName: 'rss.xml',\n source: rss,\n });\n }\n \n // Remove the dummy virtual build entry chunk\n for (const [fileName, output] of Object.entries(bundle)) {\n if (\n output.type === 'chunk' &&\n output.facadeModuleId === VIRTUAL_BUILD_ENTRY_ID\n ) {\n delete bundle[fileName];\n }\n }\n } \n\n };\n}\n","import path from 'node:path';\nimport fg from 'fast-glob';\nimport { normalizeFsPath, toPosix } from './path-utils';\nimport { getParamNames, isDynamicPage, toRoutePattern } from './route-utils';\nimport type { HtPageInfo, HtPagesPluginOptions } from './types';\nimport { PLUGIN_NAME } from './constants';\n\nexport async function discoverEntryPages(\n root: string,\n options: HtPagesPluginOptions,\n): Promise<HtPageInfo[]> {\n const rawInclude = Array.isArray(options.include)\n ? options.include\n : [options.include ?? 'src/**/*.ht.js'];\n let include = rawInclude.filter((p): p is string => typeof p === 'string' && p.length > 0);\n if (include.length === 0) {\n include = ['src/**/*.ht.js'];\n }\n\n const exclude = Array.isArray(options.exclude)\n ? options.exclude\n : options.exclude\n ? [options.exclude]\n : [];\n\n const pagesDir = options.pagesDir ?? 'src';\n const pagesRoot = normalizeFsPath(path.join(root, pagesDir));\n\n const files = await fg(include, {\n cwd: root,\n ignore: exclude,\n absolute: true,\n });\n\n return files\n .sort()\n .map((absolutePath) => {\n const entryPath = normalizeFsPath(absolutePath);\n const relativePath = toPosix(path.relative(root, entryPath));\n const relativeFromPagesDir = toPosix(path.relative(pagesRoot, entryPath));\n\n if (\n relativeFromPagesDir.startsWith('../') ||\n relativeFromPagesDir === '..'\n ) {\n throw new Error(\n `[${PLUGIN_NAME}] Page is outside pagesDir: ${entryPath} (pagesDir: ${pagesDir})`,\n );\n }\n\n const dynamic = isDynamicPage(relativeFromPagesDir);\n const routePattern = toRoutePattern(relativeFromPagesDir);\n\n return {\n id: entryPath,\n entryPath,\n absolutePath: entryPath,\n relativePath,\n routePattern,\n routePath: routePattern,\n fileName: '',\n dynamic,\n paramNames: getParamNames(relativeFromPagesDir),\n params: {},\n } satisfies HtPageInfo;\n });\n}","import path from 'node:path';\n\nexport function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n}\n\nexport function stripHtSuffix(file: string): string {\n return file.replace(/\\.ht\\.js$/i, '');\n}\n\nexport function normalizeRoutePath(p: string): string {\n let out = p.startsWith('/') ? p : `/${p}`;\n out = out.replace(/\\/+/g, '/');\n if (out !== '/' && out.endsWith('/')) out = out.slice(0, -1);\n return out;\n}\n\nexport function normalizeFsPath(p: string): string {\n return toPosix(path.resolve(p));\n}","import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';\nimport type { HtPageInfo, StaticParamRecord } from './types';\n\nfunction safeDecodeURIComponent(str: string): string {\n try {\n return decodeURIComponent(str);\n } catch {\n return str;\n }\n}\n\nconst DYNAMIC_SEGMENT_RE = /\\[([A-Za-z0-9_]+)\\]/g;\nconst CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]/g;\nconst OPTIONAL_CATCH_ALL_SEGMENT_RE = /\\[\\.\\.\\.([A-Za-z0-9_]+)\\]\\?/g;\nconst ANY_PARAM_RE = /\\[(?:\\.\\.\\.)?([A-Za-z0-9_]+)\\]\\??/g;\nconst ROUTE_GROUP_RE = /(^|\\/)\\(([^)]+)\\)(?=\\/|$)/g;\n\nexport function getParamNames(relativeFromPagesDir: string): string[] {\n return [...relativeFromPagesDir.matchAll(ANY_PARAM_RE)].map((m) => m[1]);\n}\n\nexport function isDynamicPage(relativeFromPagesDir: string): boolean {\n return /\\[(?:\\.\\.\\.)?[A-Za-z0-9_]+\\]\\??/.test(relativeFromPagesDir);\n}\n\nexport function toRoutePattern(relativeFromPagesDir: string): string {\n const noExt = stripHtSuffix(toPosix(relativeFromPagesDir));\n\n const withoutGroups = noExt.replace(ROUTE_GROUP_RE, '$1');\n const withoutIndex = withoutGroups.replace(/\\/index$/i, '').replace(/^index$/i, '');\n\n const raw = withoutIndex\n .replace(OPTIONAL_CATCH_ALL_SEGMENT_RE, '*?:$1')\n .replace(CATCH_ALL_SEGMENT_RE, '*:$1')\n .replace(DYNAMIC_SEGMENT_RE, ':$1');\n\n return normalizeRoutePath(raw || '/');\n}\n\nexport function fillParams(\n pattern: string,\n params: Record<string, string>,\n): string {\n const result = pattern\n .replace(/\\*\\?:([A-Za-z0-9_]+)/g, (_, key) => {\n const value = params[key];\n if (value == null || value === '') {\n return '';\n }\n\n return String(value)\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/\\*:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing catch-all route param \"${key}\"`);\n }\n\n return String(params[key])\n .split('/')\n .map((part) => encodeURIComponent(part))\n .join('/');\n })\n .replace(/:([A-Za-z0-9_]+)/g, (_, key) => {\n if (!(key in params)) {\n throw new Error(`Missing route param \"${key}\"`);\n }\n\n return encodeURIComponent(params[key]);\n });\n\n return normalizeRoutePath(result || '/');\n}\n\nexport function fileNameFromRoute(\n routePath: string,\n cleanUrls: boolean,\n): string {\n const normalized = normalizeRoutePath(routePath);\n\n if (normalized === '/') return 'index.html';\n\n const base = normalized.slice(1);\n return cleanUrls ? `${base}/index.html` : `${base}.html`;\n}\n\nexport function expandStaticPaths(\n basePage: Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n rows: StaticParamRecord[],\n cleanUrls: boolean,\n): HtPageInfo[] {\n return rows.map((row) => {\n const params = Object.fromEntries(\n Object.entries(row).map(([k, v]) => [k, String(v)]),\n );\n\n const routePath = fillParams(basePage.routePattern, params);\n\n return {\n ...basePage,\n routePath,\n fileName: fileNameFromRoute(routePath, cleanUrls),\n params,\n };\n });\n}\n\nexport function routeMatch(\n pattern: string,\n urlPath: string,\n): Record<string, string> | null {\n const a = normalizeRoutePath(pattern).split('/').filter(Boolean);\n const b = normalizeRoutePath(urlPath).split('/').filter(Boolean);\n const params: Record<string, string> = {};\n\n for (let i = 0; i < a.length; i++) {\n const patternSeg = a[i];\n const urlSeg = b[i];\n\n if (patternSeg.startsWith('*?:')) {\n params[patternSeg.slice(3)] =\n i < b.length ? b.slice(i).map(safeDecodeURIComponent).join('/') : '';\n return params;\n }\n\n if (patternSeg.startsWith('*:')) {\n const rest = b.slice(i);\n if (rest.length === 0) return null;\n\n params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join('/');\n return params;\n }\n\n if (!urlSeg) return null;\n\n if (patternSeg.startsWith(':')) {\n params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);\n continue;\n }\n\n if (patternSeg !== urlSeg) return null;\n }\n\n return a.length === b.length ? params : null;\n}\n\nexport function compareRoutePriority(a: string, b: string): number {\n const aSegs = normalizeRoutePath(a).split('/').filter(Boolean);\n const bSegs = normalizeRoutePath(b).split('/').filter(Boolean);\n const len = Math.max(aSegs.length, bSegs.length);\n\n for (let i = 0; i < len; i++) {\n const aa = aSegs[i];\n const bb = bSegs[i];\n\n if (aa == null) return 1;\n if (bb == null) return -1;\n\n const aOptionalCatchAll = aa.startsWith('*?:');\n const bOptionalCatchAll = bb.startsWith('*?:');\n if (aOptionalCatchAll !== bOptionalCatchAll) {\n return aOptionalCatchAll ? 1 : -1;\n }\n\n const aCatchAll = aa.startsWith('*:');\n const bCatchAll = bb.startsWith('*:');\n if (aCatchAll !== bCatchAll) {\n return aCatchAll ? 1 : -1;\n }\n\n const aDynamic = aa.startsWith(':');\n const bDynamic = bb.startsWith(':');\n if (aDynamic !== bDynamic) {\n return aDynamic ? 1 : -1;\n }\n }\n\n // More specific / longer routes first when otherwise equal\n return bSegs.length - aSegs.length;\n}","export const PLUGIN_NAME = 'vite-plugin-htjs-pages';\nexport const VIRTUAL_BUILD_ENTRY_ID = `\\0${PLUGIN_NAME}:build-entry`;\nexport const VIRTUAL_MANIFEST_ID = `\\0virtual:${PLUGIN_NAME}-manifest`;\nexport const CACHE_DIR_NAME = `node_modules/.cache/${PLUGIN_NAME}`;","import type { HtPageInfo } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport function invalidHtmlReturn(\n page: HtPageInfo,\n value: unknown,\n): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" must resolve to an HTML string, got ${typeof value}`,\n );\n}\n\nexport function missingDefaultExport(page: HtPageInfo): Error {\n return new Error(\n `[${PLUGIN_NAME}] Page \"${page.relativePath}\" does not export a default renderer`,\n );\n}\n\nexport function pageError(page: HtPageInfo, cause: unknown): Error {\n const message = `[${PLUGIN_NAME}] Failed to render \"${page.relativePath}\" at route \"${page.routePath}\"`;\n\n if (cause instanceof Error) {\n const err = new Error(`${message}: ${cause.message}`);\n\n if (cause.stack) {\n err.stack = `${err.stack}\\nCaused by:\\n${cause.stack}`;\n }\n\n return err;\n }\n\n return new Error(`${message}: ${String(cause)}`);\n}","import { invalidHtmlReturn, pageError, missingDefaultExport } from './errors';\nimport type { HtPageInfo, HtPageModule, HtPageRenderContext } from './types';\n\nexport async function renderPage(\n page: HtPageInfo,\n mod: HtPageModule,\n dev = false,\n): Promise<string> {\n const ctx: HtPageRenderContext = {\n page,\n params: page.params,\n dev,\n };\n\n try {\n if (typeof mod.data === 'function') {\n ctx.data = await mod.data(ctx);\n }\n\n const entry = mod.default;\n\n if (entry == null) {\n throw missingDefaultExport(page);\n }\n\n const html = typeof entry === 'function' ? await entry(ctx) : entry;\n\n if (typeof html !== 'string') {\n throw invalidHtmlReturn(page, html);\n }\n\n return html;\n } catch (error) {\n throw pageError(page, error);\n }\n}","import type { ViteDevServer } from 'vite';\nimport { renderPage } from './render-runtime';\nimport { routeMatch } from './route-utils';\nimport type { HtPageInfo, HtPageModule } from './types';\n\nexport function installDevServer(args: {\n server: ViteDevServer;\n getPages: () => Promise<HtPageInfo[]>;\n}): void {\n const { server, getPages } = args;\n\n server.middlewares.use(async (req, res, next) => {\n try {\n if (!req.url || req.method !== 'GET') return next();\n\n const pathname = req.url.split('?')[0];\n const pages = await getPages();\n\n for (const page of pages) {\n const params = routeMatch(page.routePattern, pathname);\n if (!params) continue;\n\n const mod = (await server.ssrLoadModule(\n `/${page.relativePath}`,\n )) as HtPageModule;\n\n const resolvedPage = {\n ...page,\n routePath: pathname || '/',\n params,\n };\n\n const html = await renderPage(resolvedPage, mod, true);\n\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/html; charset=utf-8');\n res.end(html);\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n });\n}","import {\n compareRoutePriority,\n expandStaticPaths,\n fileNameFromRoute,\n} from './route-utils';\nimport type { HtPageInfo, HtPageModule, StaticParamRecord } from './types';\nimport { PLUGIN_NAME } from './constants';\nexport async function buildPageIndex(args: {\n entries: HtPageInfo[];\n modulesByEntry: Map<string, HtPageModule>;\n cleanUrls: boolean;\n}): Promise<HtPageInfo[]> {\n const { entries, modulesByEntry, cleanUrls } = args;\n const pages: HtPageInfo[] = [];\n\n for (const entry of entries) {\n const mod = modulesByEntry.get(entry.entryPath) ?? {};\n\n if (entry.dynamic) {\n const rows =\n (mod.generateStaticParams\n ? await mod.generateStaticParams()\n : []) ?? [];\n\n pages.push(\n ...expandStaticPaths(\n {\n id: entry.id,\n entryPath: entry.entryPath,\n absolutePath: entry.absolutePath,\n relativePath: entry.relativePath,\n routePattern: entry.routePattern,\n dynamic: entry.dynamic,\n paramNames: entry.paramNames,\n } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,\n Array.isArray(rows) ? rows : [],\n cleanUrls,\n ),\n );\n } else {\n pages.push({\n ...entry,\n routePath: entry.routePattern,\n fileName: fileNameFromRoute(entry.routePattern, cleanUrls),\n params: {},\n });\n }\n }\n\n pages.sort((a, b) => compareRoutePriority(a.routePattern, b.routePattern));\n\n const seenRoutes = new Map<string, HtPageInfo>();\n\n for (const page of pages) {\n const existing = seenRoutes.get(page.routePath);\n\n if (existing) {\n throw new Error(\n `[${PLUGIN_NAME}] Duplicate route generated: \"${page.routePath}\" from \"${existing.relativePath}\" and \"${page.relativePath}\"`,\n );\n }\n\n seenRoutes.set(page.routePath, page);\n }\n\n return pages;\n}","import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { rollup, type Plugin as RollupPlugin } from 'rollup';\nimport { nodeResolve } from '@rollup/plugin-node-resolve';\nimport { createManifestModule } from './manifest';\nimport type { HtPageInfo } from './types';\nimport { PLUGIN_NAME, VIRTUAL_MANIFEST_ID } from './constants';\n\n\nexport async function buildRenderBundle(args: {\n entries: HtPageInfo[];\n cacheDir: string;\n ssrPlugins?: RollupPlugin[];\n}): Promise<string> {\n const { entries, cacheDir, ssrPlugins = [] } = args;\n\n const source = createManifestModule(entries);\n const hash = createHash('sha256').update(source).digest('hex').slice(0, 12);\n const bundlePath = path.join(cacheDir, `render-${hash}.mjs`);\n\n await fs.mkdir(cacheDir, { recursive: true });\n\n try {\n await fs.access(bundlePath);\n return bundlePath;\n } catch {\n // cache miss, continue\n }\n\n const bundle = await rollup({\n input: VIRTUAL_MANIFEST_ID,\n plugins: [\n {\n name: `${PLUGIN_NAME}:virtual-manifest`,\n resolveId(id) {\n return id === VIRTUAL_MANIFEST_ID ? id : null;\n },\n load(id) {\n return id === VIRTUAL_MANIFEST_ID ? source : null;\n },\n },\n nodeResolve({\n preferBuiltins: true,\n exportConditions: ['node'],\n }),\n ...ssrPlugins,\n ],\n treeshake: true,\n });\n\n try {\n const { output } = await bundle.generate({\n format: 'esm',\n exports: 'named',\n inlineDynamicImports: true,\n });\n\n const chunk = output.find((item) => item.type === 'chunk');\n\n if (!chunk || chunk.type !== 'chunk') {\n throw new Error(`[${PLUGIN_NAME}] Failed to generate HT.js pages render bundle.`);\n }\n\n await fs.writeFile(bundlePath, chunk.code, 'utf8');\n return bundlePath;\n } finally {\n await bundle.close();\n }\n}","import type { HtPageInfo } from './types';\n\nfunction js(value: unknown): string {\n return JSON.stringify(value);\n}\n\nexport function createManifestModule(entries: HtPageInfo[]): string {\n const imports = entries\n .map((page, i) => `import * as page${i} from ${js(page.entryPath)};`)\n .join('\\n');\n\n const records = entries\n .map(\n (page, i) => `{\n page: ${js(page)},\n mod: page${i}\n}`,\n )\n .join(',\\n');\n\n return `${imports}\n\nexport const manifest = [\n${records}\n];\n`;\n}"],"mappings":"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","names":["path","createHash","path","path","path","path","createHash","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-htjs-pages",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "author": "Paul Browne",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/discover.ts CHANGED
@@ -9,9 +9,13 @@ export async function discoverEntryPages(
9
9
  root: string,
10
10
  options: HtPagesPluginOptions,
11
11
  ): Promise<HtPageInfo[]> {
12
- const include = Array.isArray(options.include)
12
+ const rawInclude = Array.isArray(options.include)
13
13
  ? options.include
14
14
  : [options.include ?? 'src/**/*.ht.js'];
15
+ let include = rawInclude.filter((p): p is string => typeof p === 'string' && p.length > 0);
16
+ if (include.length === 0) {
17
+ include = ['src/**/*.ht.js'];
18
+ }
15
19
 
16
20
  const exclude = Array.isArray(options.exclude)
17
21
  ? options.exclude
package/src/page-index.ts CHANGED
@@ -17,9 +17,10 @@ export async function buildPageIndex(args: {
17
17
  const mod = modulesByEntry.get(entry.entryPath) ?? {};
18
18
 
19
19
  if (entry.dynamic) {
20
- const rows = mod.generateStaticParams
21
- ? await mod.generateStaticParams()
22
- : [];
20
+ const rows =
21
+ (mod.generateStaticParams
22
+ ? await mod.generateStaticParams()
23
+ : []) ?? [];
23
24
 
24
25
  pages.push(
25
26
  ...expandStaticPaths(
@@ -32,7 +33,7 @@ export async function buildPageIndex(args: {
32
33
  dynamic: entry.dynamic,
33
34
  paramNames: entry.paramNames,
34
35
  } as Omit<HtPageInfo, 'routePath' | 'fileName' | 'params'>,
35
- rows as StaticParamRecord[],
36
+ Array.isArray(rows) ? rows : [],
36
37
  cleanUrls,
37
38
  ),
38
39
  );
package/src/plugin.ts CHANGED
@@ -14,13 +14,23 @@ import type { HtPageInfo, HtPageModule, HtPagesPluginOptions } from './types';
14
14
  import { PLUGIN_NAME, VIRTUAL_BUILD_ENTRY_ID, CACHE_DIR_NAME } from './constants';
15
15
 
16
16
  function chunkArray<T>(items: T[], size: number): T[][] {
17
+ const safeSize = Math.max(1, Math.floor(size));
17
18
  const out: T[][] = [];
18
- for (let i = 0; i < items.length; i += size) {
19
- out.push(items.slice(i, i + size));
19
+ for (let i = 0; i < items.length; i += safeSize) {
20
+ out.push(items.slice(i, i + safeSize));
20
21
  }
21
22
  return out;
22
23
  }
23
24
 
25
+ function escapeXml(text: string): string {
26
+ return text
27
+ .replace(/&/g, '&amp;')
28
+ .replace(/</g, '&lt;')
29
+ .replace(/>/g, '&gt;')
30
+ .replace(/"/g, '&quot;')
31
+ .replace(/'/g, '&apos;');
32
+ }
33
+
24
34
  function createEntriesKey(entries: HtPageInfo[]): string {
25
35
  const raw = entries
26
36
  .map((e) => `${e.entryPath}|${e.routePattern}|${e.dynamic}`)
@@ -43,6 +53,7 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
43
53
 
44
54
  let cachedManifestKey: string | null = null;
45
55
  let cachedBundlePath: string | null = null;
56
+ let loadDevPagesInFlight: Promise<HtPageInfo[]> | null = null;
46
57
 
47
58
  const cleanUrls = options.cleanUrls ?? true;
48
59
 
@@ -52,6 +63,16 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
52
63
  }
53
64
 
54
65
  async function loadDevPages(): Promise<HtPageInfo[]> {
66
+ if (loadDevPagesInFlight) return loadDevPagesInFlight;
67
+ loadDevPagesInFlight = doLoadDevPages();
68
+ try {
69
+ return await loadDevPagesInFlight;
70
+ } finally {
71
+ loadDevPagesInFlight = null;
72
+ }
73
+ }
74
+
75
+ async function doLoadDevPages(): Promise<HtPageInfo[]> {
55
76
  const entries = await discoverEntryPages(root, options);
56
77
  const modulesByEntry = new Map<string, HtPageModule>();
57
78
 
@@ -210,10 +231,12 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
210
231
 
211
232
  logDebug(options.debug, 'emitting pages', pages.map((p) => p.fileName));
212
233
 
213
- const limit = pLimit(options.renderConcurrency ?? 8);
214
- const batchSize =
215
- options.renderBatchSize ??
216
- Math.max(options.renderConcurrency ?? 8, 32);
234
+ const concurrency = Math.max(1, options.renderConcurrency ?? 8);
235
+ const limit = pLimit(concurrency);
236
+ const batchSize = Math.max(
237
+ 1,
238
+ options.renderBatchSize ?? Math.max(concurrency, 32),
239
+ );
217
240
 
218
241
  for (const batch of chunkArray(pages, batchSize)) {
219
242
  await Promise.all(
@@ -248,7 +271,10 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
248
271
  const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
249
272
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
250
273
  ${sitemapRoutes
251
- .map((route) => ` <url><loc>${sitemapBase}${route}</loc></url>`)
274
+ .map(
275
+ (route) =>
276
+ ` <url><loc>${escapeXml(sitemapBase)}${escapeXml(route)}</loc></url>`,
277
+ )
252
278
  .join('\n')}
253
279
  </urlset>
254
280
  `;
@@ -269,9 +295,9 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
269
295
  .map((page) => {
270
296
  const url = `${options.rss!.site}${page.routePath}`;
271
297
  return ` <item>
272
- <title>${page.routePath}</title>
273
- <link>${url}</link>
274
- <guid>${url}</guid>
298
+ <title>${escapeXml(page.routePath)}</title>
299
+ <link>${escapeXml(url)}</link>
300
+ <guid>${escapeXml(url)}</guid>
275
301
  </item>`;
276
302
  })
277
303
  .join('\n');
@@ -279,9 +305,9 @@ export function htPages(options: HtPagesPluginOptions = {}): Plugin {
279
305
  const rss = `<?xml version="1.0" encoding="UTF-8"?>
280
306
  <rss version="2.0">
281
307
  <channel>
282
- <title>${options.rss.title ?? PLUGIN_NAME}</title>
283
- <link>${options.rss.site}</link>
284
- <description>${options.rss.description ?? 'RSS feed'}</description>
308
+ <title>${escapeXml(options.rss.title ?? PLUGIN_NAME)}</title>
309
+ <link>${escapeXml(options.rss.site)}</link>
310
+ <description>${escapeXml(options.rss.description ?? 'RSS feed')}</description>
285
311
  ${rssItems}
286
312
  </channel>
287
313
  </rss>
@@ -1,6 +1,14 @@
1
1
  import { normalizeRoutePath, stripHtSuffix, toPosix } from './path-utils';
2
2
  import type { HtPageInfo, StaticParamRecord } from './types';
3
3
 
4
+ function safeDecodeURIComponent(str: string): string {
5
+ try {
6
+ return decodeURIComponent(str);
7
+ } catch {
8
+ return str;
9
+ }
10
+ }
11
+
4
12
  const DYNAMIC_SEGMENT_RE = /\[([A-Za-z0-9_]+)\]/g;
5
13
  const CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]/g;
6
14
  const OPTIONAL_CATCH_ALL_SEGMENT_RE = /\[\.\.\.([A-Za-z0-9_]+)\]\?/g;
@@ -113,7 +121,7 @@ export function routeMatch(
113
121
 
114
122
  if (patternSeg.startsWith('*?:')) {
115
123
  params[patternSeg.slice(3)] =
116
- i < b.length ? b.slice(i).map(decodeURIComponent).join('/') : '';
124
+ i < b.length ? b.slice(i).map(safeDecodeURIComponent).join('/') : '';
117
125
  return params;
118
126
  }
119
127
 
@@ -121,14 +129,14 @@ export function routeMatch(
121
129
  const rest = b.slice(i);
122
130
  if (rest.length === 0) return null;
123
131
 
124
- params[patternSeg.slice(2)] = rest.map(decodeURIComponent).join('/');
132
+ params[patternSeg.slice(2)] = rest.map(safeDecodeURIComponent).join('/');
125
133
  return params;
126
134
  }
127
135
 
128
136
  if (!urlSeg) return null;
129
137
 
130
138
  if (patternSeg.startsWith(':')) {
131
- params[patternSeg.slice(1)] = decodeURIComponent(urlSeg);
139
+ params[patternSeg.slice(1)] = safeDecodeURIComponent(urlSeg);
132
140
  continue;
133
141
  }
134
142