vite-plugin-kiru 0.29.10 → 0.29.11

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
@@ -1,14 +1,6 @@
1
1
  # vite-plugin-kiru
2
2
 
3
- Vite plugin for <a href="https://kirujs.dev">Kiru</a> apps that enables HMR, devtools, file-based routing, and SSG/SSR.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm i -D vite-plugin-kiru
9
- # or
10
- pnpm add -D vite-plugin-kiru
11
- ```
3
+ Vite plugin for <a href="https://kirujs.dev">Kiru</a> apps that enables HMR, devtools, and SSG with file-based routing & sitemap generation.
12
4
 
13
5
  ## Basic Usage
14
6
 
@@ -60,25 +52,106 @@ kiru({
60
52
  onFileExcluded: (id) => {
61
53
  console.log(`Excluded: ${id}`)
62
54
  },
55
+
56
+ // Static Site Generation (SSG) configuration
57
+ ssg: {
58
+ // Base URL for the app
59
+ baseUrl: "/",
60
+ // Directory containing pages
61
+ dir: "./src/pages",
62
+ // Document component filename pattern
63
+ document: "document.{tsx,jsx}",
64
+ // Page component filename pattern
65
+ page: "index.{tsx,jsx}",
66
+ // Layout component filename pattern
67
+ layout: "layout.{tsx,jsx}",
68
+ // Enable view transitions for route changes and page loaders
69
+ transition: true,
70
+ // Build options
71
+ build: {
72
+ // Maximum number of pages to render concurrently
73
+ maxConcurrentRenders: 100,
74
+ },
75
+ // Sitemap generation options
76
+ sitemap: {
77
+ // Domain for sitemap URLs (required)
78
+ domain: "https://example.com",
79
+ // Default last modified date for all URLs
80
+ lastmod: new Date(),
81
+ // Default change frequency (hourly | daily | weekly | monthly | yearly | never)
82
+ changefreq: "weekly", // default: "weekly"
83
+ // Default priority (0.0 to 1.0)
84
+ priority: 0.5, // default: 0.5
85
+ // Per-route overrides
86
+ overrides: {
87
+ "/": {
88
+ changefreq: "daily",
89
+ priority: 0.8,
90
+ lastmod: new Date("2024-01-01"),
91
+ // Images to include for this route
92
+ images: ["/images/hero.png"],
93
+ // Videos to include for this route
94
+ videos: [
95
+ {
96
+ title: "Product Demo",
97
+ thumbnail_loc: "/images/video-thumbnail.png",
98
+ description: "A demonstration of our product features.",
99
+ },
100
+ ],
101
+ },
102
+ "/blog": {
103
+ changefreq: "weekly",
104
+ priority: 0.7,
105
+ },
106
+ },
107
+ },
108
+ },
109
+
110
+ // Or, if you want to enable SSG with default configuration:
111
+ ssg: true,
112
+ // (this will not include sitemap generation as it requires manual configuration)
63
113
  })
64
114
  ```
65
115
 
116
+ ## Static Site Generation (SSG)
117
+
118
+ The plugin supports static site generation with configurable sitemap creation. When SSG is enabled, all routes are pre-rendered at build time and a `sitemap.xml` file is generated if configured.
119
+
120
+ ### Sitemap Generation
121
+
122
+ The sitemap feature generates a `sitemap.xml` file in your build output with all discovered routes (excluding 404 pages).
123
+
124
+ **Features:**
125
+
126
+ - Automatic route discovery from your file structure
127
+ - Configurable default `changefreq`, `priority`, and `lastmod` for all routes
128
+ - Per-route overrides for fine-grained control
129
+ - Support for images and videos (Google sitemap extensions)
130
+
131
+ **Example:**
132
+
133
+ ```ts
134
+ ssg: {
135
+ sitemap: {
136
+ domain: "https://kirujs.dev",
137
+ changefreq: "weekly",
138
+ priority: 0.5,
139
+ overrides: {
140
+ "/": {
141
+ changefreq: "daily",
142
+ priority: 0.8,
143
+ images: ["/images/kiru.png"],
144
+ },
145
+ },
146
+ },
147
+ }
148
+ ```
149
+
150
+ This will generate a `sitemap.xml` file in `dist/client/sitemap.xml` with all your routes properly formatted.
151
+
66
152
  ## Features
