@inglorious/ssx 1.3.6 → 1.4.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 +40 -11
- package/package.json +10 -3
- package/src/build/index.js +6 -4
- package/src/build/metadata.js +10 -3
- package/src/build/metadata.test.js +19 -0
- package/src/build/vite-config.js +4 -0
- package/src/dev/vite-config.js +9 -2
- package/src/router/index.js +2 -2
- package/src/router/router.test.js +3 -3
- package/src/utils/markdown.js +90 -0
- package/types/index.d.ts +57 -266
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ SSX takes your entity-based web apps and generates optimized static HTML with fu
|
|
|
32
32
|
- **lit-html hydration** - Interactive UI without the bloat
|
|
33
33
|
- **TypeScript Ready** - Write your pages and entities in TypeScript.
|
|
34
34
|
- **Image Optimization** - Automatic compression for static assets.
|
|
35
|
+
- **Markdown Support** - Built-in support for `.md` pages with code highlighting and math.
|
|
35
36
|
|
|
36
37
|
### 🚀 Production Ready
|
|
37
38
|
|
|
@@ -397,6 +398,35 @@ SSX includes built-in image optimization using `vite-plugin-image-optimizer`.
|
|
|
397
398
|
- **Automatic compression** - PNG, JPEG, GIF, SVG, WebP, and AVIF are compressed at build time.
|
|
398
399
|
- **Lossless & Lossy** - Configurable settings via `vite` config in `site.config.js`.
|
|
399
400
|
|
|
401
|
+
### 📝 Markdown Support
|
|
402
|
+
|
|
403
|
+
SSX treats `.md` files as first-class pages. You can create `src/pages/post.md` and it will be rendered automatically.
|
|
404
|
+
|
|
405
|
+
- **Frontmatter** - Metadata is exported as `metadata`.
|
|
406
|
+
- **Code Highlighting** - Built-in syntax highlighting with `highlight.js`.
|
|
407
|
+
- **Math Support** - LaTeX support via `katex` (use `$E=mc^2$` or `$$...$$`).
|
|
408
|
+
- **Mermaid Diagrams** - Use `mermaid` code blocks (requires client-side mermaid.js).
|
|
409
|
+
|
|
410
|
+
Configure the syntax highlighting theme in `site.config.js`:
|
|
411
|
+
|
|
412
|
+
```javascript
|
|
413
|
+
export default {
|
|
414
|
+
markdown: {
|
|
415
|
+
theme: "monokai", // default: "github-dark"
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
```markdown
|
|
421
|
+
---
|
|
422
|
+
title: My Post
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
# Hello World
|
|
426
|
+
|
|
427
|
+
This is a markdown page.
|
|
428
|
+
```
|
|
429
|
+
|
|
400
430
|
---
|
|
401
431
|
|
|
402
432
|
## CLI
|
|
@@ -464,16 +494,15 @@ my-site/
|
|
|
464
494
|
|
|
465
495
|
## Comparison to Other Tools
|
|
466
496
|
|
|
467
|
-
| Feature
|
|
468
|
-
|
|
|
469
|
-
| Pre-rendered HTML
|
|
470
|
-
| Client hydration
|
|
471
|
-
| Client routing
|
|
472
|
-
| Lazy loading
|
|
473
|
-
| Entity-based state
|
|
474
|
-
|
|
|
475
|
-
|
|
|
476
|
-
| Framework agnostic | ❌ | ❌ | ✅ | ✅ |
|
|
497
|
+
| Feature | SSX | Next.js (SSG) | Astro | Eleventy |
|
|
498
|
+
| ------------------ | ----------- | ------------- | ------ | -------- |
|
|
499
|
+
| Pre-rendered HTML | ✅ | ✅ | ✅ | ✅ |
|
|
500
|
+
| Client hydration | ✅ lit-html | ✅ React | ✅ Any | ❌ |
|
|
501
|
+
| Client routing | ✅ | ✅ | ✅ | ❌ |
|
|
502
|
+
| Lazy loading | ✅ | ✅ | ✅ | ❌ |
|
|
503
|
+
| Entity-based state | ✅ | ❌ | ❌ | ❌ |
|
|
504
|
+
| Zero config | ✅ | ❌ | ❌ | ❌ |
|
|
505
|
+
| Framework agnostic | ❌ | ❌ | ✅ | ✅ |
|
|
477
506
|
|
|
478
507
|
SSX is perfect if you:
|
|
479
508
|
|
|
@@ -643,7 +672,7 @@ Check out these example projects:
|
|
|
643
672
|
- [x] TypeScript support
|
|
644
673
|
- [x] Image optimization
|
|
645
674
|
- [ ] API routes (serverless functions)
|
|
646
|
-
- [
|
|
675
|
+
- [x] Markdown support
|
|
647
676
|
- [ ] i18n helpers
|
|
648
677
|
|
|
649
678
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/ssx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Server-Side-X. Xecution? Xperience? Who knows.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"ssx": "./bin/ssx.js"
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
29
|
-
".": "./types/index.d.ts"
|
|
29
|
+
".": "./types/index.d.ts",
|
|
30
|
+
"./markdown": "./src/utils/markdown.js"
|
|
30
31
|
},
|
|
31
32
|
"files": [
|
|
32
33
|
"bin",
|
|
@@ -44,12 +45,18 @@
|
|
|
44
45
|
"connect": "^3.7.0",
|
|
45
46
|
"fast-xml-parser": "^5.3.3",
|
|
46
47
|
"glob": "^13.0.0",
|
|
48
|
+
"gray-matter": "^4.0.3",
|
|
49
|
+
"highlight.js": "^11.11.1",
|
|
50
|
+
"katex": "^0.16.27",
|
|
51
|
+
"markdown-it": "^14.1.0",
|
|
52
|
+
"markdown-it-texmath": "^1.0.0",
|
|
53
|
+
"mermaid": "^11.12.2",
|
|
47
54
|
"rollup-plugin-minify-template-literals": "^1.1.7",
|
|
48
55
|
"sharp": "^0.34.5",
|
|
49
56
|
"svgo": "^4.0.0",
|
|
50
57
|
"vite": "^7.1.3",
|
|
51
58
|
"vite-plugin-image-optimizer": "^2.0.3",
|
|
52
|
-
"@inglorious/web": "4.0.
|
|
59
|
+
"@inglorious/web": "4.0.3"
|
|
53
60
|
},
|
|
54
61
|
"devDependencies": {
|
|
55
62
|
"prettier": "^3.6.2",
|
package/src/build/index.js
CHANGED
|
@@ -103,10 +103,12 @@ export async function build(options = {}) {
|
|
|
103
103
|
loader,
|
|
104
104
|
)
|
|
105
105
|
// For skipped pages, load their metadata from disk if needed for sitemap/RSS
|
|
106
|
-
const skippedPages = await generatePages(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
const skippedPages = await generatePages(
|
|
107
|
+
store,
|
|
108
|
+
pagesToSkip,
|
|
109
|
+
{ ...mergedOptions, shouldGenerateHtml: false },
|
|
110
|
+
loader,
|
|
111
|
+
)
|
|
110
112
|
|
|
111
113
|
// Combine rendered and skipped pages for sitemap/RSS
|
|
112
114
|
const allGeneratedPages = [...changedPages, ...skippedPages]
|
package/src/build/metadata.js
CHANGED
|
@@ -24,9 +24,16 @@ export function extractPageMetadata(store, page, entity, options = {}) {
|
|
|
24
24
|
|
|
25
25
|
// sitemap metadata
|
|
26
26
|
const loc = `${hostname}${path}`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
|
|
28
|
+
let lastmod
|
|
29
|
+
try {
|
|
30
|
+
lastmod = updatedAt
|
|
31
|
+
? new Date(updatedAt).toISOString().split("T")[0]
|
|
32
|
+
: new Date().toISOString().split("T")[0]
|
|
33
|
+
} catch {
|
|
34
|
+
console.warn(`⚠️ Invalid updatedAt date for page ${path}: ${updatedAt}`)
|
|
35
|
+
lastmod = new Date().toISOString().split("T")[0]
|
|
36
|
+
}
|
|
30
37
|
|
|
31
38
|
// rss metadata
|
|
32
39
|
const title = getPageOption("title", DEFAULT_OPTIONS)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { extractPageMetadata } from "./metadata.js"
|
|
4
|
+
|
|
5
|
+
describe("extractPageMetadata", () => {
|
|
6
|
+
it("should handle invalid updatedAt gracefully", () => {
|
|
7
|
+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
8
|
+
const store = { _api: {} }
|
|
9
|
+
const page = {
|
|
10
|
+
path: "/test",
|
|
11
|
+
module: { metadata: { updatedAt: "invalid-date" } },
|
|
12
|
+
}
|
|
13
|
+
const entity = {}
|
|
14
|
+
|
|
15
|
+
const metadata = extractPageMetadata(store, page, entity)
|
|
16
|
+
expect(metadata.lastmod).toBeDefined()
|
|
17
|
+
expect(consoleSpy).toHaveBeenCalled()
|
|
18
|
+
})
|
|
19
|
+
})
|
package/src/build/vite-config.js
CHANGED
|
@@ -3,6 +3,8 @@ import path from "node:path"
|
|
|
3
3
|
import { mergeConfig } from "vite"
|
|
4
4
|
import { ViteImageOptimizer } from "vite-plugin-image-optimizer"
|
|
5
5
|
|
|
6
|
+
import { markdownPlugin } from "../utils/markdown.js"
|
|
7
|
+
|
|
6
8
|
// import { minifyTemplateLiterals } from "rollup-plugin-minify-template-literals"
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -14,6 +16,7 @@ export function createViteConfig(options = {}) {
|
|
|
14
16
|
outDir = "dist",
|
|
15
17
|
publicDir = "public",
|
|
16
18
|
vite = {},
|
|
19
|
+
markdown = {},
|
|
17
20
|
} = options
|
|
18
21
|
|
|
19
22
|
return mergeConfig(
|
|
@@ -25,6 +28,7 @@ export function createViteConfig(options = {}) {
|
|
|
25
28
|
ViteImageOptimizer({
|
|
26
29
|
// Options can be overridden by the user in site.config.js via the `vite` property
|
|
27
30
|
}),
|
|
31
|
+
markdownPlugin(markdown),
|
|
28
32
|
],
|
|
29
33
|
build: {
|
|
30
34
|
outDir,
|
package/src/dev/vite-config.js
CHANGED
|
@@ -2,6 +2,8 @@ import path from "node:path"
|
|
|
2
2
|
|
|
3
3
|
import { mergeConfig } from "vite"
|
|
4
4
|
|
|
5
|
+
import { markdownPlugin } from "../utils/markdown.js"
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Creates a Vite configuration object for the SSX dev server.
|
|
7
9
|
* It sets up the root directory, public directory, aliases, and the virtual file plugin.
|
|
@@ -13,7 +15,12 @@ import { mergeConfig } from "vite"
|
|
|
13
15
|
* @returns {Object} The merged Vite configuration.
|
|
14
16
|
*/
|
|
15
17
|
export function createViteConfig(options = {}) {
|
|
16
|
-
const {
|
|
18
|
+
const {
|
|
19
|
+
rootDir = "src",
|
|
20
|
+
publicDir = "public",
|
|
21
|
+
vite = {},
|
|
22
|
+
markdown = {},
|
|
23
|
+
} = options
|
|
17
24
|
const { port = 3000 } = vite.dev ?? {}
|
|
18
25
|
|
|
19
26
|
return mergeConfig(
|
|
@@ -22,7 +29,7 @@ export function createViteConfig(options = {}) {
|
|
|
22
29
|
publicDir: path.resolve(process.cwd(), rootDir, publicDir),
|
|
23
30
|
server: { port, middlewareMode: true },
|
|
24
31
|
appType: "custom",
|
|
25
|
-
plugins: [virtualPlugin()],
|
|
32
|
+
plugins: [virtualPlugin(), markdownPlugin(markdown)],
|
|
26
33
|
resolve: {
|
|
27
34
|
alias: {
|
|
28
35
|
"@": path.resolve(process.cwd(), rootDir),
|
package/src/router/index.js
CHANGED
|
@@ -124,7 +124,7 @@ export async function resolvePage(url, pagesDir = "pages") {
|
|
|
124
124
|
*/
|
|
125
125
|
export async function getRoutes(pagesDir = "pages") {
|
|
126
126
|
// Find all .js and .ts files in pages directory
|
|
127
|
-
const files = await glob("**/*.{js,ts,jsx,tsx}", {
|
|
127
|
+
const files = await glob("**/*.{js,ts,jsx,tsx,md}", {
|
|
128
128
|
cwd: pagesDir,
|
|
129
129
|
ignore: ["**/*.test.{js,ts}", "**/*.spec.{js,ts}"],
|
|
130
130
|
posix: true,
|
|
@@ -192,7 +192,7 @@ export function matchRoute(pattern, url) {
|
|
|
192
192
|
function filePathToPattern(file) {
|
|
193
193
|
let pattern = file
|
|
194
194
|
.replace(/\\/g, "/")
|
|
195
|
-
.replace(/\.(js|ts|jsx|tsx)$/, "") // Remove extension
|
|
195
|
+
.replace(/\.(js|ts|jsx|tsx|md)$/, "") // Remove extension
|
|
196
196
|
.replace(/\/index$/, "") // index becomes root of directory
|
|
197
197
|
.replace(/^index$/, "") // Handle root index
|
|
198
198
|
.replace(/__(\w+)/g, "*") // __path becomes *
|
|
@@ -39,7 +39,7 @@ describe("router", () => {
|
|
|
39
39
|
// Root usually comes after specific paths but before catch-all if it was a catch-all root,
|
|
40
40
|
// but here / is static.
|
|
41
41
|
// Let's just check that we found them.
|
|
42
|
-
expect(routes).toHaveLength(
|
|
42
|
+
expect(routes).toHaveLength(6)
|
|
43
43
|
})
|
|
44
44
|
})
|
|
45
45
|
|
|
@@ -100,11 +100,11 @@ describe("router", () => {
|
|
|
100
100
|
expect(pages).toMatchSnapshot()
|
|
101
101
|
|
|
102
102
|
// Dynamic route without staticPaths should be skipped (and warn)
|
|
103
|
-
const blogPage = pages.find((p) => p.path.includes("/
|
|
103
|
+
const blogPage = pages.find((p) => p.path.includes("/api/"))
|
|
104
104
|
expect(blogPage).toBeUndefined()
|
|
105
105
|
|
|
106
106
|
expect(consoleSpy).toHaveBeenCalled()
|
|
107
|
-
expect(consoleSpy.mock.calls[
|
|
107
|
+
expect(consoleSpy.mock.calls[2][0]).toContain("has no staticPaths")
|
|
108
108
|
})
|
|
109
109
|
})
|
|
110
110
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import hljs from "highlight.js"
|
|
2
|
+
import katex from "katex"
|
|
3
|
+
import MarkdownIt from "markdown-it"
|
|
4
|
+
import texmath from "markdown-it-texmath"
|
|
5
|
+
|
|
6
|
+
export function createMarkdownRenderer() {
|
|
7
|
+
const md = new MarkdownIt({
|
|
8
|
+
html: true,
|
|
9
|
+
linkify: true,
|
|
10
|
+
typographer: true,
|
|
11
|
+
highlight: (str, lang) => {
|
|
12
|
+
if (lang === "mermaid") {
|
|
13
|
+
return `<div class="mermaid">${str}</div>`
|
|
14
|
+
}
|
|
15
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
16
|
+
try {
|
|
17
|
+
return hljs.highlight(str, { language: lang }).value
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error(err)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return "" // use external default escaping
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Add LaTeX support
|
|
27
|
+
md.use(texmath, {
|
|
28
|
+
engine: katex,
|
|
29
|
+
delimiters: "dollars",
|
|
30
|
+
katexOptions: { macros: { "\\RR": "\\mathbb{R}" } },
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return md
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function renderMarkdown(markdown) {
|
|
37
|
+
const md = createMarkdownRenderer()
|
|
38
|
+
// Simple frontmatter stripping for runtime rendering (avoids Buffer/gray-matter on client)
|
|
39
|
+
const content = markdown.replace(/^---[\s\S]*?---\n/, "")
|
|
40
|
+
return md.render(content)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function markdownPlugin(options = {}) {
|
|
44
|
+
const { theme = "github-dark" } = options
|
|
45
|
+
const md = createMarkdownRenderer()
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
name: "ssx-markdown",
|
|
49
|
+
enforce: "pre",
|
|
50
|
+
async transform(code, id) {
|
|
51
|
+
if (!id.endsWith(".md")) return
|
|
52
|
+
|
|
53
|
+
const matter = (await import("gray-matter")).default
|
|
54
|
+
const { content, data } = matter(code)
|
|
55
|
+
const htmlContent = md.render(content)
|
|
56
|
+
const hasMermaid =
|
|
57
|
+
content.includes('class="mermaid"') || content.includes("```mermaid")
|
|
58
|
+
|
|
59
|
+
let mermaidCode = ""
|
|
60
|
+
if (hasMermaid) {
|
|
61
|
+
mermaidCode = `
|
|
62
|
+
import mermaid from "mermaid"
|
|
63
|
+
if (typeof window !== "undefined") {
|
|
64
|
+
mermaid.initialize({ startOnLoad: false })
|
|
65
|
+
}
|
|
66
|
+
`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return `
|
|
70
|
+
import { html, unsafeHTML } from "@inglorious/web"
|
|
71
|
+
import "katex/dist/katex.min.css"
|
|
72
|
+
import "highlight.js/styles/${theme}.css"
|
|
73
|
+
${mermaidCode}
|
|
74
|
+
|
|
75
|
+
export const metadata = ${JSON.stringify(data)}
|
|
76
|
+
|
|
77
|
+
export default {
|
|
78
|
+
render() {
|
|
79
|
+
if (typeof window !== "undefined" && ${hasMermaid}) {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
mermaid.run({ querySelector: ".mermaid" })
|
|
82
|
+
}, 0)
|
|
83
|
+
}
|
|
84
|
+
return html\`<div class="markdown-body">\${unsafeHTML(${JSON.stringify(htmlContent)})}</div>\`
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,280 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
* Represents a page being built or processed.
|
|
3
|
-
*/
|
|
4
|
-
export interface Page {
|
|
5
|
-
/**
|
|
6
|
-
* The final URL path for the page (e.g., "/about").
|
|
7
|
-
*/
|
|
8
|
-
path: string
|
|
9
|
-
/**
|
|
10
|
-
* The route pattern that matched this page (e.g., "/posts/:id").
|
|
11
|
-
*/
|
|
12
|
-
pattern: string
|
|
13
|
-
/**
|
|
14
|
-
* The absolute file path to the source component.
|
|
15
|
-
*/
|
|
16
|
-
filePath: string
|
|
17
|
-
[key: string]: any
|
|
18
|
-
}
|
|
1
|
+
import type { UserConfig } from "vite"
|
|
19
2
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The language attribute for the <html> tag.
|
|
26
|
-
*/
|
|
3
|
+
export interface SiteConfig {
|
|
4
|
+
/** Site title */
|
|
5
|
+
title?: string
|
|
6
|
+
/** HTML lang attribute */
|
|
27
7
|
lang?: string
|
|
28
|
-
/**
|
|
29
|
-
* The character encoding.
|
|
30
|
-
*/
|
|
8
|
+
/** HTML charset */
|
|
31
9
|
charset?: string
|
|
32
|
-
/**
|
|
33
|
-
* The page title.
|
|
34
|
-
*/
|
|
35
|
-
title?: string
|
|
36
|
-
/**
|
|
37
|
-
* Meta tags to include in <head>.
|
|
38
|
-
*/
|
|
10
|
+
/** Meta tags */
|
|
39
11
|
meta?: Record<string, string>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
12
|
+
|
|
13
|
+
/** Global styles to inject */
|
|
43
14
|
styles?: string[]
|
|
44
|
-
/**
|
|
45
|
-
* Additional HTML to inject into <head>.
|
|
46
|
-
*/
|
|
47
|
-
head?: string
|
|
48
|
-
/**
|
|
49
|
-
* Scripts to include.
|
|
50
|
-
*/
|
|
15
|
+
/** Global scripts to inject */
|
|
51
16
|
scripts?: string[]
|
|
52
|
-
/**
|
|
53
|
-
* Whether the build is running in development mode.
|
|
54
|
-
*/
|
|
55
|
-
isDev?: boolean
|
|
56
|
-
[key: string]: any
|
|
57
|
-
}
|
|
58
17
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
*/
|
|
70
|
-
filter?: (page: Page) => boolean
|
|
71
|
-
/**
|
|
72
|
-
* Default values for sitemap entries.
|
|
73
|
-
*/
|
|
74
|
-
defaults?: {
|
|
75
|
-
/**
|
|
76
|
-
* How frequently the page is likely to change.
|
|
77
|
-
*/
|
|
78
|
-
changefreq?: string
|
|
79
|
-
/**
|
|
80
|
-
* The priority of this URL relative to other URLs on your site.
|
|
81
|
-
*/
|
|
82
|
-
priority?: number
|
|
83
|
-
/**
|
|
84
|
-
* The date of last modification.
|
|
85
|
-
*/
|
|
86
|
-
lastmod?: string | Date
|
|
87
|
-
}
|
|
88
|
-
}
|
|
18
|
+
/** Source root directory (default: "src") */
|
|
19
|
+
rootDir?: string
|
|
20
|
+
/** Output directory (default: "dist") */
|
|
21
|
+
outDir?: string
|
|
22
|
+
/** Public directory (default: "public") */
|
|
23
|
+
publicDir?: string
|
|
24
|
+
/** Base path for the site */
|
|
25
|
+
basePath?: string
|
|
26
|
+
/** Favicon path */
|
|
27
|
+
favicon?: string
|
|
89
28
|
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
* The title of the RSS feed.
|
|
96
|
-
*/
|
|
97
|
-
title: string
|
|
98
|
-
/**
|
|
99
|
-
* The description of the RSS feed.
|
|
100
|
-
*/
|
|
101
|
-
description: string
|
|
102
|
-
/**
|
|
103
|
-
* The link to the site associated with the feed.
|
|
104
|
-
*/
|
|
105
|
-
link: string
|
|
106
|
-
/**
|
|
107
|
-
* The output path for the RSS feed file.
|
|
108
|
-
* @default "/feed.xml"
|
|
109
|
-
*/
|
|
110
|
-
feedPath?: string
|
|
111
|
-
/**
|
|
112
|
-
* The language of the feed.
|
|
113
|
-
*/
|
|
114
|
-
language?: string
|
|
115
|
-
/**
|
|
116
|
-
* Copyright notice for content in the feed.
|
|
117
|
-
*/
|
|
118
|
-
copyright?: string
|
|
119
|
-
/**
|
|
120
|
-
* Maximum number of items to include in the feed.
|
|
121
|
-
*/
|
|
122
|
-
maxItems?: number
|
|
123
|
-
/**
|
|
124
|
-
* A function to filter which pages are included in the feed.
|
|
125
|
-
*/
|
|
126
|
-
filter?: (page: Page) => boolean
|
|
127
|
-
}
|
|
29
|
+
/** Router configuration */
|
|
30
|
+
router?: {
|
|
31
|
+
trailingSlash?: boolean
|
|
32
|
+
scrollBehavior?: "auto" | "smooth"
|
|
33
|
+
}
|
|
128
34
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
*/
|
|
132
|
-
export interface RedirectConfig {
|
|
133
|
-
/**
|
|
134
|
-
* The source path or pattern to redirect from.
|
|
135
|
-
*/
|
|
136
|
-
from: string
|
|
137
|
-
/**
|
|
138
|
-
* The destination path to redirect to.
|
|
139
|
-
*/
|
|
140
|
-
to: string
|
|
141
|
-
/**
|
|
142
|
-
* The HTTP status code for the redirect.
|
|
143
|
-
* @default 301
|
|
144
|
-
*/
|
|
145
|
-
status?: number
|
|
146
|
-
}
|
|
35
|
+
/** Vite configuration */
|
|
36
|
+
vite?: UserConfig
|
|
147
37
|
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* Whether to enforce trailing slashes on URLs.
|
|
154
|
-
*/
|
|
155
|
-
trailingSlash?: boolean
|
|
156
|
-
/**
|
|
157
|
-
* The scroll behavior when navigating between pages.
|
|
158
|
-
*/
|
|
159
|
-
scrollBehavior?: "auto" | "smooth"
|
|
160
|
-
}
|
|
38
|
+
/** Markdown configuration */
|
|
39
|
+
markdown?: {
|
|
40
|
+
/** Highlight.js theme (default: "github-dark") */
|
|
41
|
+
theme?: string
|
|
42
|
+
}
|
|
161
43
|
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
44
|
+
/** Sitemap configuration */
|
|
45
|
+
sitemap?: {
|
|
46
|
+
hostname: string
|
|
47
|
+
filter?: (page: any) => boolean
|
|
48
|
+
defaults?: {
|
|
49
|
+
changefreq?: string
|
|
50
|
+
priority?: number
|
|
51
|
+
}
|
|
52
|
+
}
|
|
172
53
|
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
afterBuild?: (result: BuildResult) => Promise<void> | void
|
|
185
|
-
/**
|
|
186
|
-
* Called after an individual page is built.
|
|
187
|
-
*/
|
|
188
|
-
onPageBuild?: (page: Page) => Promise<void> | void
|
|
189
|
-
}
|
|
54
|
+
/** RSS configuration */
|
|
55
|
+
rss?: {
|
|
56
|
+
title: string
|
|
57
|
+
description: string
|
|
58
|
+
link: string
|
|
59
|
+
feedPath?: string
|
|
60
|
+
language?: string
|
|
61
|
+
copyright?: string
|
|
62
|
+
maxItems?: number
|
|
63
|
+
filter?: (page: any) => boolean
|
|
64
|
+
}
|
|
190
65
|
|
|
191
|
-
/**
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
* The language attribute for the <html> tag.
|
|
197
|
-
* @default "en"
|
|
198
|
-
*/
|
|
199
|
-
lang?: string
|
|
200
|
-
/**
|
|
201
|
-
* The character encoding for the site.
|
|
202
|
-
* @default "UTF-8"
|
|
203
|
-
*/
|
|
204
|
-
charset?: string
|
|
205
|
-
/**
|
|
206
|
-
* The default title for pages.
|
|
207
|
-
*/
|
|
208
|
-
title?: string
|
|
209
|
-
/**
|
|
210
|
-
* Default meta tags to be applied to all pages.
|
|
211
|
-
* Keys are meta names/properties, values are content.
|
|
212
|
-
*/
|
|
213
|
-
meta?: Record<string, string>
|
|
214
|
-
/**
|
|
215
|
-
* List of CSS file paths or URLs to include globally.
|
|
216
|
-
*/
|
|
217
|
-
styles?: string[]
|
|
218
|
-
/**
|
|
219
|
-
* List of JavaScript file paths or URLs to include globally.
|
|
220
|
-
*/
|
|
221
|
-
scripts?: string[]
|
|
222
|
-
/**
|
|
223
|
-
* A function that renders the full HTML document structure.
|
|
224
|
-
* Receives the page body and options.
|
|
225
|
-
*/
|
|
226
|
-
layout?: (body: string, options: LayoutOptions) => string
|
|
227
|
-
/**
|
|
228
|
-
* A function to wrap the page content before layout.
|
|
229
|
-
* Useful for adding common UI elements like headers/footers around the content.
|
|
230
|
-
*/
|
|
231
|
-
wrapper?: (body: any) => any
|
|
232
|
-
/**
|
|
233
|
-
* The base URL path for the application.
|
|
234
|
-
* @default "/"
|
|
235
|
-
*/
|
|
236
|
-
basePath?: string
|
|
237
|
-
/**
|
|
238
|
-
* The directory containing source files.
|
|
239
|
-
* @default "src"
|
|
240
|
-
*/
|
|
241
|
-
rootDir?: string
|
|
242
|
-
/**
|
|
243
|
-
* The directory where build artifacts will be output.
|
|
244
|
-
* @default "dist"
|
|
245
|
-
*/
|
|
246
|
-
outDir?: string
|
|
247
|
-
/**
|
|
248
|
-
* The directory containing static assets to be copied to the output.
|
|
249
|
-
* @default "public"
|
|
250
|
-
*/
|
|
251
|
-
publicDir?: string
|
|
252
|
-
/**
|
|
253
|
-
* Path to the favicon file.
|
|
254
|
-
*/
|
|
255
|
-
favicon?: string
|
|
256
|
-
/**
|
|
257
|
-
* Configuration for generating a sitemap.xml.
|
|
258
|
-
*/
|
|
259
|
-
sitemap?: SitemapConfig
|
|
260
|
-
/**
|
|
261
|
-
* Configuration for generating an RSS feed.
|
|
262
|
-
*/
|
|
263
|
-
rss?: RssConfig
|
|
264
|
-
/**
|
|
265
|
-
* List of redirect rules.
|
|
266
|
-
*/
|
|
267
|
-
redirects?: RedirectConfig[]
|
|
268
|
-
/**
|
|
269
|
-
* Configuration for the client-side router.
|
|
270
|
-
*/
|
|
271
|
-
router?: RouterConfig
|
|
272
|
-
/**
|
|
273
|
-
* Configuration options passed directly to Vite.
|
|
274
|
-
*/
|
|
275
|
-
vite?: Record<string, any>
|
|
276
|
-
/**
|
|
277
|
-
* Lifecycle hooks for the build process.
|
|
278
|
-
*/
|
|
279
|
-
hooks?: SSXHooks
|
|
66
|
+
/** Build hooks */
|
|
67
|
+
hooks?: {
|
|
68
|
+
beforeBuild?: (config: SiteConfig) => Promise<void> | void
|
|
69
|
+
afterBuild?: (result: any) => Promise<void> | void
|
|
70
|
+
}
|
|
280
71
|
}
|