create-spark-html-app 0.8.2 → 0.9.0
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 +21 -2
- package/bin/index.js +43 -14
- package/package.json +5 -3
- package/template/README.md +2 -2
- package/template/index.html +6 -4
- package/template/package.json +2 -1
- package/template/public/components/footer.html +1 -1
- package/template/spark.config.js +20 -0
- package/template-prerender/README.md +13 -0
- package/template-prerender/_gitignore +2 -0
- package/template-prerender/index.html +13 -0
- package/template-prerender/package.json +18 -0
- package/template-prerender/public/components/hero.html +14 -0
- package/template-prerender/spark.config.js +9 -0
- package/template-prerender/src/main.js +2 -0
- package/template-prerender/src/style.css +3 -0
- package/template-ssr/README.md +29 -0
- package/template-ssr/_gitignore +4 -0
- package/template-ssr/components/nav.html +4 -0
- package/template-ssr/package.json +15 -0
- package/template-ssr/pages/index.css +6 -0
- package/template-ssr/pages/index.html +19 -0
- package/template-ssr/setup.js +15 -0
- package/template-ssr/spark.json +3 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-spark-html-app
|
|
2
2
|
|
|
3
|
-
Scaffold a [Spark](https://github.com/wilkinnovo/spark) app in seconds — a
|
|
3
|
+
Scaffold a [Spark](https://github.com/wilkinnovo/spark-html) app in seconds — a
|
|
4
4
|
Bun-powered project (dev / build / preview via `spark-html-bun`) wired to
|
|
5
5
|
`spark-html` with live, reactive **Spark** components.
|
|
6
6
|
|
|
@@ -24,6 +24,24 @@ Run it with no name to be prompted:
|
|
|
24
24
|
bunx create-spark-html-app@latest
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Project types — SSR & Prerender
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bunx create-spark-html-app@latest myapp # client-only (default)
|
|
31
|
+
bunx create-spark-html-app@latest myapp --ssr # SSR with spark-ssr
|
|
32
|
+
bunx create-spark-html-app@latest myapp --prerender # static site with spark-prerender
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Without a flag (on a TTY) an interactive picker asks which one you want.
|
|
36
|
+
|
|
37
|
+
`--ssr` scaffolds the three-tier pattern — **pages** declare data with
|
|
38
|
+
`<spark-ssr>`, **components** are pure UI via `<div import>`, **spark.json**
|
|
39
|
+
holds the DB connection — plus a seeded SQLite dev database. No build step:
|
|
40
|
+
`bun run dev` and it serves.
|
|
41
|
+
|
|
42
|
+
`--prerender` scaffolds a minimal static site whose build writes
|
|
43
|
+
fully-rendered HTML into `dist/` via `spark-prerender`.
|
|
44
|
+
|
|
27
45
|
## What you get
|
|
28
46
|
|
|
29
47
|
The scaffold comes with the **whole Spark ecosystem pre-wired** — you delete
|
|
@@ -54,7 +72,8 @@ proprietary file format. Edit a component, save, and the page updates.
|
|
|
54
72
|
## The Spark family
|
|
55
73
|
|
|
56
74
|
Small, single-purpose packages that share one philosophy: no compiler, no
|
|
57
|
-
virtual DOM, no build step required
|
|
75
|
+
virtual DOM, no build step required — built for humans who love hand-writing
|
|
76
|
+
their web apps. Add only what you use.
|
|
58
77
|
|
|
59
78
|
| Package | What it does |
|
|
60
79
|
|---|---|
|
package/bin/index.js
CHANGED
|
@@ -27,7 +27,8 @@ import { createInterface } from 'node:readline/promises';
|
|
|
27
27
|
import { stdin, stdout, argv, exit } from 'node:process';
|
|
28
28
|
|
|
29
29
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const
|
|
30
|
+
const templateFor = (type) =>
|
|
31
|
+
resolve(here, '..', type === 'client' ? 'template' : `template-${type}`);
|
|
31
32
|
|
|
32
33
|
// ── tiny ANSI palette (no chalk; one less thing to install) ───────────
|
|
33
34
|
const supportsColor = stdout.isTTY && process.env.NO_COLOR === undefined;
|
|
@@ -99,6 +100,28 @@ async function prompt(question, fallback) {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
// ── project type ────────────────────────────────────────────────────────
|
|
104
|
+
// Client-only (the existing default), SSR (spark-ssr), or a prerendered
|
|
105
|
+
// static site (spark-prerender). Flags: --ssr / --prerender; otherwise an
|
|
106
|
+
// interactive picker on a TTY.
|
|
107
|
+
const TYPES = [
|
|
108
|
+
{ key: 'client', label: 'Client-only (default)' },
|
|
109
|
+
{ key: 'ssr', label: 'SSR (spark-ssr)' },
|
|
110
|
+
{ key: 'prerender', label: 'Prerender (spark-prerender)' },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
async function pickType() {
|
|
114
|
+
const flags = argv.slice(2);
|
|
115
|
+
if (flags.includes('--ssr')) return 'ssr';
|
|
116
|
+
if (flags.includes('--prerender')) return 'prerender';
|
|
117
|
+
if (flags.includes('--client')) return 'client';
|
|
118
|
+
if (!stdin.isTTY) return 'client';
|
|
119
|
+
stdout.write(`${c.accent('?')} Project type:\n`);
|
|
120
|
+
TYPES.forEach((t, i) => stdout.write(` ${c.dim(String(i + 1) + ')')} ${t.label}\n`));
|
|
121
|
+
const a = (await prompt(`${c.accent('?')} Pick one ${c.dim('(1)')} `, '1')).trim();
|
|
122
|
+
return (TYPES[Number(a) - 1] || TYPES[0]).key;
|
|
123
|
+
}
|
|
124
|
+
|
|
102
125
|
// ── optional features ──────────────────────────────────────────────────
|
|
103
126
|
// The template ships with EVERYTHING wired; excluded features are stripped
|
|
104
127
|
// out of the copied files via `@spark:<name>` … `@spark:end` marker blocks
|
|
@@ -106,6 +129,7 @@ async function prompt(question, fallback) {
|
|
|
106
129
|
const FEATURES = [
|
|
107
130
|
{ key: 'router', question: 'Include router (multi-page SPA)?', def: true, deps: ['spark-html-router'], files: ['public/components/about.html'] },
|
|
108
131
|
{ key: 'theme', question: 'Include theme (dark/light toggle)?', def: true, deps: ['spark-html-theme'], files: [] },
|
|
132
|
+
{ key: 'font', question: 'Include font loading optimizer (no FOUT)?', def: true, deps: ['spark-html-font'], files: [] },
|
|
109
133
|
{ key: 'image', question: 'Include image optimization?', def: true, deps: ['spark-html-image'], files: ['public/components/demo-image.html', 'public/sample.jpg'] },
|
|
110
134
|
{ key: 'sri', question: 'Include SRI integrity checks?', def: true, deps: ['spark-html-sri'], files: [] },
|
|
111
135
|
{ key: 'pwa', question: 'Include PWA support (manifest + offline shell)?', def: false, deps: ['spark-html-manifest'], files: ['public/icon.png'] },
|
|
@@ -122,7 +146,7 @@ async function pickFeatures() {
|
|
|
122
146
|
if (flags.includes(`--no-${f.key}`)) on[f.key] = false;
|
|
123
147
|
}
|
|
124
148
|
// Interactive only on a TTY, and only when no preset flag was given.
|
|
125
|
-
const preset = flags.some((a) => /^--(yes|all|minimal|(no-)?(router|theme|image|sri|pwa))$/.test(a));
|
|
149
|
+
const preset = flags.some((a) => /^--(yes|all|minimal|(no-)?(router|theme|font|image|sri|pwa))$/.test(a));
|
|
126
150
|
if (stdin.isTTY && !preset) {
|
|
127
151
|
for (const f of FEATURES) {
|
|
128
152
|
const hint = f.def ? '(Y/n)' : '(y/N)';
|
|
@@ -181,10 +205,10 @@ function applyFeatures(targetDir, on) {
|
|
|
181
205
|
|
|
182
206
|
async function main() {
|
|
183
207
|
stdout.write(`\n${BOLT} ${c.bold('create-spark-html-app')}\n`);
|
|
184
|
-
stdout.write(`${c.dim(' HTML that reacts — no compiler, no virtual DOM.')}\n\n`);
|
|
208
|
+
stdout.write(`${c.dim(' HTML that reacts. Built for humans — no compiler, no virtual DOM.')}\n\n`);
|
|
185
209
|
|
|
186
210
|
// 1 ─ figure out the target directory ────────────────────────────────
|
|
187
|
-
let targetArg = argv
|
|
211
|
+
let targetArg = argv.slice(2).find((a) => !a.startsWith('-'));
|
|
188
212
|
if (!targetArg) {
|
|
189
213
|
if (!stdin.isTTY) bail('Please pass a project name: create-spark-html-app <name>');
|
|
190
214
|
targetArg = await prompt(
|
|
@@ -206,11 +230,12 @@ async function main() {
|
|
|
206
230
|
if (!/^y(es)?$/i.test(ok)) bail('Aborted — nothing was written.');
|
|
207
231
|
}
|
|
208
232
|
|
|
209
|
-
// 3 ─ pick features, copy the template
|
|
210
|
-
const
|
|
233
|
+
// 3 ─ pick the project type + features, copy the template ────────────
|
|
234
|
+
const type = await pickType();
|
|
235
|
+
const features = type === 'client' ? await pickFeatures() : {};
|
|
211
236
|
mkdirSync(targetDir, { recursive: true });
|
|
212
|
-
cpSync(
|
|
213
|
-
applyFeatures(targetDir, features);
|
|
237
|
+
cpSync(templateFor(type), targetDir, { recursive: true });
|
|
238
|
+
if (type === 'client') applyFeatures(targetDir, features);
|
|
214
239
|
|
|
215
240
|
// npm renames/strips dotfiles on publish, so the template ships them
|
|
216
241
|
// with safe underscore prefixes. Restore the real names here.
|
|
@@ -228,7 +253,7 @@ async function main() {
|
|
|
228
253
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
229
254
|
pkg.name = projectName;
|
|
230
255
|
// Drop dependencies that belong to excluded features.
|
|
231
|
-
for (const f of FEATURES) {
|
|
256
|
+
for (const f of type === 'client' ? FEATURES : []) {
|
|
232
257
|
if (features[f.key]) continue;
|
|
233
258
|
for (const dep of f.deps) {
|
|
234
259
|
if (pkg.dependencies) delete pkg.dependencies[dep];
|
|
@@ -247,7 +272,8 @@ async function main() {
|
|
|
247
272
|
const deps = pkg[group];
|
|
248
273
|
if (!deps) continue;
|
|
249
274
|
for (const name of Object.keys(deps)) {
|
|
250
|
-
if (name !== 'spark-html' && !name.startsWith('spark-html-')
|
|
275
|
+
if (name !== 'spark-html' && !name.startsWith('spark-html-')
|
|
276
|
+
&& name !== 'spark-prerender' && name !== 'spark-ssr') continue;
|
|
251
277
|
const range = await latestRange(name);
|
|
252
278
|
if (range) {
|
|
253
279
|
deps[name] = range;
|
|
@@ -259,13 +285,16 @@ async function main() {
|
|
|
259
285
|
|
|
260
286
|
// 5 ─ celebrate + print next steps ───────────────────────────────────
|
|
261
287
|
const rel = relative(process.cwd(), targetDir) || '.';
|
|
262
|
-
const
|
|
263
|
-
|
|
288
|
+
const flavor = type === 'ssr' ? 'SSR — spark-ssr, zero config, no build'
|
|
289
|
+
: type === 'prerender' ? 'prerendered static site — spark-prerender'
|
|
290
|
+
: `head, persist, prerender, devtools + ${FEATURES.filter((f) => features[f.key]).map((f) => f.key).join(', ') || 'core only'}`;
|
|
291
|
+
stdout.write(`\n${c.green('✔')} Scaffolded ${c.bold(projectName)} in ${c.cyan(rel)} ${c.dim(`(${flavor})`)}\n\n`);
|
|
264
292
|
stdout.write(`${c.bold('Next steps:')}\n`);
|
|
265
293
|
if (rel !== '.') stdout.write(` ${c.dim('1.')} cd ${rel}\n`);
|
|
266
294
|
stdout.write(` ${c.dim(rel !== '.' ? '2.' : '1.')} bun install\n`);
|
|
267
|
-
stdout.write(` ${c.dim(rel !== '.' ? '3.' : '2.')} bun dev\n\n`);
|
|
268
|
-
|
|
295
|
+
stdout.write(` ${c.dim(rel !== '.' ? '3.' : '2.')} bun run dev\n\n`);
|
|
296
|
+
const editHint = type === 'ssr' ? 'pages/index.html' : 'public/components/hero.html';
|
|
297
|
+
stdout.write(`${BOLT} Then open the dev server and edit ${c.cyan(editHint)}.\n\n`);
|
|
269
298
|
}
|
|
270
299
|
|
|
271
300
|
main().catch((err) => bail(err?.message || String(err)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-spark-html-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Scaffold a spark-html app — dev/build/preview on Bun",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
-
"template"
|
|
11
|
+
"template",
|
|
12
|
+
"template-ssr",
|
|
13
|
+
"template-prerender"
|
|
12
14
|
],
|
|
13
15
|
"engines": {
|
|
14
16
|
"node": ">=18"
|
|
15
17
|
},
|
|
16
18
|
"repository": {
|
|
17
19
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/wilkinnovo/spark.git",
|
|
20
|
+
"url": "git+https://github.com/wilkinnovo/spark-html.git",
|
|
19
21
|
"directory": "packages/create-spark-html-app"
|
|
20
22
|
},
|
|
21
23
|
"keywords": [
|
package/template/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ⚡ Spark App
|
|
2
2
|
|
|
3
|
-
A starter built with [spark-html](https://github.com/wilkinnovo/spark) — single-file
|
|
3
|
+
A starter built with [spark-html](https://github.com/wilkinnovo/spark-html) — single-file
|
|
4
4
|
HTML components with built-in reactivity. No compiler, no virtual DOM, no build step.
|
|
5
5
|
|
|
6
6
|
The scaffold is a **multi-page SPA** with client-side routing, live demos, and a
|
|
@@ -72,5 +72,5 @@ Derive values with `$:`, share state across components with `useStore(name)`, us
|
|
|
72
72
|
`bind:value` for two-way binds, and pass props as attributes on the `import`
|
|
73
73
|
placeholder.
|
|
74
74
|
|
|
75
|
-
See the [full docs](https://wilkinnovo.github.io/spark/docs) for the complete
|
|
75
|
+
See the [full docs](https://wilkinnovo.github.io/spark-html/docs) for the complete
|
|
76
76
|
template syntax reference.
|
package/template/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<title>Spark App</title>
|
|
7
7
|
<meta name="description" content="A reactive app built with Spark — single-file HTML components, no compiler, no virtual DOM." />
|
|
8
8
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<!-- Fonts are injected by spark-html-font/bun (see spark.config.js):
|
|
10
|
+
preconnects, the stylesheet, and a size-adjusted fallback face so the
|
|
11
|
+
swap never moves the page. -->
|
|
12
12
|
<style>
|
|
13
13
|
/* Design system — the same palette and monospace type as the Spark
|
|
14
14
|
website. Tokens + base + a shared card/button/input system live here in
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
--spark-ink: #ffd24a;
|
|
35
35
|
--danger: #ff6b6b;
|
|
36
36
|
--radius: 12px;
|
|
37
|
-
--font
|
|
37
|
+
/* the injected --font-jetbrains-mono stack includes the fallback face;
|
|
38
|
+
the var() fallback list covers a config without the font step */
|
|
39
|
+
--font: var(--font-jetbrains-mono, "JetBrains Mono", ui-monospace, monospace);
|
|
38
40
|
}
|
|
39
41
|
[data-theme="light"] {
|
|
40
42
|
--bg: #fff;
|
package/template/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<footer class="foot">
|
|
2
2
|
Edit any file in <code>public/components/</code> and save — the page updates
|
|
3
3
|
instantly. Built with
|
|
4
|
-
<a href="https://github.com/wilkinnovo/spark" target="_blank" rel="noopener">Spark</a>.
|
|
4
|
+
<a href="https://github.com/wilkinnovo/spark-html" target="_blank" rel="noopener">Spark</a>.
|
|
5
5
|
</footer>
|
|
6
6
|
|
|
7
7
|
<style>
|
package/template/spark.config.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import prerender from 'spark-prerender/bun';
|
|
2
|
+
// @spark:theme
|
|
3
|
+
import theme from 'spark-html-theme/bun';
|
|
4
|
+
// @spark:end
|
|
5
|
+
// @spark:font
|
|
6
|
+
import font from 'spark-html-font/bun';
|
|
7
|
+
// @spark:end
|
|
2
8
|
// @spark:image
|
|
3
9
|
import image from 'spark-html-image/bun';
|
|
4
10
|
// @spark:end
|
|
@@ -26,6 +32,20 @@ import sri from 'spark-html-sri/bun';
|
|
|
26
32
|
export default {
|
|
27
33
|
pipeline: [
|
|
28
34
|
prerender({ pages: ['index.html'] }),
|
|
35
|
+
// @spark:theme
|
|
36
|
+
// Bakes the tiny no-flash script into each page (dev too) so the saved /
|
|
37
|
+
// OS theme is on <html> before first paint — no wrong-theme flash on load.
|
|
38
|
+
theme(),
|
|
39
|
+
// @spark:end
|
|
40
|
+
// @spark:font
|
|
41
|
+
// Preconnect + the Google stylesheet + a size-adjusted local fallback
|
|
42
|
+
// face, baked into each page (dev too) — the font swap never moves or
|
|
43
|
+
// visibly reflows the page.
|
|
44
|
+
font({
|
|
45
|
+
fallback: ['ui-monospace', 'monospace'],
|
|
46
|
+
fonts: [{ family: 'JetBrains Mono', google: true, weights: [300, 400, 500, 600, 700, 800] }],
|
|
47
|
+
}),
|
|
48
|
+
// @spark:end
|
|
29
49
|
// @spark:image
|
|
30
50
|
// Every <img> in pages and components: converted to webp/avif at multiple
|
|
31
51
|
// widths, srcset + width/height added (no layout shift), loading="lazy".
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ⚡ Spark Static App
|
|
2
|
+
|
|
3
|
+
A prerendered static site built with
|
|
4
|
+
[spark-html](https://github.com/wilkinnovo/spark-html) + `spark-prerender`.
|
|
5
|
+
Components stay single-file HTML; the build writes fully-rendered pages into
|
|
6
|
+
`dist/` (plus sitemap/robots hooks) and the browser hydrates over them.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
bun install
|
|
10
|
+
bun run dev # dev server with HMR
|
|
11
|
+
bun run build # prerendered static output → dist/, deploy anywhere
|
|
12
|
+
bun run preview # preview the production build
|
|
13
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Spark Static App</title>
|
|
7
|
+
<link rel="stylesheet" href="/src/style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div import="components/hero"></div>
|
|
11
|
+
<script type="module" src="/src/main.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spark-static-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "spark dev",
|
|
8
|
+
"build": "spark build",
|
|
9
|
+
"preview": "spark preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"spark-html": "latest"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"spark-html-bun": "latest",
|
|
16
|
+
"spark-prerender": "latest"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<main>
|
|
2
|
+
<h1>⚡ {greeting}</h1>
|
|
3
|
+
<p>This page is prerendered at build time — view-source on the deployed
|
|
4
|
+
site and the content is right there. Edit this component and save.</p>
|
|
5
|
+
<button onclick={cheer}>Clicked {count} times</button>
|
|
6
|
+
</main>
|
|
7
|
+
|
|
8
|
+
<script>
|
|
9
|
+
let greeting = 'HTML that reacts';
|
|
10
|
+
let count = 0;
|
|
11
|
+
let pageTitle = 'Spark Static App';
|
|
12
|
+
let pageDescription = 'A prerendered spark-html starter — SEO-ready static HTML that hydrates into a reactive app.';
|
|
13
|
+
function cheer() { count++; }
|
|
14
|
+
</script>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import prerender from 'spark-prerender/bun';
|
|
2
|
+
|
|
3
|
+
// Static site, the Spark way: `spark dev` serves components raw with HMR;
|
|
4
|
+
// `spark build` copies public/, bundles the entry, then prerender() runs the
|
|
5
|
+
// REAL app at build time and writes fully-rendered HTML into dist/ — crawlers
|
|
6
|
+
// and AI tools read real content, the browser hydrates over it.
|
|
7
|
+
export default {
|
|
8
|
+
pipeline: [prerender({ pages: ['index.html'] })],
|
|
9
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# ⚡ Spark SSR App
|
|
2
|
+
|
|
3
|
+
SSR the Spark way — zero config, no build. The HTML template infers
|
|
4
|
+
everything: `<template each="todo in todos">` means you need `todos`,
|
|
5
|
+
`<spark-ssr table="todos" />` backs it with a table and the REST endpoints
|
|
6
|
+
the handlers imply.
|
|
7
|
+
|
|
8
|
+
## Develop
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun install
|
|
12
|
+
bun run dev # creates + seeds dev.db, then serves on :3000
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## The three-tier pattern
|
|
16
|
+
|
|
17
|
+
- **Page** — `pages/index.html` declares its data with `<spark-ssr>`
|
|
18
|
+
- **Component** — `components/nav.html` is pure UI via `<div import>`
|
|
19
|
+
- **Config** — `spark.json` holds the DB connection (swap sqlite → postgres
|
|
20
|
+
by changing one line)
|
|
21
|
+
|
|
22
|
+
Add a page by adding a file: `pages/about.html` → `/about`,
|
|
23
|
+
`pages/blog/[slug].html` → `/blog/:slug` (`:slug` binds into your queries).
|
|
24
|
+
|
|
25
|
+
## Deploy
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bun run build # dist/ with a compiled single binary
|
|
29
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spark-ssr-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun setup.js && bun spark-ssr",
|
|
8
|
+
"start": "bun spark-ssr",
|
|
9
|
+
"build": "bun spark-ssr build"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"spark-html": "latest",
|
|
13
|
+
"spark-ssr": "latest"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
body { font-family: system-ui, sans-serif; max-width: 32rem; margin: 3rem auto; padding: 0 1.5rem; }
|
|
2
|
+
nav { opacity: 0.7; margin-bottom: 2rem; }
|
|
3
|
+
ul { padding: 0; }
|
|
4
|
+
li { list-style: none; display: flex; gap: 0.5rem; align-items: center; padding: 0.35rem 0; }
|
|
5
|
+
li button { margin-left: auto; }
|
|
6
|
+
input[type="text"], input:not([type]) { padding: 0.4rem 0.6rem; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<div import="/components/nav" title="Tasks"></div>
|
|
2
|
+
|
|
3
|
+
<h1>Tasks</h1>
|
|
4
|
+
|
|
5
|
+
<template await="todos">
|
|
6
|
+
<input bind:value="draft" placeholder="New task">
|
|
7
|
+
<button onclick={add}>Add</button>
|
|
8
|
+
<ul>
|
|
9
|
+
<template each="todo in todos">
|
|
10
|
+
<li>
|
|
11
|
+
<input type="checkbox" bind:checked="todo.done" onchange={patch}>
|
|
12
|
+
{todo.title}
|
|
13
|
+
<button onclick={remove}>✕</button>
|
|
14
|
+
</li>
|
|
15
|
+
</template>
|
|
16
|
+
</ul>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<spark-ssr table="todos" />
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// One-time (idempotent) dev database setup — `bun run dev` runs it for you.
|
|
2
|
+
// Swap spark.json's db to postgres:// any time; no code changes needed.
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
|
|
5
|
+
const db = new Database('./dev.db', { create: true });
|
|
6
|
+
db.run(`CREATE TABLE IF NOT EXISTS todos (
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
+
title TEXT NOT NULL,
|
|
9
|
+
done INTEGER DEFAULT 0
|
|
10
|
+
)`);
|
|
11
|
+
if (db.query('SELECT COUNT(*) AS n FROM todos').get().n === 0) {
|
|
12
|
+
db.run("INSERT INTO todos (title) VALUES ('Try spark-ssr'), ('Edit pages/index.html'), ('Ship it')");
|
|
13
|
+
console.log('⚡ seeded dev.db with a few todos');
|
|
14
|
+
}
|
|
15
|
+
db.close();
|