@b9g/shovel 0.2.13 → 0.2.15
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/bin/create.js +282 -154
- package/package.json +2 -2
package/bin/create.js
CHANGED
|
@@ -265,31 +265,48 @@ async function createProject(config, projectPath) {
|
|
|
265
265
|
await mkdir(join(projectPath, "src"), { recursive: true });
|
|
266
266
|
const ext = config.uiFramework === "crank" && config.useJSX ? config.typescript ? "tsx" : "jsx" : config.typescript ? "ts" : "js";
|
|
267
267
|
const isCrank = config.uiFramework === "crank";
|
|
268
|
-
const
|
|
268
|
+
const hasClientBundle = config.template === "static-site" || config.template === "full-stack";
|
|
269
|
+
const entryFile = hasClientBundle ? `src/server.${ext}` : `src/app.${ext}`;
|
|
269
270
|
const startCmd = config.platform === "bun" ? "bun dist/server/supervisor.js" : "node dist/server/supervisor.js";
|
|
270
271
|
const dependencies = {
|
|
271
272
|
"@b9g/router": "^0.2.0",
|
|
272
273
|
"@b9g/shovel": "^0.2.0"
|
|
273
274
|
};
|
|
275
|
+
if (hasClientBundle) {
|
|
276
|
+
dependencies["@b9g/assets"] = "^0.2.0";
|
|
277
|
+
}
|
|
274
278
|
if (isCrank) {
|
|
275
279
|
dependencies["@b9g/crank"] = "^0.7.2";
|
|
276
|
-
|
|
280
|
+
}
|
|
281
|
+
if (config.uiFramework === "htmx") {
|
|
282
|
+
dependencies["htmx.org"] = "^2.0.0";
|
|
283
|
+
}
|
|
284
|
+
if (config.uiFramework === "alpine") {
|
|
285
|
+
dependencies["alpinejs"] = "^3.14.0";
|
|
277
286
|
}
|
|
278
287
|
const devDependencies = {};
|
|
279
288
|
if (config.typescript) {
|
|
280
289
|
devDependencies["@types/node"] = "^18.0.0";
|
|
281
290
|
devDependencies["typescript"] = "^5.0.0";
|
|
282
291
|
}
|
|
283
|
-
if (
|
|
292
|
+
if (hasClientBundle) {
|
|
284
293
|
devDependencies["eslint"] = "^10.0.0";
|
|
285
294
|
devDependencies["@eslint/js"] = "^10.0.0";
|
|
295
|
+
if (config.typescript) {
|
|
296
|
+
devDependencies["typescript-eslint"] = "^8.0.0";
|
|
297
|
+
} else {
|
|
298
|
+
devDependencies["globals"] = "^16.0.0";
|
|
299
|
+
}
|
|
300
|
+
if (isCrank) {
|
|
301
|
+
devDependencies["eslint-plugin-crank"] = "^0.2.0";
|
|
302
|
+
}
|
|
286
303
|
}
|
|
287
304
|
const scripts = {
|
|
288
305
|
develop: `shovel develop ${entryFile} --platform ${config.platform}`,
|
|
289
306
|
build: `shovel build ${entryFile} --platform ${config.platform}`,
|
|
290
307
|
start: startCmd
|
|
291
308
|
};
|
|
292
|
-
if (
|
|
309
|
+
if (hasClientBundle) {
|
|
293
310
|
scripts.lint = "eslint src/";
|
|
294
311
|
}
|
|
295
312
|
const packageJSON = {
|
|
@@ -322,34 +339,61 @@ async function createProject(config, projectPath) {
|
|
|
322
339
|
esModuleInterop: true,
|
|
323
340
|
strict: true,
|
|
324
341
|
skipLibCheck: true,
|
|
342
|
+
noEmit: true,
|
|
343
|
+
allowImportingTsExtensions: true,
|
|
325
344
|
forceConsistentCasingInFileNames: true,
|
|
326
|
-
lib: ["ES2022", "WebWorker"]
|
|
345
|
+
lib: ["ES2022", "DOM", "DOM.Iterable", "WebWorker"]
|
|
327
346
|
};
|
|
328
347
|
if (config.uiFramework === "crank" && config.useJSX) {
|
|
329
348
|
compilerOptions.jsx = "react-jsx";
|
|
330
349
|
compilerOptions.jsxImportSource = "@b9g/crank";
|
|
331
350
|
}
|
|
332
351
|
const tsConfig = {
|
|
333
|
-
compilerOptions
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
352
|
+
compilerOptions,
|
|
353
|
+
include: [
|
|
354
|
+
"src/**/*",
|
|
355
|
+
"node_modules/@b9g/platform/src/globals.d.ts",
|
|
356
|
+
"dist/server/shovel.d.ts"
|
|
357
|
+
],
|
|
358
|
+
exclude: []
|
|
339
359
|
};
|
|
340
360
|
await writeFile(
|
|
341
361
|
join(projectPath, "tsconfig.json"),
|
|
342
362
|
JSON.stringify(tsConfig, null, 2)
|
|
343
363
|
);
|
|
344
364
|
}
|
|
345
|
-
if (
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
365
|
+
if (hasClientBundle) {
|
|
366
|
+
const crankImport = isCrank ? `import crank from "eslint-plugin-crank";
|
|
367
|
+
` : "";
|
|
368
|
+
const crankConfig = isCrank ? `
|
|
369
|
+
{ plugins: { crank }, rules: crank.configs.recommended.rules },` : "";
|
|
370
|
+
let eslintConfig;
|
|
371
|
+
if (config.typescript) {
|
|
372
|
+
eslintConfig = `import js from "@eslint/js";
|
|
373
|
+
import tseslint from "typescript-eslint";
|
|
374
|
+
${crankImport}
|
|
375
|
+
export default tseslint.config(
|
|
349
376
|
js.configs.recommended,
|
|
350
|
-
|
|
377
|
+
tseslint.configs.recommended,
|
|
378
|
+
{ ignores: ["dist/"] },${crankConfig}
|
|
379
|
+
);
|
|
380
|
+
`;
|
|
381
|
+
} else {
|
|
382
|
+
let langOpts = "globals: globals.browser";
|
|
383
|
+
let filesOpt = "";
|
|
384
|
+
if (isCrank && config.useJSX) {
|
|
385
|
+
filesOpt = `files: ["**/*.{js,jsx}"], `;
|
|
386
|
+
langOpts += ", parserOptions: { ecmaFeatures: { jsx: true } }";
|
|
387
|
+
}
|
|
388
|
+
eslintConfig = `import js from "@eslint/js";
|
|
389
|
+
import globals from "globals";
|
|
390
|
+
${crankImport}
|
|
391
|
+
export default [
|
|
392
|
+
{ ${filesOpt}...js.configs.recommended, languageOptions: { ${langOpts} } },
|
|
393
|
+
{ ignores: ["dist/"] },${crankConfig}
|
|
351
394
|
];
|
|
352
395
|
`;
|
|
396
|
+
}
|
|
353
397
|
await writeFile(join(projectPath, "eslint.config.js"), eslintConfig);
|
|
354
398
|
}
|
|
355
399
|
const readme = generateReadme(config);
|
|
@@ -477,44 +521,35 @@ function generateStaticSite(config) {
|
|
|
477
521
|
function generateStaticSiteVanilla(config) {
|
|
478
522
|
const ext = config.typescript ? "ts" : "js";
|
|
479
523
|
const t = config.typescript;
|
|
480
|
-
return
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Response>" : ""} {
|
|
488
|
-
const url = new URL(request.url);
|
|
524
|
+
return {
|
|
525
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
526
|
+
import {assets} from "@b9g/assets/middleware";
|
|
527
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
528
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
489
529
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
<h1>Welcome to ${config.name}</h1>
|
|
493
|
-
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
494
|
-
<button id="counter">Clicked: 0</button>
|
|
495
|
-
<script>
|
|
496
|
-
let count = 0;
|
|
497
|
-
const btn = document.getElementById("counter");
|
|
498
|
-
btn.addEventListener("click", () => btn.textContent = "Clicked: " + ++count);
|
|
499
|
-
</script>
|
|
500
|
-
<p><a href="/about">About</a></p>
|
|
501
|
-
\`), {
|
|
502
|
-
headers: { "Content-Type": "text/html" },
|
|
503
|
-
});
|
|
504
|
-
}
|
|
530
|
+
const router = new Router();
|
|
531
|
+
router.use(assets());
|
|
505
532
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
}
|
|
533
|
+
router.route("/").get(() => {
|
|
534
|
+
return new Response(renderPage("Home", \`
|
|
535
|
+
<h1>Welcome to ${config.name}</h1>
|
|
536
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
537
|
+
<button id="counter">Clicked: 0</button>
|
|
538
|
+
<p><a href="/about">About</a></p>
|
|
539
|
+
\`), {
|
|
540
|
+
headers: { "Content-Type": "text/html" },
|
|
541
|
+
});
|
|
542
|
+
});
|
|
515
543
|
|
|
516
|
-
|
|
517
|
-
|
|
544
|
+
router.route("/about").get(() => {
|
|
545
|
+
return new Response(renderPage("About", \`
|
|
546
|
+
<h1>About</h1>
|
|
547
|
+
<p>This is a static site built with <strong>Shovel</strong>.</p>
|
|
548
|
+
<p><a href="/">Home</a></p>
|
|
549
|
+
\`), {
|
|
550
|
+
headers: { "Content-Type": "text/html" },
|
|
551
|
+
});
|
|
552
|
+
});
|
|
518
553
|
|
|
519
554
|
function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})${t ? ": string" : ""} {
|
|
520
555
|
return \`<!DOCTYPE html>
|
|
@@ -529,56 +564,64 @@ ${css}
|
|
|
529
564
|
</head>
|
|
530
565
|
<body>
|
|
531
566
|
<main>\${content}</main>
|
|
567
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
532
568
|
</body>
|
|
533
569
|
</html>\`;
|
|
534
570
|
}
|
|
535
|
-
|
|
571
|
+
|
|
572
|
+
self.addEventListener("fetch", (event) => {
|
|
573
|
+
event.respondWith(router.handle(event.request));
|
|
574
|
+
});
|
|
575
|
+
`,
|
|
576
|
+
[`client.${ext}`]: `const btn = document.getElementById("counter");
|
|
577
|
+
if (btn) {
|
|
578
|
+
let count = 0;
|
|
579
|
+
btn.addEventListener("click", () => btn.textContent = "Clicked: " + ++count);
|
|
580
|
+
}
|
|
581
|
+
`
|
|
582
|
+
};
|
|
536
583
|
}
|
|
537
584
|
function generateStaticSiteHtmx(config) {
|
|
538
585
|
const ext = config.typescript ? "ts" : "js";
|
|
539
586
|
const t = config.typescript;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Response>" : ""} {
|
|
548
|
-
const url = new URL(request.url);
|
|
587
|
+
const files = {
|
|
588
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
589
|
+
import {assets} from "@b9g/assets/middleware";
|
|
590
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
591
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
549
592
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
<h1>Welcome to ${config.name}</h1>
|
|
553
|
-
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
554
|
-
<button hx-get="/greeting" hx-target="#result" hx-swap="innerHTML">Get Greeting</button>
|
|
555
|
-
<div id="result"></div>
|
|
556
|
-
<p><a href="/about">About</a></p>
|
|
557
|
-
\`), {
|
|
558
|
-
headers: { "Content-Type": "text/html" },
|
|
559
|
-
});
|
|
560
|
-
}
|
|
593
|
+
const router = new Router();
|
|
594
|
+
router.use(assets());
|
|
561
595
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
596
|
+
router.route("/").get(() => {
|
|
597
|
+
return new Response(renderPage("Home", \`
|
|
598
|
+
<h1>Welcome to ${config.name}</h1>
|
|
599
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
600
|
+
<button hx-get="/greeting" hx-target="#result" hx-swap="innerHTML">Get Greeting</button>
|
|
601
|
+
<div id="result"></div>
|
|
602
|
+
<p><a href="/about">About</a></p>
|
|
603
|
+
\`), {
|
|
604
|
+
headers: { "Content-Type": "text/html" },
|
|
605
|
+
});
|
|
606
|
+
});
|
|
569
607
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
});
|
|
578
|
-
}
|
|
608
|
+
router.route("/greeting").get(() => {
|
|
609
|
+
const hour = new Date().getHours();
|
|
610
|
+
const greeting = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
|
|
611
|
+
return new Response(\`<p>\${greeting}! The time is \${new Date().toLocaleTimeString()}.</p>\`, {
|
|
612
|
+
headers: { "Content-Type": "text/html" },
|
|
613
|
+
});
|
|
614
|
+
});
|
|
579
615
|
|
|
580
|
-
|
|
581
|
-
|
|
616
|
+
router.route("/about").get(() => {
|
|
617
|
+
return new Response(renderPage("About", \`
|
|
618
|
+
<h1>About</h1>
|
|
619
|
+
<p>This is a static site built with <strong>Shovel</strong> and <strong>HTMX</strong>.</p>
|
|
620
|
+
<p><a href="/">Home</a></p>
|
|
621
|
+
\`), {
|
|
622
|
+
headers: { "Content-Type": "text/html" },
|
|
623
|
+
});
|
|
624
|
+
});
|
|
582
625
|
|
|
583
626
|
function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})${t ? ": string" : ""} {
|
|
584
627
|
return \`<!DOCTYPE html>
|
|
@@ -587,56 +630,64 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
587
630
|
<meta charset="UTF-8">
|
|
588
631
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
589
632
|
<title>\${title} - ${config.name}</title>
|
|
590
|
-
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
591
633
|
<style>
|
|
592
634
|
${css}
|
|
593
635
|
</style>
|
|
594
636
|
</head>
|
|
595
637
|
<body>
|
|
596
638
|
<main>\${content}</main>
|
|
639
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
597
640
|
</body>
|
|
598
641
|
</html>\`;
|
|
599
642
|
}
|
|
643
|
+
|
|
644
|
+
self.addEventListener("fetch", (event) => {
|
|
645
|
+
event.respondWith(router.handle(event.request));
|
|
646
|
+
});
|
|
647
|
+
`,
|
|
648
|
+
[`client.${ext}`]: `import "htmx.org";
|
|
649
|
+
`
|
|
650
|
+
};
|
|
651
|
+
if (t) {
|
|
652
|
+
files[`env.d.ts`] = `declare module "htmx.org";
|
|
600
653
|
`;
|
|
654
|
+
}
|
|
655
|
+
return files;
|
|
601
656
|
}
|
|
602
657
|
function generateStaticSiteAlpine(config) {
|
|
603
658
|
const ext = config.typescript ? "ts" : "js";
|
|
604
659
|
const t = config.typescript;
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Response>" : ""} {
|
|
613
|
-
const url = new URL(request.url);
|
|
660
|
+
const files = {
|
|
661
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
662
|
+
import {assets} from "@b9g/assets/middleware";
|
|
663
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
664
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
614
665
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
<h1>Welcome to ${config.name}</h1>
|
|
618
|
-
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
619
|
-
<div x-data="{ count: 0 }">
|
|
620
|
-
<button @click="count++">Clicked: <span x-text="count"></span></button>
|
|
621
|
-
</div>
|
|
622
|
-
<p><a href="/about">About</a></p>
|
|
623
|
-
\`), {
|
|
624
|
-
headers: { "Content-Type": "text/html" },
|
|
625
|
-
});
|
|
626
|
-
}
|
|
666
|
+
const router = new Router();
|
|
667
|
+
router.use(assets());
|
|
627
668
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
669
|
+
router.route("/").get(() => {
|
|
670
|
+
return new Response(renderPage("Home", \`
|
|
671
|
+
<h1>Welcome to ${config.name}</h1>
|
|
672
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
673
|
+
<div x-data="{ count: 0 }">
|
|
674
|
+
<button @click="count++">Clicked: <span x-text="count"></span></button>
|
|
675
|
+
</div>
|
|
676
|
+
<p><a href="/about">About</a></p>
|
|
677
|
+
\`), {
|
|
678
|
+
headers: { "Content-Type": "text/html" },
|
|
679
|
+
});
|
|
680
|
+
});
|
|
637
681
|
|
|
638
|
-
|
|
639
|
-
|
|
682
|
+
router.route("/about").get(() => {
|
|
683
|
+
return new Response(renderPage("About", \`
|
|
684
|
+
<h1>About</h1>
|
|
685
|
+
<p>This is a static site built with <strong>Shovel</strong> and <strong>Alpine.js</strong>.</p>
|
|
686
|
+
<p><a href="/">Home</a></p>
|
|
687
|
+
\`), {
|
|
688
|
+
headers: { "Content-Type": "text/html" },
|
|
689
|
+
});
|
|
690
|
+
});
|
|
640
691
|
|
|
641
692
|
function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})${t ? ": string" : ""} {
|
|
642
693
|
return \`<!DOCTYPE html>
|
|
@@ -645,17 +696,38 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
645
696
|
<meta charset="UTF-8">
|
|
646
697
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
647
698
|
<title>\${title} - ${config.name}</title>
|
|
648
|
-
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
|
|
649
699
|
<style>
|
|
650
700
|
${css}
|
|
651
701
|
</style>
|
|
652
702
|
</head>
|
|
653
703
|
<body>
|
|
654
704
|
<main>\${content}</main>
|
|
705
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
655
706
|
</body>
|
|
656
707
|
</html>\`;
|
|
657
708
|
}
|
|
709
|
+
|
|
710
|
+
self.addEventListener("fetch", (event) => {
|
|
711
|
+
event.respondWith(router.handle(event.request));
|
|
712
|
+
});
|
|
713
|
+
`,
|
|
714
|
+
[`client.${ext}`]: `import Alpine from "alpinejs";
|
|
715
|
+
Alpine.start();
|
|
716
|
+
`
|
|
717
|
+
};
|
|
718
|
+
if (t) {
|
|
719
|
+
files[`env.d.ts`] = `declare module "alpinejs" {
|
|
720
|
+
interface Alpine {
|
|
721
|
+
start(): void;
|
|
722
|
+
plugin(plugin: unknown): void;
|
|
723
|
+
[key: string]: unknown;
|
|
724
|
+
}
|
|
725
|
+
const alpine: Alpine;
|
|
726
|
+
export default alpine;
|
|
727
|
+
}
|
|
658
728
|
`;
|
|
729
|
+
}
|
|
730
|
+
return files;
|
|
659
731
|
}
|
|
660
732
|
function generateStaticSiteCrank(config) {
|
|
661
733
|
const t = config.typescript;
|
|
@@ -666,7 +738,8 @@ function generateStaticSiteCrank(config) {
|
|
|
666
738
|
import {Router} from "@b9g/router";
|
|
667
739
|
import {assets} from "@b9g/assets/middleware";
|
|
668
740
|
import {Page, Counter} from "./components";
|
|
669
|
-
|
|
741
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
742
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
670
743
|
|
|
671
744
|
const router = new Router();
|
|
672
745
|
router.use(assets());
|
|
@@ -726,8 +799,7 @@ export function Page({title, children, clientUrl}${t ? ": {title: string, childr
|
|
|
726
799
|
export function *Counter(${t ? "this: Context" : ""}) {
|
|
727
800
|
let count = 0;
|
|
728
801
|
const handleClick = () => {
|
|
729
|
-
count
|
|
730
|
-
this.refresh();
|
|
802
|
+
this.refresh(() => count++);
|
|
731
803
|
};
|
|
732
804
|
for ({} of this) {
|
|
733
805
|
yield <button onclick={handleClick}>Clicked: {count}</button>;
|
|
@@ -747,7 +819,8 @@ import {renderer} from "@b9g/crank/html";
|
|
|
747
819
|
import {Router} from "@b9g/router";
|
|
748
820
|
import {assets} from "@b9g/assets/middleware";
|
|
749
821
|
import {Page, Counter} from "./components";
|
|
750
|
-
|
|
822
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
823
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
751
824
|
|
|
752
825
|
const router = new Router();
|
|
753
826
|
router.use(assets());
|
|
@@ -808,8 +881,7 @@ export function Page({title, children, clientUrl}${t ? ": {title: string, childr
|
|
|
808
881
|
export function *Counter(${t ? "this: Context" : ""}) {
|
|
809
882
|
let count = 0;
|
|
810
883
|
const handleClick = () => {
|
|
811
|
-
count
|
|
812
|
-
this.refresh();
|
|
884
|
+
this.refresh(() => count++);
|
|
813
885
|
};
|
|
814
886
|
for ({} of this) {
|
|
815
887
|
yield jsx\`<button onclick=\${handleClick}>Clicked: \${count}</button>\`;
|
|
@@ -838,11 +910,16 @@ function generateFullStack(config) {
|
|
|
838
910
|
function generateFullStackVanilla(config) {
|
|
839
911
|
const ext = config.typescript ? "ts" : "js";
|
|
840
912
|
const t = config.typescript;
|
|
841
|
-
return
|
|
913
|
+
return {
|
|
914
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
842
915
|
import {logger} from "@b9g/router/middleware";
|
|
916
|
+
import {assets} from "@b9g/assets/middleware";
|
|
917
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
918
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
843
919
|
|
|
844
920
|
const router = new Router();
|
|
845
921
|
router.use(logger());
|
|
922
|
+
router.use(assets());
|
|
846
923
|
|
|
847
924
|
// API routes
|
|
848
925
|
router.route("/api/hello").get(() => {
|
|
@@ -861,17 +938,9 @@ router.route("/api/echo").post(async (req) => {
|
|
|
861
938
|
router.route("/").get(() => {
|
|
862
939
|
return new Response(renderPage("Home", \`
|
|
863
940
|
<h1>Welcome to ${config.name}</h1>
|
|
864
|
-
<p>Edit <code>src/
|
|
941
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
865
942
|
<button id="call-api">Call API</button>
|
|
866
943
|
<div id="result"></div>
|
|
867
|
-
<script>
|
|
868
|
-
document.getElementById("call-api").addEventListener("click", async () => {
|
|
869
|
-
const res = await fetch("/api/hello");
|
|
870
|
-
const data = await res.json();
|
|
871
|
-
document.getElementById("result").innerHTML =
|
|
872
|
-
"<p>" + data.message + "</p><p><small>" + data.timestamp + "</small></p>";
|
|
873
|
-
});
|
|
874
|
-
</script>
|
|
875
944
|
<ul>
|
|
876
945
|
<li><a href="/about">About</a></li>
|
|
877
946
|
<li><a href="/api/hello">API: /api/hello</a></li>
|
|
@@ -904,6 +973,7 @@ ${css}
|
|
|
904
973
|
</head>
|
|
905
974
|
<body>
|
|
906
975
|
<main>\${content}</main>
|
|
976
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
907
977
|
</body>
|
|
908
978
|
</html>\`;
|
|
909
979
|
}
|
|
@@ -911,16 +981,35 @@ ${css}
|
|
|
911
981
|
self.addEventListener("fetch", (event) => {
|
|
912
982
|
event.respondWith(router.handle(event.request));
|
|
913
983
|
});
|
|
914
|
-
|
|
984
|
+
`,
|
|
985
|
+
[`client.${ext}`]: `const callBtn = document.getElementById("call-api");
|
|
986
|
+
if (callBtn) {
|
|
987
|
+
callBtn.addEventListener("click", async () => {
|
|
988
|
+
const res = await fetch("/api/hello");
|
|
989
|
+
const data = await res.json();
|
|
990
|
+
const result = document.getElementById("result");
|
|
991
|
+
if (result) {
|
|
992
|
+
result.innerHTML =
|
|
993
|
+
\`<p>\${data.message}</p><p><small>\${data.timestamp}</small></p>\`;
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
`
|
|
998
|
+
};
|
|
915
999
|
}
|
|
916
1000
|
function generateFullStackHtmx(config) {
|
|
917
1001
|
const ext = config.typescript ? "ts" : "js";
|
|
918
1002
|
const t = config.typescript;
|
|
919
|
-
|
|
1003
|
+
const files = {
|
|
1004
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
920
1005
|
import {logger} from "@b9g/router/middleware";
|
|
1006
|
+
import {assets} from "@b9g/assets/middleware";
|
|
1007
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1008
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
921
1009
|
|
|
922
1010
|
const router = new Router();
|
|
923
1011
|
router.use(logger());
|
|
1012
|
+
router.use(assets());
|
|
924
1013
|
|
|
925
1014
|
// API routes \u2014 return HTML fragments when HTMX requests, JSON otherwise
|
|
926
1015
|
router.route("/api/hello").get((req) => {
|
|
@@ -946,7 +1035,7 @@ router.route("/api/echo").post(async (req) => {
|
|
|
946
1035
|
router.route("/").get(() => {
|
|
947
1036
|
return new Response(renderPage("Home", \`
|
|
948
1037
|
<h1>Welcome to ${config.name}</h1>
|
|
949
|
-
<p>Edit <code>src/
|
|
1038
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
950
1039
|
<button hx-get="/api/hello" hx-target="#result" hx-swap="innerHTML">Call API</button>
|
|
951
1040
|
<div id="result"></div>
|
|
952
1041
|
<ul>
|
|
@@ -975,13 +1064,13 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
975
1064
|
<meta charset="UTF-8">
|
|
976
1065
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
977
1066
|
<title>\${title} - ${config.name}</title>
|
|
978
|
-
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
|
979
1067
|
<style>
|
|
980
1068
|
${css}
|
|
981
1069
|
</style>
|
|
982
1070
|
</head>
|
|
983
1071
|
<body>
|
|
984
1072
|
<main>\${content}</main>
|
|
1073
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
985
1074
|
</body>
|
|
986
1075
|
</html>\`;
|
|
987
1076
|
}
|
|
@@ -989,16 +1078,29 @@ ${css}
|
|
|
989
1078
|
self.addEventListener("fetch", (event) => {
|
|
990
1079
|
event.respondWith(router.handle(event.request));
|
|
991
1080
|
});
|
|
1081
|
+
`,
|
|
1082
|
+
[`client.${ext}`]: `import "htmx.org";
|
|
1083
|
+
`
|
|
1084
|
+
};
|
|
1085
|
+
if (t) {
|
|
1086
|
+
files[`env.d.ts`] = `declare module "htmx.org";
|
|
992
1087
|
`;
|
|
1088
|
+
}
|
|
1089
|
+
return files;
|
|
993
1090
|
}
|
|
994
1091
|
function generateFullStackAlpine(config) {
|
|
995
1092
|
const ext = config.typescript ? "ts" : "js";
|
|
996
1093
|
const t = config.typescript;
|
|
997
|
-
|
|
1094
|
+
const files = {
|
|
1095
|
+
[`server.${ext}`]: `import {Router} from "@b9g/router";
|
|
998
1096
|
import {logger} from "@b9g/router/middleware";
|
|
1097
|
+
import {assets} from "@b9g/assets/middleware";
|
|
1098
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1099
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
999
1100
|
|
|
1000
1101
|
const router = new Router();
|
|
1001
1102
|
router.use(logger());
|
|
1103
|
+
router.use(assets());
|
|
1002
1104
|
|
|
1003
1105
|
// API routes
|
|
1004
1106
|
router.route("/api/hello").get(() => {
|
|
@@ -1017,7 +1119,7 @@ router.route("/api/echo").post(async (req) => {
|
|
|
1017
1119
|
router.route("/").get(() => {
|
|
1018
1120
|
return new Response(renderPage("Home", \`
|
|
1019
1121
|
<h1>Welcome to ${config.name}</h1>
|
|
1020
|
-
<p>Edit <code>src/
|
|
1122
|
+
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
1021
1123
|
<div x-data="{ result: null }">
|
|
1022
1124
|
<button @click="fetch('/api/hello').then(r => r.json()).then(d => result = d)">Call API</button>
|
|
1023
1125
|
<div id="result" x-show="result">
|
|
@@ -1051,13 +1153,13 @@ function renderPage(title${t ? ": string" : ""}, content${t ? ": string" : ""})$
|
|
|
1051
1153
|
<meta charset="UTF-8">
|
|
1052
1154
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1053
1155
|
<title>\${title} - ${config.name}</title>
|
|
1054
|
-
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.9/dist/cdn.min.js"></script>
|
|
1055
1156
|
<style>
|
|
1056
1157
|
${css}
|
|
1057
1158
|
</style>
|
|
1058
1159
|
</head>
|
|
1059
1160
|
<body>
|
|
1060
1161
|
<main>\${content}</main>
|
|
1162
|
+
<script src="\${clientUrl}" type="module"></script>
|
|
1061
1163
|
</body>
|
|
1062
1164
|
</html>\`;
|
|
1063
1165
|
}
|
|
@@ -1065,7 +1167,24 @@ ${css}
|
|
|
1065
1167
|
self.addEventListener("fetch", (event) => {
|
|
1066
1168
|
event.respondWith(router.handle(event.request));
|
|
1067
1169
|
});
|
|
1170
|
+
`,
|
|
1171
|
+
[`client.${ext}`]: `import Alpine from "alpinejs";
|
|
1172
|
+
Alpine.start();
|
|
1173
|
+
`
|
|
1174
|
+
};
|
|
1175
|
+
if (t) {
|
|
1176
|
+
files[`env.d.ts`] = `declare module "alpinejs" {
|
|
1177
|
+
interface Alpine {
|
|
1178
|
+
start(): void;
|
|
1179
|
+
plugin(plugin: unknown): void;
|
|
1180
|
+
[key: string]: unknown;
|
|
1181
|
+
}
|
|
1182
|
+
const alpine: Alpine;
|
|
1183
|
+
export default alpine;
|
|
1184
|
+
}
|
|
1068
1185
|
`;
|
|
1186
|
+
}
|
|
1187
|
+
return files;
|
|
1069
1188
|
}
|
|
1070
1189
|
function generateFullStackCrank(config) {
|
|
1071
1190
|
const t = config.typescript;
|
|
@@ -1077,7 +1196,8 @@ import {Router} from "@b9g/router";
|
|
|
1077
1196
|
import {logger} from "@b9g/router/middleware";
|
|
1078
1197
|
import {assets} from "@b9g/assets/middleware";
|
|
1079
1198
|
import {Page, Counter} from "./components";
|
|
1080
|
-
|
|
1199
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1200
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
1081
1201
|
|
|
1082
1202
|
const router = new Router();
|
|
1083
1203
|
router.use(logger());
|
|
@@ -1155,8 +1275,7 @@ export function Page({title, children, clientUrl}${t ? ": {title: string, childr
|
|
|
1155
1275
|
export function *Counter(${t ? "this: Context" : ""}) {
|
|
1156
1276
|
let count = 0;
|
|
1157
1277
|
const handleClick = () => {
|
|
1158
|
-
count
|
|
1159
|
-
this.refresh();
|
|
1278
|
+
this.refresh(() => count++);
|
|
1160
1279
|
};
|
|
1161
1280
|
for ({} of this) {
|
|
1162
1281
|
yield <button onclick={handleClick}>Clicked: {count}</button>;
|
|
@@ -1177,7 +1296,8 @@ import {Router} from "@b9g/router";
|
|
|
1177
1296
|
import {logger} from "@b9g/router/middleware";
|
|
1178
1297
|
import {assets} from "@b9g/assets/middleware";
|
|
1179
1298
|
import {Page, Counter} from "./components";
|
|
1180
|
-
|
|
1299
|
+
${t ? `// @ts-expect-error \u2014 asset URL resolved by Shovel build system
|
|
1300
|
+
` : ""}import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
1181
1301
|
|
|
1182
1302
|
const router = new Router();
|
|
1183
1303
|
router.use(logger());
|
|
@@ -1256,8 +1376,7 @@ export function Page({title, children, clientUrl}${t ? ": {title: string, childr
|
|
|
1256
1376
|
export function *Counter(${t ? "this: Context" : ""}) {
|
|
1257
1377
|
let count = 0;
|
|
1258
1378
|
const handleClick = () => {
|
|
1259
|
-
count
|
|
1260
|
-
this.refresh();
|
|
1379
|
+
this.refresh(() => count++);
|
|
1261
1380
|
};
|
|
1262
1381
|
for ({} of this) {
|
|
1263
1382
|
yield jsx\`<button onclick=\${handleClick}>Clicked: \${count}</button>\`;
|
|
@@ -1286,6 +1405,7 @@ function generateReadme(config) {
|
|
|
1286
1405
|
};
|
|
1287
1406
|
const ext = config.uiFramework === "crank" && config.useJSX ? config.typescript ? "tsx" : "jsx" : config.typescript ? "ts" : "js";
|
|
1288
1407
|
const isCrank = config.uiFramework === "crank";
|
|
1408
|
+
const hasClientBundle = config.template === "static-site" || config.template === "full-stack";
|
|
1289
1409
|
let projectTree;
|
|
1290
1410
|
if (isCrank) {
|
|
1291
1411
|
projectTree = `${config.name}/
|
|
@@ -1295,6 +1415,14 @@ function generateReadme(config) {
|
|
|
1295
1415
|
\u2502 \u2514\u2500\u2500 client.${ext} # Client-side hydration
|
|
1296
1416
|
\u251C\u2500\u2500 eslint.config.js
|
|
1297
1417
|
\u251C\u2500\u2500 package.json
|
|
1418
|
+
${config.typescript ? "\u251C\u2500\u2500 tsconfig.json\n" : ""}\u2514\u2500\u2500 README.md`;
|
|
1419
|
+
} else if (hasClientBundle) {
|
|
1420
|
+
projectTree = `${config.name}/
|
|
1421
|
+
\u251C\u2500\u2500 src/
|
|
1422
|
+
\u2502 \u251C\u2500\u2500 server.${ext} # Application entry point
|
|
1423
|
+
\u2502 \u2514\u2500\u2500 client.${ext} # Client-side code
|
|
1424
|
+
\u251C\u2500\u2500 eslint.config.js
|
|
1425
|
+
\u251C\u2500\u2500 package.json
|
|
1298
1426
|
${config.typescript ? "\u251C\u2500\u2500 tsconfig.json\n" : ""}\u2514\u2500\u2500 README.md`;
|
|
1299
1427
|
} else {
|
|
1300
1428
|
projectTree = `${config.name}/
|
|
@@ -1320,7 +1448,7 @@ Open http://localhost:7777
|
|
|
1320
1448
|
|
|
1321
1449
|
- \`npm run develop\` - Start development server
|
|
1322
1450
|
- \`npm run build\` - Build for production
|
|
1323
|
-
- \`npm start\` - Run production build${
|
|
1451
|
+
- \`npm start\` - Run production build${hasClientBundle ? "\n- `npm run lint` - Lint source files" : ""}
|
|
1324
1452
|
|
|
1325
1453
|
## Project Structure
|
|
1326
1454
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/shovel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "ServiceWorker-first universal deployment platform. Write ServiceWorker apps once, deploy anywhere (Node/Bun/Cloudflare). Registry-based multi-app orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@b9g/filesystem": "^0.2.0",
|
|
20
20
|
"@b9g/http-errors": "^0.2.1",
|
|
21
21
|
"@b9g/node-webworker": "^0.2.1",
|
|
22
|
-
"@b9g/platform": "^0.1.
|
|
22
|
+
"@b9g/platform": "^0.1.18",
|
|
23
23
|
"@b9g/platform-bun": "^0.1.16",
|
|
24
24
|
"@b9g/platform-cloudflare": "^0.1.15",
|
|
25
25
|
"@b9g/platform-node": "^0.1.17",
|