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 +96 -23
- package/dist/index.d.ts +85 -0
- package/dist/index.js +101 -2
- package/package.json +2 -2
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
|
|
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
|
-
- **
|
|
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 =
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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.
|
|
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.
|
|
27
|
+
"vite": "^7.2.2"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "echo \"Error: no test specified\"",
|