create-spark-html-app 0.8.3 → 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 +19 -1
- package/bin/index.js +40 -12
- package/package.json +5 -3
- package/template/README.md +2 -2
- package/template/public/components/footer.html +1 -1
- 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
|
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
|
|
@@ -185,7 +208,7 @@ async function main() {
|
|
|
185
208
|
stdout.write(`${c.dim(' HTML that reacts. Built for humans — no compiler, no virtual DOM.')}\n\n`);
|
|
186
209
|
|
|
187
210
|
// 1 ─ figure out the target directory ────────────────────────────────
|
|
188
|
-
let targetArg = argv
|
|
211
|
+
let targetArg = argv.slice(2).find((a) => !a.startsWith('-'));
|
|
189
212
|
if (!targetArg) {
|
|
190
213
|
if (!stdin.isTTY) bail('Please pass a project name: create-spark-html-app <name>');
|
|
191
214
|
targetArg = await prompt(
|
|
@@ -207,11 +230,12 @@ async function main() {
|
|
|
207
230
|
if (!/^y(es)?$/i.test(ok)) bail('Aborted — nothing was written.');
|
|
208
231
|
}
|
|
209
232
|
|
|
210
|
-
// 3 ─ pick features, copy the template
|
|
211
|
-
const
|
|
233
|
+
// 3 ─ pick the project type + features, copy the template ────────────
|
|
234
|
+
const type = await pickType();
|
|
235
|
+
const features = type === 'client' ? await pickFeatures() : {};
|
|
212
236
|
mkdirSync(targetDir, { recursive: true });
|
|
213
|
-
cpSync(
|
|
214
|
-
applyFeatures(targetDir, features);
|
|
237
|
+
cpSync(templateFor(type), targetDir, { recursive: true });
|
|
238
|
+
if (type === 'client') applyFeatures(targetDir, features);
|
|
215
239
|
|
|
216
240
|
// npm renames/strips dotfiles on publish, so the template ships them
|
|
217
241
|
// with safe underscore prefixes. Restore the real names here.
|
|
@@ -229,7 +253,7 @@ async function main() {
|
|
|
229
253
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
230
254
|
pkg.name = projectName;
|
|
231
255
|
// Drop dependencies that belong to excluded features.
|
|
232
|
-
for (const f of FEATURES) {
|
|
256
|
+
for (const f of type === 'client' ? FEATURES : []) {
|
|
233
257
|
if (features[f.key]) continue;
|
|
234
258
|
for (const dep of f.deps) {
|
|
235
259
|
if (pkg.dependencies) delete pkg.dependencies[dep];
|
|
@@ -248,7 +272,8 @@ async function main() {
|
|
|
248
272
|
const deps = pkg[group];
|
|
249
273
|
if (!deps) continue;
|
|
250
274
|
for (const name of Object.keys(deps)) {
|
|
251
|
-
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;
|
|
252
277
|
const range = await latestRange(name);
|
|
253
278
|
if (range) {
|
|
254
279
|
deps[name] = range;
|
|
@@ -260,13 +285,16 @@ async function main() {
|
|
|
260
285
|
|
|
261
286
|
// 5 ─ celebrate + print next steps ───────────────────────────────────
|
|
262
287
|
const rel = relative(process.cwd(), targetDir) || '.';
|
|
263
|
-
const
|
|
264
|
-
|
|
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`);
|
|
265
292
|
stdout.write(`${c.bold('Next steps:')}\n`);
|
|
266
293
|
if (rel !== '.') stdout.write(` ${c.dim('1.')} cd ${rel}\n`);
|
|
267
294
|
stdout.write(` ${c.dim(rel !== '.' ? '2.' : '1.')} bun install\n`);
|
|
268
|
-
stdout.write(` ${c.dim(rel !== '.' ? '3.' : '2.')} bun dev\n\n`);
|
|
269
|
-
|
|
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`);
|
|
270
298
|
}
|
|
271
299
|
|
|
272
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.
|
|
@@ -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>
|
|
@@ -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();
|