67
153
 
68
- - **File-based routing**: Automatic route generation from your pages directory
69
- - **SSR/SSG**: Server-side rendering and static site generation
154
+ - **SSG + file-based routing**: Automatic route discovery, static site and sitemap generation
70
155
  - **HMR**: Hot module replacement for fast development
71
156
  - **Devtools**: Built-in development tools for debugging
72
157
  - **TypeScript**: Full TypeScript support with proper type definitions
73
-
74
- ## Architecture
75
-
76
- The plugin is organized into focused modules:
77
-
78
- - `virtual-modules.ts` - Virtual module generation for routing
79
- - `dev-server.ts` - Development server SSR handling
80
- - `preview-server.ts` - Preview server middleware
81
- - `devtools.ts` - Development tools integration
82
- - `ssg.ts` - Static site generation
83
- - `config.ts` - Configuration management
84
- - `utils.ts` - Shared utilities
package/dist/index.d.ts CHANGED
@@ -10,6 +10,86 @@ export interface SSGBuildOptions {
10
10
  maxConcurrentRenders?: number
11
11
  }
12
12
 
13
+ export type SSGSitemapChangefreq =
14
+ // | "always"
15
+ "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"
16
+
17
+ export interface SSGSitemapVideo {
18
+ title: string
19
+ thumbnail_loc: string
20
+ description?: string
21
+ }
22
+
23
+ export interface SSGSitemapOverride {
24
+ /**
25
+ * Change frequency override for this route
26
+ * @default "weekly"
27
+ */
28
+ changefreq?: SSGSitemapChangefreq
29
+ /**
30
+ * Priority override for this route (0.0 to 1.0)
31
+ * @default 0.5
32
+ */
33
+ priority?: number
34
+ /**
35
+ * Last modified date override for this route
36
+ */
37
+ lastmod?: Date
38
+ /**
39
+ * Images to include for this route
40
+ * @example ["/images/kiru.png"]
41
+ */
42
+ images?: string[]
43
+ /**
44
+ * Videos to include for this route
45
+ * @example
46
+ * ```ts
47
+ * videos: [
48
+ * {
49
+ * title: "Kiru",
50
+ * thumbnail_loc: "/images/kiru.png",
51
+ * description: "Kiru is a framework for building web applications."
52
+ * }
53
+ * ]
54
+ * ```
55
+ */
56
+ videos?: Array<SSGSitemapVideo>
57
+ }
58
+
59
+ export interface SSGSitemapOptions {
60
+ /**
61
+ * The domain to use for sitemap URLs
62
+ * @example "https://example.com"
63
+ */
64
+ domain: string
65
+ /**
66
+ * Default last modified date for all URLs
67
+ */
68
+ lastmod?: Date
69
+ /**
70
+ * Default change frequency for all URLs
71
+ * @default "weekly"
72
+ */
73
+ changefreq?: SSGSitemapChangefreq
74
+ /**
75
+ * Default priority for all URLs (0.0 to 1.0)
76
+ * @default 0.5
77
+ */
78
+ priority?: number
79
+ /**
80
+ * Per-route overrides for sitemap entries
81
+ * @example
82
+ * ```ts
83
+ * overrides: {
84
+ * "/": {
85
+ * changefreq: "never",
86
+ * priority: 0.8,
87
+ * },
88
+ * }
89
+ */
90
+ overrides?: Record<string, SSGSitemapOverride>
91
+ }
92
+
13
93
  export interface SSGOptions {
14
94
  /**
15
95
  * The base URL of the app
@@ -42,6 +122,11 @@ export interface SSGOptions {
42
122
  */
43
123
  transition?: boolean
44
124
 
125
+ /**
126
+ * Options for sitemap generation
127
+ */
128
+ sitemap?: SSGSitemapOptions
129
+
45
130
  /**
46
131
  * Options for build
47
132
  */
package/dist/index.js CHANGED
@@ -8968,6 +8968,7 @@ function createPluginState(opts = {}) {
8968
8968
  page: ssg.page ?? page,
8969
8969
  layout: ssg.layout ?? layout,
8970
8970
  transition: ssg.transition ?? transition,
8971
+ sitemap: ssg.sitemap,
8971
8972
  build: {
8972
8973
  maxConcurrentRenders: ssg.build?.maxConcurrentRenders ?? maxConcurrentRenders
8973
8974
  }
@@ -11212,7 +11213,7 @@ import path6 from "node:path";
11212
11213
  import fs3 from "node:fs";
11213
11214
  import { pathToFileURL } from "node:url";
11214
11215
  async function generateStaticSite(state, outputOptions, bundle, log) {
11215
- const { projectRoot, baseOutDir, manifestPath } = state;
11216
+ const { projectRoot, baseOutDir, manifestPath, ssgOptions } = state;
11216
11217
  const outDirAbs = path6.resolve(projectRoot, outputOptions?.dir ?? "dist");
11217
11218
  const ssrEntry = Object.values(bundle).find(
11218
11219
  (c) => c.type === "chunk" && c.isEntry
@@ -11234,7 +11235,7 @@ async function generateStaticSite(state, outputOptions, bundle, log) {
11234
11235
  const routes = Object.keys(paths);
11235
11236
  log(ANSI.cyan("[SSG]"), `discovered ${routes.length} routes:`, routes);
11236
11237
  const renderingChunks = [];
11237
- const maxConcurrentRenders = state.ssgOptions.build.maxConcurrentRenders;
11238
+ const maxConcurrentRenders = ssgOptions.build.maxConcurrentRenders;
11238
11239
  for (let i = 0; i < routes.length; i += maxConcurrentRenders) {
11239
11240
  const chunkKeys = routes.slice(i, i + maxConcurrentRenders);
11240
11241
  renderingChunks.push(
@@ -11263,6 +11264,9 @@ async function generateStaticSite(state, outputOptions, bundle, log) {
11263
11264
  );
11264
11265
  }
11265
11266
  await appendStaticPropsToClientModules(state, clientOutDirAbs, log);
11267
+ if (ssgOptions.sitemap?.domain) {
11268
+ await generateSitemap(state, routes, clientOutDirAbs, log);
11269
+ }
11266
11270
  }
11267
11271
  async function getClientAssets(clientOutDirAbs, manifestPath) {
11268
11272
  let clientEntry = null;
@@ -11395,6 +11399,101 @@ function getOutputPath(clientOutDirAbs, route) {
11395
11399
  }
11396
11400
  return path6.resolve(dirPath, `${last}.html`);
11397
11401
  }
11402
+ async function generateSitemap(state, routes, clientOutDirAbs, log) {
11403
+ const sitemapConfig = state.ssgOptions.sitemap;
11404
+ const {
11405
+ domain,
11406
+ lastmod: lastModified,
11407
+ changefreq = "weekly",
11408
+ priority = 0.5,
11409
+ overrides = {}
11410
+ } = sitemapConfig;
11411
+ const baseUrl = state.ssgOptions.baseUrl;
11412
+ const normalizedDomain = domain.replace(/\/$/, "");
11413
+ const normalizedBaseUrl = baseUrl === "/" ? "" : baseUrl.replace(/\/$/, "");
11414
+ let hasImages = false;
11415
+ let hasVideos = false;
11416
+ for (const route of routes) {
11417
+ const override = overrides[route];
11418
+ if (override?.images?.length) {
11419
+ hasImages = true;
11420
+ }
11421
+ if (override?.videos?.length) {
11422
+ hasVideos = true;
11423
+ }
11424
+ }
11425
+ const sortedRoutes = [...routes].filter((route) => !route.endsWith("/404")).sort((a, b) => {
11426
+ if (a === "/") return -1;
11427
+ if (b === "/") return 1;
11428
+ const aDepth = a.split("/").filter(Boolean).length;
11429
+ const bDepth = b.split("/").filter(Boolean).length;
11430
+ if (aDepth !== bDepth) {
11431
+ return aDepth - bDepth;
11432
+ }
11433
+ return a < b ? -1 : a > b ? 1 : 0;
11434
+ });
11435
+ const urls = sortedRoutes.map((route) => {
11436
+ const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
11437
+ const url = `${normalizedDomain}${normalizedBaseUrl}${normalizedRoute}`;
11438
+ const override = overrides[route] || {};
11439
+ const routeChangefreq = override.changefreq ?? changefreq;
11440
+ const routePriority = override.priority ?? priority;
11441
+ const routeLastModified = override.lastmod ?? lastModified;
11442
+ let urlEntry = ` <url>
11443
+ <loc>${escapeXml(url)}</loc>
11444
+ <changefreq>${routeChangefreq}</changefreq>
11445
+ <priority>${routePriority}</priority>`;
11446
+ if (routeLastModified) {
11447
+ const lastMod = routeLastModified.toISOString().split("T")[0];
11448
+ urlEntry += `
11449
+ <lastmod>${lastMod}</lastmod>`;
11450
+ }
11451
+ if (override.images?.length) {
11452
+ for (const image of override.images) {
11453
+ const imageUrl = image.startsWith("http") ? image : `${normalizedDomain}${normalizedBaseUrl}${image.startsWith("/") ? image : `/${image}`}`;
11454
+ urlEntry += `
11455
+ <image:image>
11456
+ <image:loc>${escapeXml(imageUrl)}</image:loc>
11457
+ </image:image>`;
11458
+ }
11459
+ }
11460
+ if (override.videos?.length) {
11461
+ for (const video of override.videos) {
11462
+ const thumbnailUrl = video.thumbnail_loc.startsWith("http") ? video.thumbnail_loc : `${normalizedDomain}${normalizedBaseUrl}${video.thumbnail_loc.startsWith("/") ? video.thumbnail_loc : `/${video.thumbnail_loc}`}`;
11463
+ urlEntry += `
11464
+ <video:video>
11465
+ <video:title>${escapeXml(video.title)}</video:title>
11466
+ <video:thumbnail_loc>${escapeXml(thumbnailUrl)}</video:thumbnail_loc>`;
11467
+ if (video.description) {
11468
+ urlEntry += `
11469
+ <video:description>${escapeXml(
11470
+ video.description
11471
+ )}</video:description>`;
11472
+ }
11473
+ urlEntry += `
11474
+ </video:video>`;
11475
+ }
11476
+ }
11477
+ urlEntry += `
11478
+ </url>`;
11479
+ return urlEntry;
11480
+ }).join("\n");
11481
+ const namespaces = [
11482
+ 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"',
11483
+ hasImages && 'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"',
11484
+ hasVideos && 'xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"'
11485
+ ].filter(Boolean).join(" ");
11486
+ const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
11487
+ <urlset ${namespaces}>
11488
+ ${urls}
11489
+ </urlset>`;
11490
+ const sitemapPath = path6.resolve(clientOutDirAbs, "sitemap.xml");
11491
+ fs3.writeFileSync(sitemapPath, sitemapXml, "utf-8");
11492
+ log(ANSI.cyan("[SSG]"), "Generated sitemap:", ANSI.black(sitemapPath));
11493
+ }
11494
+ function escapeXml(unsafe) {
11495
+ return unsafe.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
11496
+ }
11398
11497
  async function appendStaticPropsToClientModules(state, clientOutDirAbs, log) {
11399
11498
  const { projectRoot, manifestPath, ssgOptions } = state;
11400
11499
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-kiru",
3
- "version": "0.29.10",
3
+ "version": "0.29.11",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "glob": "^11.0.3",
25
25
  "magic-string": "^0.30.17",
26
26
  "mime": "^4.1.0",
27
- "vite": "^7.0.6"
27
+ "vite": "^7.2.2"
28
28
  },
29
29
  "scripts": {
30
30
  "test": "echo \"Error: no test specified\"",