@inglorious/ssx 1.4.2 → 1.4.3
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 +724 -724
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,724 +1,724 @@
|
|
|
1
|
-
# @inglorious/ssx
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@inglorious/ssx)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
|
|
6
|
-
**Static Site Xecution** - Build blazing-fast static sites with [@inglorious/web](https://www.npmjs.com/package/@inglorious/web), complete with server-side rendering, client-side hydration, and zero-config routing.
|
|
7
|
-
|
|
8
|
-
SSX takes your entity-based web apps and generates optimized static HTML with full hydration support. Think Next.js SSG or Astro, but with the simplicity and predictability of Inglorious Web's entity architecture.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Why SSX?
|
|
13
|
-
|
|
14
|
-
### ⚡️ Fast by Default
|
|
15
|
-
|
|
16
|
-
- **Pre-rendered HTML** - Every page is built at compile time
|
|
17
|
-
- **Instant load times** - No waiting for server responses
|
|
18
|
-
- **CDN-ready** - Deploy anywhere static files are served
|
|
19
|
-
- **Perfect Lighthouse scores** - SEO and performance out of the box
|
|
20
|
-
|
|
21
|
-
### 🎯 Simple Architecture
|
|
22
|
-
|
|
23
|
-
- **No server required** - Pure static files
|
|
24
|
-
- **No complex build configs** - Convention over configuration
|
|
25
|
-
- **File-based routing** - Pages are just files in `src/pages/`
|
|
26
|
-
- **Entity-based state** - Same familiar patterns from @inglorious/web
|
|
27
|
-
|
|
28
|
-
### 🔥 Modern DX
|
|
29
|
-
|
|
30
|
-
- **Hot reload dev server** - See changes instantly
|
|
31
|
-
- **Lazy-loaded routes** - Code splitting automatically
|
|
32
|
-
- **lit-html hydration** - Interactive UI without the bloat
|
|
33
|
-
- **TypeScript Ready** - Write your pages and entities in TypeScript.
|
|
34
|
-
- **Image Optimization** - Automatic compression for static assets.
|
|
35
|
-
- **Markdown Support** - Built-in support for `.md` pages with code highlighting and math.
|
|
36
|
-
|
|
37
|
-
### 🚀 Production Ready
|
|
38
|
-
|
|
39
|
-
- **Automatic code splitting** - Per-page bundles
|
|
40
|
-
- **Optimized builds** - Minified, tree-shaken output
|
|
41
|
-
- **Source maps** - Debug production like development
|
|
42
|
-
- **Error boundaries** - Graceful failure handling
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Quick Start
|
|
47
|
-
|
|
48
|
-
### Installation
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm install @inglorious/ssx @inglorious/web
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Create Your First Site
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npx @inglorious/create-app my-site --template ssx-js
|
|
58
|
-
cd my-site
|
|
59
|
-
npm run dev
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Or manually:
|
|
63
|
-
|
|
64
|
-
### Create Your First Site (TypeScript)
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// src/pages/index.ts
|
|
68
|
-
import { html } from "@inglorious/web"
|
|
69
|
-
|
|
70
|
-
// You can import API for type safety, though it's optional
|
|
71
|
-
// import type { API } from "@inglorious/web"
|
|
72
|
-
|
|
73
|
-
export const index = {
|
|
74
|
-
render(/* entity: any, api: API */) {
|
|
75
|
-
return html`
|
|
76
|
-
<div>
|
|
77
|
-
<h1>Welcome to SSX!</h1>
|
|
78
|
-
<p>This page was pre-rendered at build time.</p>
|
|
79
|
-
<nav>
|
|
80
|
-
<a href="/about">About</a>
|
|
81
|
-
</nav>
|
|
82
|
-
</div>
|
|
83
|
-
`
|
|
84
|
-
},
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Create Your First Site (JavaScript)
|
|
89
|
-
|
|
90
|
-
```javascript
|
|
91
|
-
// src/pages/index.js
|
|
92
|
-
import { html } from "@inglorious/web"
|
|
93
|
-
|
|
94
|
-
export const index = {
|
|
95
|
-
render() {
|
|
96
|
-
return html`
|
|
97
|
-
<div>
|
|
98
|
-
<h1>Welcome to SSX!</h1>
|
|
99
|
-
<p>This page was pre-rendered at build time.</p>
|
|
100
|
-
<nav>
|
|
101
|
-
<a href="/about">About</a>
|
|
102
|
-
</nav>
|
|
103
|
-
</div>
|
|
104
|
-
`
|
|
105
|
-
},
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const metadata = {
|
|
109
|
-
title: "Home",
|
|
110
|
-
meta: {
|
|
111
|
-
description: "Welcome to our site",
|
|
112
|
-
"og:image": "/og-image.png",
|
|
113
|
-
},
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Development
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
npm run dev
|
|
121
|
-
# → Dev server at http://localhost:3000
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Build
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
npm run build
|
|
128
|
-
# → Static site in dist/
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Deploy
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
npm run preview
|
|
135
|
-
# → Preview production build
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Deploy `dist/` to:
|
|
139
|
-
|
|
140
|
-
- **Vercel** - Zero config
|
|
141
|
-
- **Netlify** - Drop folder
|
|
142
|
-
- **GitHub Pages** - Push and done
|
|
143
|
-
- **Cloudflare Pages** - Instant edge
|
|
144
|
-
- **Any CDN** - It's just files!
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## Features
|
|
149
|
-
|
|
150
|
-
### 🗺️ Sitemap & RSS Generation
|
|
151
|
-
|
|
152
|
-
SSX automatically generates `sitemap.xml` and `rss.xml` based on your pages. Configure them in `src/site.config.js`:
|
|
153
|
-
|
|
154
|
-
```javascript
|
|
155
|
-
export default {
|
|
156
|
-
// Basic metadata
|
|
157
|
-
title: "My Awesome Site",
|
|
158
|
-
meta: {
|
|
159
|
-
description: "A site built with SSX",
|
|
160
|
-
"og:type": "website",
|
|
161
|
-
"og:site_name": "My Site",
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
// Sitemap configuration
|
|
165
|
-
sitemap: {
|
|
166
|
-
hostname: "https://myblog.com",
|
|
167
|
-
filter: (page) => !["/admin", "/draft-*", "/test"].includes(page.pattern),
|
|
168
|
-
defaults: {
|
|
169
|
-
changefreq: "weekly",
|
|
170
|
-
priority: 0.5,
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
// RSS configuration
|
|
175
|
-
rss: {
|
|
176
|
-
title: "My Blog",
|
|
177
|
-
description: "Latest posts from my blog",
|
|
178
|
-
link: "https://myblog.com",
|
|
179
|
-
feedPath: "/feed.xml",
|
|
180
|
-
language: "en",
|
|
181
|
-
copyright: "© 2026 My Blog",
|
|
182
|
-
maxItems: 10,
|
|
183
|
-
filter: (page) => page.path.startsWith("/posts/"),
|
|
184
|
-
},
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
Pages with a `published` date in metadata are included in RSS feeds.
|
|
189
|
-
|
|
190
|
-
### 📁 File-Based Routing
|
|
191
|
-
|
|
192
|
-
Your file structure defines your routes:
|
|
193
|
-
|
|
194
|
-
```
|
|
195
|
-
src/pages/
|
|
196
|
-
├── index.js → /
|
|
197
|
-
├── about.js → /about
|
|
198
|
-
├── blog.js → /blog
|
|
199
|
-
└── posts/
|
|
200
|
-
└── _slug.js → /posts/:slug
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
Dynamic routes use underscore prefix: `_id.js`, `_slug.js`, etc.
|
|
204
|
-
|
|
205
|
-
### ⚛️ Entity-Based State And Behavior
|
|
206
|
-
|
|
207
|
-
```javascript
|
|
208
|
-
// src/pages/about.js
|
|
209
|
-
import { html } from "@inglorious/web"
|
|
210
|
-
|
|
211
|
-
export const about = {
|
|
212
|
-
click(entity) {
|
|
213
|
-
entity.name += "!"
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
render(entity, api) {
|
|
217
|
-
return html`<h1>
|
|
218
|
-
About
|
|
219
|
-
<span @click=${() => api.notify(`#${entity.id}:click`)}
|
|
220
|
-
>${entity.name}</span
|
|
221
|
-
>
|
|
222
|
-
</h1>`
|
|
223
|
-
},
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
```javascript
|
|
228
|
-
// src/store/entities.js
|
|
229
|
-
export const entities = {
|
|
230
|
-
about: {
|
|
231
|
-
type: "about",
|
|
232
|
-
name: "Us",
|
|
233
|
-
},
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### 🔄 Data Loading
|
|
238
|
-
|
|
239
|
-
Load data at build time with the `load` export:
|
|
240
|
-
|
|
241
|
-
```javascript
|
|
242
|
-
// src/pages/blog.js
|
|
243
|
-
import { html } from "@inglorious/web"
|
|
244
|
-
|
|
245
|
-
export const blog = {
|
|
246
|
-
render(entity) {
|
|
247
|
-
return html`
|
|
248
|
-
<h1>Blog Posts</h1>
|
|
249
|
-
<ul>
|
|
250
|
-
${entity.posts?.map(
|
|
251
|
-
(post) => html`
|
|
252
|
-
<li>
|
|
253
|
-
<a href="/posts/${post.id}">${post.title}</a>
|
|
254
|
-
</li>
|
|
255
|
-
`,
|
|
256
|
-
)}
|
|
257
|
-
</ul>
|
|
258
|
-
`
|
|
259
|
-
},
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// SSR: Load data during build
|
|
263
|
-
export async function load(entity) {
|
|
264
|
-
const response = await fetch("https://api.example.com/posts")
|
|
265
|
-
entity.posts = await response.json()
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export const title = "Blog"
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
The `load` function runs on the server during build. Data is serialized into the HTML and available immediately on the client.
|
|
272
|
-
|
|
273
|
-
### 🎨 Dynamic Routes with `staticPaths`
|
|
274
|
-
|
|
275
|
-
Generate multiple pages from data:
|
|
276
|
-
|
|
277
|
-
```javascript
|
|
278
|
-
// src/pages/posts/_slug.js
|
|
279
|
-
import { html } from "@inglorious/web"
|
|
280
|
-
|
|
281
|
-
export const post = {
|
|
282
|
-
render(entity) {
|
|
283
|
-
return html`
|
|
284
|
-
<article>
|
|
285
|
-
<h1>${entity.post.title}</h1>
|
|
286
|
-
<div>${entity.post.body}</div>
|
|
287
|
-
</article>
|
|
288
|
-
`
|
|
289
|
-
},
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Load data for a specific post
|
|
293
|
-
export async function load(entity, page) {
|
|
294
|
-
const response = await fetch(
|
|
295
|
-
`https://api.example.com/posts/${page.params.slug}`,
|
|
296
|
-
)
|
|
297
|
-
entity.post = await response.json()
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Tell SSX which pages to generate
|
|
301
|
-
export async function staticPaths() {
|
|
302
|
-
const response = await fetch(`https://api.example.com/posts`)
|
|
303
|
-
const posts = await response.json()
|
|
304
|
-
|
|
305
|
-
return posts.map((post) => ({
|
|
306
|
-
params: { slug: post.slug },
|
|
307
|
-
path: `/posts/${post.slug}`,
|
|
308
|
-
}))
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export const metadata = (entity) => ({
|
|
312
|
-
title: entity.post.title ?? "Post",
|
|
313
|
-
meta: {
|
|
314
|
-
description: entity.post.excerpt,
|
|
315
|
-
},
|
|
316
|
-
})
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### 📄 Page Metadata
|
|
320
|
-
|
|
321
|
-
Export metadata for HTML `<head>`. The `metadata` export can be a plain object or a function:
|
|
322
|
-
|
|
323
|
-
```javascript
|
|
324
|
-
export const index = {
|
|
325
|
-
render() {
|
|
326
|
-
return html`<h1>Home</h1>`
|
|
327
|
-
},
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Static metadata
|
|
331
|
-
export const metadata = {
|
|
332
|
-
title: "My Site",
|
|
333
|
-
meta: {
|
|
334
|
-
description: "An awesome static site",
|
|
335
|
-
"og:image": "/og-image.png",
|
|
336
|
-
},
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Or dynamic metadata (uses entity data)
|
|
340
|
-
export const metadata = (entity) => ({
|
|
341
|
-
title: `${entity.user.name}'s Profile`,
|
|
342
|
-
meta: {
|
|
343
|
-
description: entity.user.bio,
|
|
344
|
-
"og:image": entity.user.avatar,
|
|
345
|
-
},
|
|
346
|
-
})
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### 🔥 Client-Side Hydration
|
|
350
|
-
|
|
351
|
-
Pages hydrate automatically with lit-html. Interactivity works immediately:
|
|
352
|
-
|
|
353
|
-
```javascript
|
|
354
|
-
export const counter = {
|
|
355
|
-
click(entity) {
|
|
356
|
-
entity.count++
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
render(entity, api) {
|
|
360
|
-
return html`
|
|
361
|
-
<div>
|
|
362
|
-
<p>Count: ${entity.count}</p>
|
|
363
|
-
<button @click=${() => api.notify(`#${entity.id}:click`)}>
|
|
364
|
-
Increment
|
|
365
|
-
</button>
|
|
366
|
-
</div>
|
|
367
|
-
`
|
|
368
|
-
},
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
The HTML is pre-rendered on the server. When JavaScript loads, lit-html hydrates the existing DOM and wires up event handlers. No flash of unstyled content, no duplicate rendering.
|
|
373
|
-
|
|
374
|
-
### 🧭 Client-Side Navigation
|
|
375
|
-
|
|
376
|
-
After hydration, navigation is instant:
|
|
377
|
-
|
|
378
|
-
```javascript
|
|
379
|
-
// Links navigate without page reload
|
|
380
|
-
;<a href="/about">About</a> // Client-side routing
|
|
381
|
-
|
|
382
|
-
// Programmatic navigation
|
|
383
|
-
api.notify("navigate", "/posts")
|
|
384
|
-
|
|
385
|
-
// With options
|
|
386
|
-
api.notify("navigate", {
|
|
387
|
-
to: "/posts/123",
|
|
388
|
-
replace: true,
|
|
389
|
-
})
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
Routes are lazy-loaded on demand, keeping initial bundle size small.
|
|
393
|
-
|
|
394
|
-
### 🖼️ Image Optimization
|
|
395
|
-
|
|
396
|
-
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.
|
|
399
|
-
- **Lossless & Lossy** - Configurable settings via `vite` config in `site.config.js`.
|
|
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
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
|
-
## CLI
|
|
433
|
-
|
|
434
|
-
SSX provides a simple CLI for building and developing:
|
|
435
|
-
|
|
436
|
-
### `ssx build`
|
|
437
|
-
|
|
438
|
-
Builds your static site:
|
|
439
|
-
|
|
440
|
-
```bash
|
|
441
|
-
pnpm ssx build [options]
|
|
442
|
-
|
|
443
|
-
Options:
|
|
444
|
-
-c, --config <file> Config file (default: "site.config.js")
|
|
445
|
-
-r, --root <dir> Source root directory (default: "src")
|
|
446
|
-
-o, --out <dir> Output directory (default: "dist")
|
|
447
|
-
-i, --incremental Enable incremental builds (default: true)
|
|
448
|
-
-f, --force Force clean build, ignore cache
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### `preview`
|
|
452
|
-
|
|
453
|
-
Serves the built static site on port 3000 through the `serve` NPM package.
|
|
454
|
-
|
|
455
|
-
```bash
|
|
456
|
-
pnpm preview
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### `ssx dev`
|
|
460
|
-
|
|
461
|
-
Starts the Vite development server on port 3000 with hot reload:
|
|
462
|
-
|
|
463
|
-
```bash
|
|
464
|
-
pnpm ssx dev [options]
|
|
465
|
-
|
|
466
|
-
Options:
|
|
467
|
-
-c, --config <file> Config file (default: "site.config.js")
|
|
468
|
-
-r, --root <dir> Source root directory (default: "src")
|
|
469
|
-
-p, --port <port> Dev server port (default: 3000)
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
---
|
|
473
|
-
|
|
474
|
-
## Project Structure
|
|
475
|
-
|
|
476
|
-
```
|
|
477
|
-
my-site/
|
|
478
|
-
├── src/
|
|
479
|
-
│ ├── pages/ # File-based routes
|
|
480
|
-
│ │ ├── index.js # Home page
|
|
481
|
-
│ │ ├── about.js # About page
|
|
482
|
-
│ │ └── posts/
|
|
483
|
-
│ │ ├── index.js # /posts
|
|
484
|
-
│ │ └── _id.js # /posts/:id
|
|
485
|
-
│ ├── store/ # Store configuration
|
|
486
|
-
│ │ └── entities.js # Entity definitions
|
|
487
|
-
│ └── types/ # Custom entity types (optional)
|
|
488
|
-
├── dist/ # Build output
|
|
489
|
-
├── package.json
|
|
490
|
-
└── site.config.js # Site configuration
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
---
|
|
494
|
-
|
|
495
|
-
## Comparison to Other Tools
|
|
496
|
-
|
|
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 | ❌ | ❌ | ✅ | ✅ |
|
|
506
|
-
|
|
507
|
-
SSX is perfect if you:
|
|
508
|
-
|
|
509
|
-
- Want static site performance
|
|
510
|
-
- Love entity-based architecture
|
|
511
|
-
- Prefer convention over configuration
|
|
512
|
-
- Need full client-side interactivity
|
|
513
|
-
- Don't want React/Vue lock-in
|
|
514
|
-
|
|
515
|
-
---
|
|
516
|
-
|
|
517
|
-
## Advanced Usage
|
|
518
|
-
|
|
519
|
-
### Site Configuration
|
|
520
|
-
|
|
521
|
-
Customize SSX behavior in `src/site.config.js`:
|
|
522
|
-
|
|
523
|
-
```javascript
|
|
524
|
-
export default {
|
|
525
|
-
// Basic metadata
|
|
526
|
-
lang: "en",
|
|
527
|
-
charset: "UTF-8",
|
|
528
|
-
title: "My Awesome Site",
|
|
529
|
-
meta: {
|
|
530
|
-
description: "A site built with SSX",
|
|
531
|
-
"og:type": "website",
|
|
532
|
-
},
|
|
533
|
-
|
|
534
|
-
// Global assets
|
|
535
|
-
styles: ["./styles/reset.css", "./styles/theme.css"],
|
|
536
|
-
scripts: ["./scripts/analytics.js"],
|
|
537
|
-
|
|
538
|
-
// Build options
|
|
539
|
-
basePath: "/",
|
|
540
|
-
rootDir: "src",
|
|
541
|
-
outDir: "dist",
|
|
542
|
-
publicDir: "public",
|
|
543
|
-
favicon: "/favicon.ico",
|
|
544
|
-
|
|
545
|
-
// Router config
|
|
546
|
-
router: {
|
|
547
|
-
trailingSlash: false,
|
|
548
|
-
scrollBehavior: "smooth",
|
|
549
|
-
},
|
|
550
|
-
|
|
551
|
-
// Vite config passthrough
|
|
552
|
-
vite: {
|
|
553
|
-
server: {
|
|
554
|
-
port: 3000,
|
|
555
|
-
open: true,
|
|
556
|
-
},
|
|
557
|
-
},
|
|
558
|
-
|
|
559
|
-
// Build hooks
|
|
560
|
-
hooks: {
|
|
561
|
-
beforeBuild: async (config) => console.log("Starting build..."),
|
|
562
|
-
afterBuild: async (result) => console.log(`Built ${result.pages} pages`),
|
|
563
|
-
},
|
|
564
|
-
}
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### Environment Variables
|
|
568
|
-
|
|
569
|
-
Use Vite's environment variables:
|
|
570
|
-
|
|
571
|
-
```javascript
|
|
572
|
-
// Access in your code
|
|
573
|
-
const apiUrl = import.meta.env.VITE_API_URL
|
|
574
|
-
|
|
575
|
-
// .env file
|
|
576
|
-
VITE_API_URL=https://api.example.com
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
### Custom 404 Page
|
|
580
|
-
|
|
581
|
-
Create a fallback route:
|
|
582
|
-
|
|
583
|
-
```javascript
|
|
584
|
-
// src/pages/404.js
|
|
585
|
-
export const notFound = {
|
|
586
|
-
render() {
|
|
587
|
-
return html`
|
|
588
|
-
<div>
|
|
589
|
-
<h1>404 - Page Not Found</h1>
|
|
590
|
-
<a href="/">Go Home</a>
|
|
591
|
-
</div>
|
|
592
|
-
`
|
|
593
|
-
},
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export const metadata = {
|
|
597
|
-
title: "404",
|
|
598
|
-
}
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
Register it in your router:
|
|
602
|
-
|
|
603
|
-
```javascript
|
|
604
|
-
// src/store/entities.js
|
|
605
|
-
import { setRoutes } from "@inglorious/web/router"
|
|
606
|
-
|
|
607
|
-
setRoutes({
|
|
608
|
-
// ... other routes
|
|
609
|
-
"*": "notFound", // Fallback
|
|
610
|
-
})
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
### Incremental Builds
|
|
614
|
-
|
|
615
|
-
SSX enables incremental builds by default. Only changed pages are rebuilt, dramatically speeding up your build process:
|
|
616
|
-
|
|
617
|
-
```bash
|
|
618
|
-
ssx build
|
|
619
|
-
# Only changed pages are rebuilt
|
|
620
|
-
|
|
621
|
-
ssx build --force
|
|
622
|
-
# Force a clean rebuild of all pages
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
Incremental builds respect your page dependencies and invalidate cache when dependencies change.
|
|
626
|
-
|
|
627
|
-
---
|
|
628
|
-
|
|
629
|
-
## API Reference
|
|
630
|
-
|
|
631
|
-
### Build API
|
|
632
|
-
|
|
633
|
-
```javascript
|
|
634
|
-
import { build } from "@inglorious/ssx/build"
|
|
635
|
-
|
|
636
|
-
await build({
|
|
637
|
-
rootDir: "src",
|
|
638
|
-
outDir: "dist",
|
|
639
|
-
configFile: "site.config.js",
|
|
640
|
-
incremental: true,
|
|
641
|
-
clean: false,
|
|
642
|
-
})
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
### Dev Server API
|
|
646
|
-
|
|
647
|
-
```javascript
|
|
648
|
-
import { dev } from "@inglorious/ssx/dev"
|
|
649
|
-
|
|
650
|
-
await dev({
|
|
651
|
-
rootDir: "src",
|
|
652
|
-
port: 3000,
|
|
653
|
-
configFile: "site.config.js",
|
|
654
|
-
})
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
<!-- ## Examples
|
|
660
|
-
|
|
661
|
-
Check out these example projects:
|
|
662
|
-
|
|
663
|
-
- **[Basic Blog](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-blog)** - Simple blog with posts
|
|
664
|
-
- **[Documentation Site](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-docs)** - Multi-page docs
|
|
665
|
-
- **[E-commerce](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-shop)** - Product catalog
|
|
666
|
-
- **[Portfolio](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-portfolio)** - Personal portfolio
|
|
667
|
-
|
|
668
|
-
--- -->
|
|
669
|
-
|
|
670
|
-
## Roadmap
|
|
671
|
-
|
|
672
|
-
- [x] TypeScript support
|
|
673
|
-
- [x] Image optimization
|
|
674
|
-
- [ ] API routes (serverless functions)
|
|
675
|
-
- [x] Markdown support
|
|
676
|
-
- [ ] i18n helpers
|
|
677
|
-
|
|
678
|
-
---
|
|
679
|
-
|
|
680
|
-
## Philosophy
|
|
681
|
-
|
|
682
|
-
SSX embraces the philosophy of [@inglorious/web](https://www.npmjs.com/package/@inglorious/web):
|
|
683
|
-
|
|
684
|
-
- **Simplicity over cleverness** - Obvious beats clever
|
|
685
|
-
- **Convention over configuration** - Sensible defaults
|
|
686
|
-
- **Predictability over magic** - Explicit is better than implicit
|
|
687
|
-
- **Standards over abstractions** - Use the platform
|
|
688
|
-
|
|
689
|
-
Static site generation should be simple. SSX makes it simple.
|
|
690
|
-
|
|
691
|
-
---
|
|
692
|
-
|
|
693
|
-
## Contributing
|
|
694
|
-
|
|
695
|
-
Contributions are welcome! Please read our [Contributing Guidelines](../../CONTRIBUTING.md) first.
|
|
696
|
-
|
|
697
|
-
---
|
|
698
|
-
|
|
699
|
-
## License
|
|
700
|
-
|
|
701
|
-
**MIT License** - Free and open source
|
|
702
|
-
|
|
703
|
-
Created by [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
|
|
704
|
-
|
|
705
|
-
---
|
|
706
|
-
|
|
707
|
-
## Related Packages
|
|
708
|
-
|
|
709
|
-
- [@inglorious/web](https://www.npmjs.com/package/@inglorious/web) - Entity-based web framework
|
|
710
|
-
- [@inglorious/store](https://www.npmjs.com/package/@inglorious/store) - State management
|
|
711
|
-
- [@inglorious/engine](https://www.npmjs.com/package/@inglorious/engine) - Game engine
|
|
712
|
-
|
|
713
|
-
---
|
|
714
|
-
|
|
715
|
-
## Support
|
|
716
|
-
|
|
717
|
-
- 📖 [Documentation](https://inglorious-engine.vercel.app)
|
|
718
|
-
- 💬 [Discord Community](https://discord.gg/Byx85t2eFp)
|
|
719
|
-
- 🐛 [Issue Tracker](https://github.com/IngloriousCoderz/inglorious-forge/issues)
|
|
720
|
-
- 📧 [Email Support](mailto:antony.mistretta@gmail.com)
|
|
721
|
-
|
|
722
|
-
---
|
|
723
|
-
|
|
724
|
-
**Build static sites the Inglorious way. Simple. Predictable. Fast.** 🚀
|
|
1
|
+
# @inglorious/ssx
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@inglorious/ssx)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**Static Site Xecution** - Build blazing-fast static sites with [@inglorious/web](https://www.npmjs.com/package/@inglorious/web), complete with server-side rendering, client-side hydration, and zero-config routing.
|
|
7
|
+
|
|
8
|
+
SSX takes your entity-based web apps and generates optimized static HTML with full hydration support. Think Next.js SSG or Astro, but with the simplicity and predictability of Inglorious Web's entity architecture.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Why SSX?
|
|
13
|
+
|
|
14
|
+
### ⚡️ Fast by Default
|
|
15
|
+
|
|
16
|
+
- **Pre-rendered HTML** - Every page is built at compile time
|
|
17
|
+
- **Instant load times** - No waiting for server responses
|
|
18
|
+
- **CDN-ready** - Deploy anywhere static files are served
|
|
19
|
+
- **Perfect Lighthouse scores** - SEO and performance out of the box
|
|
20
|
+
|
|
21
|
+
### 🎯 Simple Architecture
|
|
22
|
+
|
|
23
|
+
- **No server required** - Pure static files
|
|
24
|
+
- **No complex build configs** - Convention over configuration
|
|
25
|
+
- **File-based routing** - Pages are just files in `src/pages/`
|
|
26
|
+
- **Entity-based state** - Same familiar patterns from @inglorious/web
|
|
27
|
+
|
|
28
|
+
### 🔥 Modern DX
|
|
29
|
+
|
|
30
|
+
- **Hot reload dev server** - See changes instantly
|
|
31
|
+
- **Lazy-loaded routes** - Code splitting automatically
|
|
32
|
+
- **lit-html hydration** - Interactive UI without the bloat
|
|
33
|
+
- **TypeScript Ready** - Write your pages and entities in TypeScript.
|
|
34
|
+
- **Image Optimization** - Automatic compression for static assets.
|
|
35
|
+
- **Markdown Support** - Built-in support for `.md` pages with code highlighting and math.
|
|
36
|
+
|
|
37
|
+
### 🚀 Production Ready
|
|
38
|
+
|
|
39
|
+
- **Automatic code splitting** - Per-page bundles
|
|
40
|
+
- **Optimized builds** - Minified, tree-shaken output
|
|
41
|
+
- **Source maps** - Debug production like development
|
|
42
|
+
- **Error boundaries** - Graceful failure handling
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @inglorious/ssx @inglorious/web
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Create Your First Site
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx @inglorious/create-app my-site --template ssx-js
|
|
58
|
+
cd my-site
|
|
59
|
+
npm run dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or manually:
|
|
63
|
+
|
|
64
|
+
### Create Your First Site (TypeScript)
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// src/pages/index.ts
|
|
68
|
+
import { html } from "@inglorious/web"
|
|
69
|
+
|
|
70
|
+
// You can import API for type safety, though it's optional
|
|
71
|
+
// import type { API } from "@inglorious/web"
|
|
72
|
+
|
|
73
|
+
export const index = {
|
|
74
|
+
render(/* entity: any, api: API */) {
|
|
75
|
+
return html`
|
|
76
|
+
<div>
|
|
77
|
+
<h1>Welcome to SSX!</h1>
|
|
78
|
+
<p>This page was pre-rendered at build time.</p>
|
|
79
|
+
<nav>
|
|
80
|
+
<a href="/about">About</a>
|
|
81
|
+
</nav>
|
|
82
|
+
</div>
|
|
83
|
+
`
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Create Your First Site (JavaScript)
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
// src/pages/index.js
|
|
92
|
+
import { html } from "@inglorious/web"
|
|
93
|
+
|
|
94
|
+
export const index = {
|
|
95
|
+
render() {
|
|
96
|
+
return html`
|
|
97
|
+
<div>
|
|
98
|
+
<h1>Welcome to SSX!</h1>
|
|
99
|
+
<p>This page was pre-rendered at build time.</p>
|
|
100
|
+
<nav>
|
|
101
|
+
<a href="/about">About</a>
|
|
102
|
+
</nav>
|
|
103
|
+
</div>
|
|
104
|
+
`
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const metadata = {
|
|
109
|
+
title: "Home",
|
|
110
|
+
meta: {
|
|
111
|
+
description: "Welcome to our site",
|
|
112
|
+
"og:image": "/og-image.png",
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run dev
|
|
121
|
+
# → Dev server at http://localhost:3000
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Build
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run build
|
|
128
|
+
# → Static site in dist/
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Deploy
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm run preview
|
|
135
|
+
# → Preview production build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Deploy `dist/` to:
|
|
139
|
+
|
|
140
|
+
- **Vercel** - Zero config
|
|
141
|
+
- **Netlify** - Drop folder
|
|
142
|
+
- **GitHub Pages** - Push and done
|
|
143
|
+
- **Cloudflare Pages** - Instant edge
|
|
144
|
+
- **Any CDN** - It's just files!
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Features
|
|
149
|
+
|
|
150
|
+
### 🗺️ Sitemap & RSS Generation
|
|
151
|
+
|
|
152
|
+
SSX automatically generates `sitemap.xml` and `rss.xml` based on your pages. Configure them in `src/site.config.js`:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
export default {
|
|
156
|
+
// Basic metadata
|
|
157
|
+
title: "My Awesome Site",
|
|
158
|
+
meta: {
|
|
159
|
+
description: "A site built with SSX",
|
|
160
|
+
"og:type": "website",
|
|
161
|
+
"og:site_name": "My Site",
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Sitemap configuration
|
|
165
|
+
sitemap: {
|
|
166
|
+
hostname: "https://myblog.com",
|
|
167
|
+
filter: (page) => !["/admin", "/draft-*", "/test"].includes(page.pattern),
|
|
168
|
+
defaults: {
|
|
169
|
+
changefreq: "weekly",
|
|
170
|
+
priority: 0.5,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
// RSS configuration
|
|
175
|
+
rss: {
|
|
176
|
+
title: "My Blog",
|
|
177
|
+
description: "Latest posts from my blog",
|
|
178
|
+
link: "https://myblog.com",
|
|
179
|
+
feedPath: "/feed.xml",
|
|
180
|
+
language: "en",
|
|
181
|
+
copyright: "© 2026 My Blog",
|
|
182
|
+
maxItems: 10,
|
|
183
|
+
filter: (page) => page.path.startsWith("/posts/"),
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Pages with a `published` date in metadata are included in RSS feeds.
|
|
189
|
+
|
|
190
|
+
### 📁 File-Based Routing
|
|
191
|
+
|
|
192
|
+
Your file structure defines your routes:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
src/pages/
|
|
196
|
+
├── index.js → /
|
|
197
|
+
├── about.js → /about
|
|
198
|
+
├── blog.js → /blog
|
|
199
|
+
└── posts/
|
|
200
|
+
└── _slug.js → /posts/:slug
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Dynamic routes use underscore prefix: `_id.js`, `_slug.js`, etc.
|
|
204
|
+
|
|
205
|
+
### ⚛️ Entity-Based State And Behavior
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// src/pages/about.js
|
|
209
|
+
import { html } from "@inglorious/web"
|
|
210
|
+
|
|
211
|
+
export const about = {
|
|
212
|
+
click(entity) {
|
|
213
|
+
entity.name += "!"
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
render(entity, api) {
|
|
217
|
+
return html`<h1>
|
|
218
|
+
About
|
|
219
|
+
<span @click=${() => api.notify(`#${entity.id}:click`)}
|
|
220
|
+
>${entity.name}</span
|
|
221
|
+
>
|
|
222
|
+
</h1>`
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// src/store/entities.js
|
|
229
|
+
export const entities = {
|
|
230
|
+
about: {
|
|
231
|
+
type: "about",
|
|
232
|
+
name: "Us",
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 🔄 Data Loading
|
|
238
|
+
|
|
239
|
+
Load data at build time with the `load` export:
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
// src/pages/blog.js
|
|
243
|
+
import { html } from "@inglorious/web"
|
|
244
|
+
|
|
245
|
+
export const blog = {
|
|
246
|
+
render(entity) {
|
|
247
|
+
return html`
|
|
248
|
+
<h1>Blog Posts</h1>
|
|
249
|
+
<ul>
|
|
250
|
+
${entity.posts?.map(
|
|
251
|
+
(post) => html`
|
|
252
|
+
<li>
|
|
253
|
+
<a href="/posts/${post.id}">${post.title}</a>
|
|
254
|
+
</li>
|
|
255
|
+
`,
|
|
256
|
+
)}
|
|
257
|
+
</ul>
|
|
258
|
+
`
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// SSR: Load data during build
|
|
263
|
+
export async function load(entity) {
|
|
264
|
+
const response = await fetch("https://api.example.com/posts")
|
|
265
|
+
entity.posts = await response.json()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export const title = "Blog"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The `load` function runs on the server during build. Data is serialized into the HTML and available immediately on the client.
|
|
272
|
+
|
|
273
|
+
### 🎨 Dynamic Routes with `staticPaths`
|
|
274
|
+
|
|
275
|
+
Generate multiple pages from data:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// src/pages/posts/_slug.js
|
|
279
|
+
import { html } from "@inglorious/web"
|
|
280
|
+
|
|
281
|
+
export const post = {
|
|
282
|
+
render(entity) {
|
|
283
|
+
return html`
|
|
284
|
+
<article>
|
|
285
|
+
<h1>${entity.post.title}</h1>
|
|
286
|
+
<div>${entity.post.body}</div>
|
|
287
|
+
</article>
|
|
288
|
+
`
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Load data for a specific post
|
|
293
|
+
export async function load(entity, page) {
|
|
294
|
+
const response = await fetch(
|
|
295
|
+
`https://api.example.com/posts/${page.params.slug}`,
|
|
296
|
+
)
|
|
297
|
+
entity.post = await response.json()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Tell SSX which pages to generate
|
|
301
|
+
export async function staticPaths() {
|
|
302
|
+
const response = await fetch(`https://api.example.com/posts`)
|
|
303
|
+
const posts = await response.json()
|
|
304
|
+
|
|
305
|
+
return posts.map((post) => ({
|
|
306
|
+
params: { slug: post.slug },
|
|
307
|
+
path: `/posts/${post.slug}`,
|
|
308
|
+
}))
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export const metadata = (entity) => ({
|
|
312
|
+
title: entity.post.title ?? "Post",
|
|
313
|
+
meta: {
|
|
314
|
+
description: entity.post.excerpt,
|
|
315
|
+
},
|
|
316
|
+
})
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 📄 Page Metadata
|
|
320
|
+
|
|
321
|
+
Export metadata for HTML `<head>`. The `metadata` export can be a plain object or a function:
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
export const index = {
|
|
325
|
+
render() {
|
|
326
|
+
return html`<h1>Home</h1>`
|
|
327
|
+
},
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Static metadata
|
|
331
|
+
export const metadata = {
|
|
332
|
+
title: "My Site",
|
|
333
|
+
meta: {
|
|
334
|
+
description: "An awesome static site",
|
|
335
|
+
"og:image": "/og-image.png",
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Or dynamic metadata (uses entity data)
|
|
340
|
+
export const metadata = (entity) => ({
|
|
341
|
+
title: `${entity.user.name}'s Profile`,
|
|
342
|
+
meta: {
|
|
343
|
+
description: entity.user.bio,
|
|
344
|
+
"og:image": entity.user.avatar,
|
|
345
|
+
},
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 🔥 Client-Side Hydration
|
|
350
|
+
|
|
351
|
+
Pages hydrate automatically with lit-html. Interactivity works immediately:
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
export const counter = {
|
|
355
|
+
click(entity) {
|
|
356
|
+
entity.count++
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
render(entity, api) {
|
|
360
|
+
return html`
|
|
361
|
+
<div>
|
|
362
|
+
<p>Count: ${entity.count}</p>
|
|
363
|
+
<button @click=${() => api.notify(`#${entity.id}:click`)}>
|
|
364
|
+
Increment
|
|
365
|
+
</button>
|
|
366
|
+
</div>
|
|
367
|
+
`
|
|
368
|
+
},
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
The HTML is pre-rendered on the server. When JavaScript loads, lit-html hydrates the existing DOM and wires up event handlers. No flash of unstyled content, no duplicate rendering.
|
|
373
|
+
|
|
374
|
+
### 🧭 Client-Side Navigation
|
|
375
|
+
|
|
376
|
+
After hydration, navigation is instant:
|
|
377
|
+
|
|
378
|
+
```javascript
|
|
379
|
+
// Links navigate without page reload
|
|
380
|
+
;<a href="/about">About</a> // Client-side routing
|
|
381
|
+
|
|
382
|
+
// Programmatic navigation
|
|
383
|
+
api.notify("navigate", "/posts")
|
|
384
|
+
|
|
385
|
+
// With options
|
|
386
|
+
api.notify("navigate", {
|
|
387
|
+
to: "/posts/123",
|
|
388
|
+
replace: true,
|
|
389
|
+
})
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Routes are lazy-loaded on demand, keeping initial bundle size small.
|
|
393
|
+
|
|
394
|
+
### 🖼️ Image Optimization
|
|
395
|
+
|
|
396
|
+
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.
|
|
399
|
+
- **Lossless & Lossy** - Configurable settings via `vite` config in `site.config.js`.
|
|
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
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## CLI
|
|
433
|
+
|
|
434
|
+
SSX provides a simple CLI for building and developing:
|
|
435
|
+
|
|
436
|
+
### `ssx build`
|
|
437
|
+
|
|
438
|
+
Builds your static site:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
pnpm ssx build [options]
|
|
442
|
+
|
|
443
|
+
Options:
|
|
444
|
+
-c, --config <file> Config file (default: "site.config.js")
|
|
445
|
+
-r, --root <dir> Source root directory (default: "src")
|
|
446
|
+
-o, --out <dir> Output directory (default: "dist")
|
|
447
|
+
-i, --incremental Enable incremental builds (default: true)
|
|
448
|
+
-f, --force Force clean build, ignore cache
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### `preview`
|
|
452
|
+
|
|
453
|
+
Serves the built static site on port 3000 through the `serve` NPM package.
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
pnpm preview
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### `ssx dev`
|
|
460
|
+
|
|
461
|
+
Starts the Vite development server on port 3000 with hot reload:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
pnpm ssx dev [options]
|
|
465
|
+
|
|
466
|
+
Options:
|
|
467
|
+
-c, --config <file> Config file (default: "site.config.js")
|
|
468
|
+
-r, --root <dir> Source root directory (default: "src")
|
|
469
|
+
-p, --port <port> Dev server port (default: 3000)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Project Structure
|
|
475
|
+
|
|
476
|
+
```
|
|
477
|
+
my-site/
|
|
478
|
+
├── src/
|
|
479
|
+
│ ├── pages/ # File-based routes
|
|
480
|
+
│ │ ├── index.js # Home page
|
|
481
|
+
│ │ ├── about.js # About page
|
|
482
|
+
│ │ └── posts/
|
|
483
|
+
│ │ ├── index.js # /posts
|
|
484
|
+
│ │ └── _id.js # /posts/:id
|
|
485
|
+
│ ├── store/ # Store configuration
|
|
486
|
+
│ │ └── entities.js # Entity definitions
|
|
487
|
+
│ └── types/ # Custom entity types (optional)
|
|
488
|
+
├── dist/ # Build output
|
|
489
|
+
├── package.json
|
|
490
|
+
└── site.config.js # Site configuration
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Comparison to Other Tools
|
|
496
|
+
|
|
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 | ❌ | ❌ | ✅ | ✅ |
|
|
506
|
+
|
|
507
|
+
SSX is perfect if you:
|
|
508
|
+
|
|
509
|
+
- Want static site performance
|
|
510
|
+
- Love entity-based architecture
|
|
511
|
+
- Prefer convention over configuration
|
|
512
|
+
- Need full client-side interactivity
|
|
513
|
+
- Don't want React/Vue lock-in
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Advanced Usage
|
|
518
|
+
|
|
519
|
+
### Site Configuration
|
|
520
|
+
|
|
521
|
+
Customize SSX behavior in `src/site.config.js`:
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
export default {
|
|
525
|
+
// Basic metadata
|
|
526
|
+
lang: "en",
|
|
527
|
+
charset: "UTF-8",
|
|
528
|
+
title: "My Awesome Site",
|
|
529
|
+
meta: {
|
|
530
|
+
description: "A site built with SSX",
|
|
531
|
+
"og:type": "website",
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
// Global assets
|
|
535
|
+
styles: ["./styles/reset.css", "./styles/theme.css"],
|
|
536
|
+
scripts: ["./scripts/analytics.js"],
|
|
537
|
+
|
|
538
|
+
// Build options
|
|
539
|
+
basePath: "/",
|
|
540
|
+
rootDir: "src",
|
|
541
|
+
outDir: "dist",
|
|
542
|
+
publicDir: "public",
|
|
543
|
+
favicon: "/favicon.ico",
|
|
544
|
+
|
|
545
|
+
// Router config
|
|
546
|
+
router: {
|
|
547
|
+
trailingSlash: false,
|
|
548
|
+
scrollBehavior: "smooth",
|
|
549
|
+
},
|
|
550
|
+
|
|
551
|
+
// Vite config passthrough
|
|
552
|
+
vite: {
|
|
553
|
+
server: {
|
|
554
|
+
port: 3000,
|
|
555
|
+
open: true,
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
// Build hooks
|
|
560
|
+
hooks: {
|
|
561
|
+
beforeBuild: async (config) => console.log("Starting build..."),
|
|
562
|
+
afterBuild: async (result) => console.log(`Built ${result.pages} pages`),
|
|
563
|
+
},
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### Environment Variables
|
|
568
|
+
|
|
569
|
+
Use Vite's environment variables:
|
|
570
|
+
|
|
571
|
+
```javascript
|
|
572
|
+
// Access in your code
|
|
573
|
+
const apiUrl = import.meta.env.VITE_API_URL
|
|
574
|
+
|
|
575
|
+
// .env file
|
|
576
|
+
VITE_API_URL=https://api.example.com
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Custom 404 Page
|
|
580
|
+
|
|
581
|
+
Create a fallback route:
|
|
582
|
+
|
|
583
|
+
```javascript
|
|
584
|
+
// src/pages/404.js
|
|
585
|
+
export const notFound = {
|
|
586
|
+
render() {
|
|
587
|
+
return html`
|
|
588
|
+
<div>
|
|
589
|
+
<h1>404 - Page Not Found</h1>
|
|
590
|
+
<a href="/">Go Home</a>
|
|
591
|
+
</div>
|
|
592
|
+
`
|
|
593
|
+
},
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export const metadata = {
|
|
597
|
+
title: "404",
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Register it in your router:
|
|
602
|
+
|
|
603
|
+
```javascript
|
|
604
|
+
// src/store/entities.js
|
|
605
|
+
import { setRoutes } from "@inglorious/web/router"
|
|
606
|
+
|
|
607
|
+
setRoutes({
|
|
608
|
+
// ... other routes
|
|
609
|
+
"*": "notFound", // Fallback
|
|
610
|
+
})
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Incremental Builds
|
|
614
|
+
|
|
615
|
+
SSX enables incremental builds by default. Only changed pages are rebuilt, dramatically speeding up your build process:
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
ssx build
|
|
619
|
+
# Only changed pages are rebuilt
|
|
620
|
+
|
|
621
|
+
ssx build --force
|
|
622
|
+
# Force a clean rebuild of all pages
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Incremental builds respect your page dependencies and invalidate cache when dependencies change.
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## API Reference
|
|
630
|
+
|
|
631
|
+
### Build API
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
import { build } from "@inglorious/ssx/build"
|
|
635
|
+
|
|
636
|
+
await build({
|
|
637
|
+
rootDir: "src",
|
|
638
|
+
outDir: "dist",
|
|
639
|
+
configFile: "site.config.js",
|
|
640
|
+
incremental: true,
|
|
641
|
+
clean: false,
|
|
642
|
+
})
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Dev Server API
|
|
646
|
+
|
|
647
|
+
```javascript
|
|
648
|
+
import { dev } from "@inglorious/ssx/dev"
|
|
649
|
+
|
|
650
|
+
await dev({
|
|
651
|
+
rootDir: "src",
|
|
652
|
+
port: 3000,
|
|
653
|
+
configFile: "site.config.js",
|
|
654
|
+
})
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
<!-- ## Examples
|
|
660
|
+
|
|
661
|
+
Check out these example projects:
|
|
662
|
+
|
|
663
|
+
- **[Basic Blog](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-blog)** - Simple blog with posts
|
|
664
|
+
- **[Documentation Site](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-docs)** - Multi-page docs
|
|
665
|
+
- **[E-commerce](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-shop)** - Product catalog
|
|
666
|
+
- **[Portfolio](https://github.com/IngloriousCoderz/inglorious-forge/tree/main/examples/ssx-portfolio)** - Personal portfolio
|
|
667
|
+
|
|
668
|
+
--- -->
|
|
669
|
+
|
|
670
|
+
## Roadmap
|
|
671
|
+
|
|
672
|
+
- [x] TypeScript support
|
|
673
|
+
- [x] Image optimization
|
|
674
|
+
- [ ] API routes (serverless functions)
|
|
675
|
+
- [x] Markdown support
|
|
676
|
+
- [ ] i18n helpers
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## Philosophy
|
|
681
|
+
|
|
682
|
+
SSX embraces the philosophy of [@inglorious/web](https://www.npmjs.com/package/@inglorious/web):
|
|
683
|
+
|
|
684
|
+
- **Simplicity over cleverness** - Obvious beats clever
|
|
685
|
+
- **Convention over configuration** - Sensible defaults
|
|
686
|
+
- **Predictability over magic** - Explicit is better than implicit
|
|
687
|
+
- **Standards over abstractions** - Use the platform
|
|
688
|
+
|
|
689
|
+
Static site generation should be simple. SSX makes it simple.
|
|
690
|
+
|
|
691
|
+
---
|
|
692
|
+
|
|
693
|
+
## Contributing
|
|
694
|
+
|
|
695
|
+
Contributions are welcome! Please read our [Contributing Guidelines](../../CONTRIBUTING.md) first.
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## License
|
|
700
|
+
|
|
701
|
+
**MIT License** - Free and open source
|
|
702
|
+
|
|
703
|
+
Created by [Matteo Antony Mistretta](https://github.com/IngloriousCoderz)
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## Related Packages
|
|
708
|
+
|
|
709
|
+
- [@inglorious/web](https://www.npmjs.com/package/@inglorious/web) - Entity-based web framework
|
|
710
|
+
- [@inglorious/store](https://www.npmjs.com/package/@inglorious/store) - State management
|
|
711
|
+
- [@inglorious/engine](https://www.npmjs.com/package/@inglorious/engine) - Game engine
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## Support
|
|
716
|
+
|
|
717
|
+
- 📖 [Documentation](https://inglorious-engine.vercel.app)
|
|
718
|
+
- 💬 [Discord Community](https://discord.gg/Byx85t2eFp)
|
|
719
|
+
- 🐛 [Issue Tracker](https://github.com/IngloriousCoderz/inglorious-forge/issues)
|
|
720
|
+
- 📧 [Email Support](mailto:antony.mistretta@gmail.com)
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
**Build static sites the Inglorious way. Simple. Predictable. Fast.** 🚀
|