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 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 templateDir = resolve(here, '..', 'template');
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[2];
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, strip what's excluded ────────
211
- const features = await pickFeatures();
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(templateDir, targetDir, { recursive: true });
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-') && name !== 'spark-prerender') continue;
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 picked = FEATURES.filter((f) => features[f.key]).map((f) => f.key).join(', ') || 'core only';
264
- stdout.write(`\n${c.green('')} Scaffolded ${c.bold(projectName)} in ${c.cyan(rel)} ${c.dim(`(head, persist, prerender, devtools + ${picked})`)}\n\n`);
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
- stdout.write(`${BOLT} Then open the dev server and edit ${c.cyan('public/components/hero.html')}.\n\n`);
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.8.3",
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": [
@@ -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,2 @@
1
+ node_modules
2
+ dist
@@ -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,2 @@
1
+ import { mount } from 'spark-html';
2
+ mount();
@@ -0,0 +1,3 @@
1
+ body { font-family: system-ui, sans-serif; max-width: 36rem; margin: 4rem auto; padding: 0 1.5rem; }
2
+ main { text-align: center; }
3
+ button { padding: 0.5rem 1rem; cursor: pointer; }
@@ -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,4 @@
1
+ node_modules
2
+ dist
3
+ dev.db
4
+ uploads
@@ -0,0 +1,4 @@
1
+ <!-- Components are pure UI — they just render what they receive. -->
2
+ <nav>
3
+ <strong>⚡ {title}</strong>
4
+ </nav>
@@ -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();
@@ -0,0 +1,3 @@
1
+ {
2
+ "db": "sqlite://./dev.db"
3
+ }