@b9g/shovel 0.2.10 → 0.2.12
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/CHANGELOG.md +42 -0
- package/bin/cli.js +2 -2
- package/bin/create.js +138 -37
- package/package.json +13 -12
- package/src/_chunks/{build-O5LLUOND.js → build-YOORTV6F.js} +1 -1
- package/src/_chunks/{chunk-DQDUKJQ4.js → chunk-DRCLXHTF.js} +6 -8
- package/src/_chunks/{develop-N265AMEN.js → develop-I74XUWOE.js} +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Shovel will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.12] - 2026-02-24
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **`@b9g/shovel`** - Plain `.js` and `.mjs` client assets are now bundled for the browser instead of copied raw. Previously only `.ts`/`.tsx`/`.jsx` were transpiled, so non-TypeScript Crank projects using `@b9g/crank/standalone` in client code failed with "Module name does not resolve to a valid URL" in the browser
|
|
10
|
+
- **`@b9g/shovel`** - Removed `.cts` from asset transpilation list (no CJS in client assets)
|
|
11
|
+
- **`@b9g/router`** - Simplified logger middleware to a single response line (`200 GET / (3ms)`) instead of logging both request and response with arrow prefixes
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
|
|
15
|
+
- **`create-shovel`** - Added CSS owl selector (`main > * + *`) for consistent vertical spacing, removed inline margin styles
|
|
16
|
+
- **`create-shovel`** - Added interactive counter demo to static-site vanilla template
|
|
17
|
+
- **`create-shovel`** - Added API call demo to full-stack vanilla template
|
|
18
|
+
- **`create-shovel`** - Crank templates now include client-side hydration with `@b9g/assets`
|
|
19
|
+
- **`create-shovel`** - Initial project version is now `0.1.0` instead of `0.0.1`
|
|
20
|
+
|
|
21
|
+
### Dependencies
|
|
22
|
+
|
|
23
|
+
- **`@b9g/crank`** `^0.7.2` → `^0.7.7` (type declarations)
|
|
24
|
+
|
|
25
|
+
## [0.2.11] - 2026-02-21
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
- **Broken bin exports in published package** - Upgraded to `@b9g/libuild@0.1.24` which fixes `./dist/bin/` → `./bin/` rewriting in the exports map. `import "@b9g/shovel/bin/create.js"` now resolves correctly when installed from npm
|
|
30
|
+
|
|
31
|
+
### Dependencies
|
|
32
|
+
|
|
33
|
+
- **`@b9g/libuild`** `^0.1.22` → `^0.1.24`
|
|
34
|
+
- **`create-shovel`** `0.1.1` - README and metadata updates
|
|
35
|
+
|
|
36
|
+
## [0.2.10] - 2026-02-21
|
|
37
|
+
|
|
38
|
+
### Features
|
|
39
|
+
|
|
40
|
+
- **Crank JSX / tagged template choice** - `create-shovel` now prompts "Use JSX?" when Crank is selected. Choose JSX (`.tsx`) or tagged template literals (`.ts`, no build step)
|
|
41
|
+
- **Multi-file Crank templates** - Crank projects generate `server.{ext}` + `components.{ext}` with `@b9g/router` for route-based request handling
|
|
42
|
+
- **`--framework` and `--jsx` CLI flags** - `create-shovel --framework crank --no-jsx` skips prompts for CI/scripting
|
|
43
|
+
- **ESLint config for Crank projects** - Generates `eslint.config.js` with flat config format
|
|
44
|
+
- **Bun-aware next steps** - Post-scaffold instructions show `bun install` / `bun run develop` when bun platform is selected
|
|
45
|
+
- **Removed redundant `env.d.ts`** - `@b9g/platform` already ships `globals.d.ts`; scaffolded projects now use `"types": ["@b9g/platform/globals"]` in tsconfig instead
|
|
46
|
+
|
|
5
47
|
## [0.2.9] - 2026-02-20
|
|
6
48
|
|
|
7
49
|
### Features
|
package/bin/cli.js
CHANGED
|
@@ -75,7 +75,7 @@ program.command("develop <entrypoint>").description("Start development server wi
|
|
|
75
75
|
DEFAULTS.WORKERS
|
|
76
76
|
).option("--platform <name>", "Runtime platform (node, cloudflare, bun)").action(async (entrypoint, options) => {
|
|
77
77
|
checkPlatformReexec(options);
|
|
78
|
-
const { developCommand } = await import("../src/_chunks/develop-
|
|
78
|
+
const { developCommand } = await import("../src/_chunks/develop-I74XUWOE.js");
|
|
79
79
|
await developCommand(entrypoint, options, config);
|
|
80
80
|
});
|
|
81
81
|
program.command("create [name]").description("Create a new Shovel project").action(async (name) => {
|
|
@@ -91,7 +91,7 @@ program.command("build <entrypoint>").description("Build app for production").op
|
|
|
91
91
|
"Run ServiceWorker lifecycle after build (install or activate, default: activate)"
|
|
92
92
|
).action(async (entrypoint, options) => {
|
|
93
93
|
checkPlatformReexec(options);
|
|
94
|
-
const { buildCommand } = await import("../src/_chunks/build-
|
|
94
|
+
const { buildCommand } = await import("../src/_chunks/build-YOORTV6F.js");
|
|
95
95
|
await buildCommand(entrypoint, options, config);
|
|
96
96
|
process.exit(0);
|
|
97
97
|
});
|
package/bin/create.js
CHANGED
|
@@ -140,7 +140,7 @@ async function main() {
|
|
|
140
140
|
{
|
|
141
141
|
value: "crank",
|
|
142
142
|
label: "Crank.js",
|
|
143
|
-
hint: "JSX components
|
|
143
|
+
hint: "JSX components with server rendering and hydration"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
value: "htmx",
|
|
@@ -273,6 +273,7 @@ async function createProject(config, projectPath) {
|
|
|
273
273
|
};
|
|
274
274
|
if (isCrank) {
|
|
275
275
|
dependencies["@b9g/crank"] = "^0.7.2";
|
|
276
|
+
dependencies["@b9g/assets"] = "^0.2.0";
|
|
276
277
|
}
|
|
277
278
|
const devDependencies = {};
|
|
278
279
|
if (config.typescript) {
|
|
@@ -280,8 +281,8 @@ async function createProject(config, projectPath) {
|
|
|
280
281
|
devDependencies["typescript"] = "^5.0.0";
|
|
281
282
|
}
|
|
282
283
|
if (isCrank) {
|
|
283
|
-
devDependencies["eslint"] = "^
|
|
284
|
-
devDependencies["@eslint/js"] = "^
|
|
284
|
+
devDependencies["eslint"] = "^10.0.0";
|
|
285
|
+
devDependencies["@eslint/js"] = "^10.0.0";
|
|
285
286
|
}
|
|
286
287
|
const scripts = {
|
|
287
288
|
develop: `shovel develop ${entryFile} --platform ${config.platform}`,
|
|
@@ -291,10 +292,10 @@ async function createProject(config, projectPath) {
|
|
|
291
292
|
if (isCrank) {
|
|
292
293
|
scripts.lint = "eslint src/";
|
|
293
294
|
}
|
|
294
|
-
const
|
|
295
|
+
const packageJSON = {
|
|
295
296
|
name: config.name,
|
|
296
297
|
private: true,
|
|
297
|
-
version: "0.0
|
|
298
|
+
version: "0.1.0",
|
|
298
299
|
type: "module",
|
|
299
300
|
scripts,
|
|
300
301
|
dependencies,
|
|
@@ -302,7 +303,7 @@ async function createProject(config, projectPath) {
|
|
|
302
303
|
};
|
|
303
304
|
await writeFile(
|
|
304
305
|
join(projectPath, "package.json"),
|
|
305
|
-
JSON.stringify(
|
|
306
|
+
JSON.stringify(packageJSON, null, 2)
|
|
306
307
|
);
|
|
307
308
|
const appResult = generateAppFile(config);
|
|
308
309
|
if (typeof appResult === "string") {
|
|
@@ -388,8 +389,8 @@ self.addEventListener("fetch", (event) => {
|
|
|
388
389
|
`;
|
|
389
390
|
}
|
|
390
391
|
function generateApi(config) {
|
|
391
|
-
return `import {
|
|
392
|
-
import {
|
|
392
|
+
return `import {Router} from "@b9g/router";
|
|
393
|
+
import {logger} from "@b9g/router/middleware";
|
|
393
394
|
|
|
394
395
|
const router = new Router();
|
|
395
396
|
router.use(logger());
|
|
@@ -453,9 +454,10 @@ var css = ` * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
453
454
|
body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; color: #333; background: #fafafa; }
|
|
454
455
|
main { max-width: 640px; margin: 4rem auto; padding: 2rem; }
|
|
455
456
|
h1 { color: #2563eb; margin-bottom: 1rem; }
|
|
457
|
+
main > * + * { margin-top: 1rem; }
|
|
456
458
|
code { background: #e5e7eb; padding: 0.2rem 0.4rem; border-radius: 4px; font-size: 0.9em; }
|
|
457
459
|
a { color: #2563eb; }
|
|
458
|
-
ul {
|
|
460
|
+
ul { padding-left: 1.5rem; }
|
|
459
461
|
li { margin-bottom: 0.5rem; }
|
|
460
462
|
button { background: #2563eb; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; font-size: 1rem; }
|
|
461
463
|
button:hover { background: #1d4ed8; }
|
|
@@ -489,6 +491,12 @@ async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Res
|
|
|
489
491
|
return new Response(renderPage("Home", \`
|
|
490
492
|
<h1>Welcome to ${config.name}</h1>
|
|
491
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>
|
|
492
500
|
<p><a href="/about">About</a></p>
|
|
493
501
|
\`), {
|
|
494
502
|
headers: { "Content-Type": "text/html" },
|
|
@@ -545,7 +553,7 @@ async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Res
|
|
|
545
553
|
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
546
554
|
<button hx-get="/greeting" hx-target="#result" hx-swap="innerHTML">Get Greeting</button>
|
|
547
555
|
<div id="result"></div>
|
|
548
|
-
<p
|
|
556
|
+
<p><a href="/about">About</a></p>
|
|
549
557
|
\`), {
|
|
550
558
|
headers: { "Content-Type": "text/html" },
|
|
551
559
|
});
|
|
@@ -611,7 +619,7 @@ async function handleRequest(request${t ? ": Request" : ""})${t ? ": Promise<Res
|
|
|
611
619
|
<div x-data="{ count: 0 }">
|
|
612
620
|
<button @click="count++">Clicked: <span x-text="count"></span></button>
|
|
613
621
|
</div>
|
|
614
|
-
<p
|
|
622
|
+
<p><a href="/about">About</a></p>
|
|
615
623
|
\`), {
|
|
616
624
|
headers: { "Content-Type": "text/html" },
|
|
617
625
|
});
|
|
@@ -656,15 +664,19 @@ function generateStaticSiteCrank(config) {
|
|
|
656
664
|
return {
|
|
657
665
|
[`server.${ext}`]: `import {renderer} from "@b9g/crank/html";
|
|
658
666
|
import {Router} from "@b9g/router";
|
|
659
|
-
import {
|
|
667
|
+
import {assets} from "@b9g/assets/middleware";
|
|
668
|
+
import {Page, Counter} from "./components";
|
|
669
|
+
import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
660
670
|
|
|
661
671
|
const router = new Router();
|
|
672
|
+
router.use(assets());
|
|
662
673
|
|
|
663
674
|
router.route("/").get(async () => {
|
|
664
675
|
const html = await renderer.render(
|
|
665
|
-
<Page title="Home">
|
|
676
|
+
<Page title="Home" clientUrl={clientUrl}>
|
|
666
677
|
<h1>Welcome to ${config.name}</h1>
|
|
667
678
|
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
679
|
+
<div id="counter"><Counter /></div>
|
|
668
680
|
<p><a href="/about">About</a></p>
|
|
669
681
|
</Page>
|
|
670
682
|
);
|
|
@@ -690,11 +702,11 @@ self.addEventListener("fetch", (event) => {
|
|
|
690
702
|
event.respondWith(router.handle(event.request));
|
|
691
703
|
});
|
|
692
704
|
`,
|
|
693
|
-
[`components.${ext}`]:
|
|
705
|
+
[`components.${ext}`]: `${t ? 'import type {Context} from "@b9g/crank";\n\n' : ""}const css = \`
|
|
694
706
|
${css}
|
|
695
707
|
\`;
|
|
696
708
|
|
|
697
|
-
export function Page({title, children}${t ? ": {title: string, children: unknown}" : ""}) {
|
|
709
|
+
export function Page({title, children, clientUrl}${t ? ": {title: string, children: unknown, clientUrl?: string}" : ""}) {
|
|
698
710
|
return (
|
|
699
711
|
<html lang="en">
|
|
700
712
|
<head>
|
|
@@ -705,10 +717,27 @@ export function Page({title, children}${t ? ": {title: string, children: unknown
|
|
|
705
717
|
</head>
|
|
706
718
|
<body>
|
|
707
719
|
<main>{children}</main>
|
|
720
|
+
{clientUrl && <script src={clientUrl} type="module" />}
|
|
708
721
|
</body>
|
|
709
722
|
</html>
|
|
710
723
|
);
|
|
711
724
|
}
|
|
725
|
+
|
|
726
|
+
export function *Counter(${t ? "this: Context" : ""}) {
|
|
727
|
+
let count = 0;
|
|
728
|
+
const handleClick = () => {
|
|
729
|
+
count++;
|
|
730
|
+
this.refresh();
|
|
731
|
+
};
|
|
732
|
+
for ({} of this) {
|
|
733
|
+
yield <button onclick={handleClick}>Clicked: {count}</button>;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
`,
|
|
737
|
+
[`client.${ext}`]: `import {renderer} from "@b9g/crank/dom";
|
|
738
|
+
import {Counter} from "./components";
|
|
739
|
+
|
|
740
|
+
renderer.hydrate(<Counter />, document.getElementById("counter")${t ? "!" : ""});
|
|
712
741
|
`
|
|
713
742
|
};
|
|
714
743
|
}
|
|
@@ -716,15 +745,19 @@ export function Page({title, children}${t ? ": {title: string, children: unknown
|
|
|
716
745
|
[`server.${ext}`]: `import {jsx} from "@b9g/crank/standalone";
|
|
717
746
|
import {renderer} from "@b9g/crank/html";
|
|
718
747
|
import {Router} from "@b9g/router";
|
|
719
|
-
import {
|
|
748
|
+
import {assets} from "@b9g/assets/middleware";
|
|
749
|
+
import {Page, Counter} from "./components";
|
|
750
|
+
import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
720
751
|
|
|
721
752
|
const router = new Router();
|
|
753
|
+
router.use(assets());
|
|
722
754
|
|
|
723
755
|
router.route("/").get(async () => {
|
|
724
756
|
const html = await renderer.render(jsx\`
|
|
725
|
-
<\${Page} title="Home">
|
|
757
|
+
<\${Page} title="Home" clientUrl=\${clientUrl}>
|
|
726
758
|
<h1>Welcome to ${config.name}</h1>
|
|
727
759
|
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
760
|
+
<div id="counter"><\${Counter} /></div>
|
|
728
761
|
<p><a href="/about">About</a></p>
|
|
729
762
|
</\${Page}>
|
|
730
763
|
\`);
|
|
@@ -751,12 +784,11 @@ self.addEventListener("fetch", (event) => {
|
|
|
751
784
|
});
|
|
752
785
|
`,
|
|
753
786
|
[`components.${ext}`]: `import {jsx} from "@b9g/crank/standalone";
|
|
754
|
-
|
|
755
|
-
const css = \`
|
|
787
|
+
${t ? 'import type {Context} from "@b9g/crank/standalone";\n\n' : "\n"}const css = \`
|
|
756
788
|
${css}
|
|
757
789
|
\`;
|
|
758
790
|
|
|
759
|
-
export function Page({title, children}${t ? ": {title: string, children: unknown}" : ""}) {
|
|
791
|
+
export function Page({title, children, clientUrl}${t ? ": {title: string, children: unknown, clientUrl?: string}" : ""}) {
|
|
760
792
|
return jsx\`
|
|
761
793
|
<html lang="en">
|
|
762
794
|
<head>
|
|
@@ -767,10 +799,27 @@ export function Page({title, children}${t ? ": {title: string, children: unknown
|
|
|
767
799
|
</head>
|
|
768
800
|
<body>
|
|
769
801
|
<main>\${children}</main>
|
|
802
|
+
\${clientUrl && jsx\`<script src=\${clientUrl} type="module" />\`}
|
|
770
803
|
</body>
|
|
771
804
|
</html>
|
|
772
805
|
\`;
|
|
773
806
|
}
|
|
807
|
+
|
|
808
|
+
export function *Counter(${t ? "this: Context" : ""}) {
|
|
809
|
+
let count = 0;
|
|
810
|
+
const handleClick = () => {
|
|
811
|
+
count++;
|
|
812
|
+
this.refresh();
|
|
813
|
+
};
|
|
814
|
+
for ({} of this) {
|
|
815
|
+
yield jsx\`<button onclick=\${handleClick}>Clicked: \${count}</button>\`;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
`,
|
|
819
|
+
[`client.${ext}`]: `import {jsx, renderer} from "@b9g/crank/standalone";
|
|
820
|
+
import {Counter} from "./components";
|
|
821
|
+
|
|
822
|
+
renderer.hydrate(jsx\`<\${Counter} />\`, document.getElementById("counter")${t ? "!" : ""});
|
|
774
823
|
`
|
|
775
824
|
};
|
|
776
825
|
}
|
|
@@ -789,8 +838,8 @@ function generateFullStack(config) {
|
|
|
789
838
|
function generateFullStackVanilla(config) {
|
|
790
839
|
const ext = config.typescript ? "ts" : "js";
|
|
791
840
|
const t = config.typescript;
|
|
792
|
-
return `import {
|
|
793
|
-
import {
|
|
841
|
+
return `import {Router} from "@b9g/router";
|
|
842
|
+
import {logger} from "@b9g/router/middleware";
|
|
794
843
|
|
|
795
844
|
const router = new Router();
|
|
796
845
|
router.use(logger());
|
|
@@ -813,6 +862,16 @@ router.route("/").get(() => {
|
|
|
813
862
|
return new Response(renderPage("Home", \`
|
|
814
863
|
<h1>Welcome to ${config.name}</h1>
|
|
815
864
|
<p>Edit <code>src/app.${ext}</code> to get started.</p>
|
|
865
|
+
<button id="call-api">Call API</button>
|
|
866
|
+
<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>
|
|
816
875
|
<ul>
|
|
817
876
|
<li><a href="/about">About</a></li>
|
|
818
877
|
<li><a href="/api/hello">API: /api/hello</a></li>
|
|
@@ -857,8 +916,8 @@ self.addEventListener("fetch", (event) => {
|
|
|
857
916
|
function generateFullStackHtmx(config) {
|
|
858
917
|
const ext = config.typescript ? "ts" : "js";
|
|
859
918
|
const t = config.typescript;
|
|
860
|
-
return `import {
|
|
861
|
-
import {
|
|
919
|
+
return `import {Router} from "@b9g/router";
|
|
920
|
+
import {logger} from "@b9g/router/middleware";
|
|
862
921
|
|
|
863
922
|
const router = new Router();
|
|
864
923
|
router.use(logger());
|
|
@@ -935,8 +994,8 @@ self.addEventListener("fetch", (event) => {
|
|
|
935
994
|
function generateFullStackAlpine(config) {
|
|
936
995
|
const ext = config.typescript ? "ts" : "js";
|
|
937
996
|
const t = config.typescript;
|
|
938
|
-
return `import {
|
|
939
|
-
import {
|
|
997
|
+
return `import {Router} from "@b9g/router";
|
|
998
|
+
import {logger} from "@b9g/router/middleware";
|
|
940
999
|
|
|
941
1000
|
const router = new Router();
|
|
942
1001
|
router.use(logger());
|
|
@@ -1016,10 +1075,13 @@ function generateFullStackCrank(config) {
|
|
|
1016
1075
|
[`server.${ext}`]: `import {renderer} from "@b9g/crank/html";
|
|
1017
1076
|
import {Router} from "@b9g/router";
|
|
1018
1077
|
import {logger} from "@b9g/router/middleware";
|
|
1019
|
-
import {
|
|
1078
|
+
import {assets} from "@b9g/assets/middleware";
|
|
1079
|
+
import {Page, Counter} from "./components";
|
|
1080
|
+
import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
1020
1081
|
|
|
1021
1082
|
const router = new Router();
|
|
1022
1083
|
router.use(logger());
|
|
1084
|
+
router.use(assets());
|
|
1023
1085
|
|
|
1024
1086
|
// API routes
|
|
1025
1087
|
router.route("/api/hello").get(() => {
|
|
@@ -1037,9 +1099,10 @@ router.route("/api/echo").post(async (req) => {
|
|
|
1037
1099
|
// HTML pages
|
|
1038
1100
|
router.route("/").get(async () => {
|
|
1039
1101
|
const html = await renderer.render(
|
|
1040
|
-
<Page title="Home">
|
|
1102
|
+
<Page title="Home" clientUrl={clientUrl}>
|
|
1041
1103
|
<h1>Welcome to ${config.name}</h1>
|
|
1042
1104
|
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
1105
|
+
<div id="counter"><Counter /></div>
|
|
1043
1106
|
<ul>
|
|
1044
1107
|
<li><a href="/about">About</a></li>
|
|
1045
1108
|
<li><a href="/api/hello">API: /api/hello</a></li>
|
|
@@ -1068,11 +1131,11 @@ self.addEventListener("fetch", (event) => {
|
|
|
1068
1131
|
event.respondWith(router.handle(event.request));
|
|
1069
1132
|
});
|
|
1070
1133
|
`,
|
|
1071
|
-
[`components.${ext}`]:
|
|
1134
|
+
[`components.${ext}`]: `${t ? 'import type {Context} from "@b9g/crank";\n\n' : ""}const css = \`
|
|
1072
1135
|
${css}
|
|
1073
1136
|
\`;
|
|
1074
1137
|
|
|
1075
|
-
export function Page({title, children}${t ? ": {title: string, children: unknown}" : ""}) {
|
|
1138
|
+
export function Page({title, children, clientUrl}${t ? ": {title: string, children: unknown, clientUrl?: string}" : ""}) {
|
|
1076
1139
|
return (
|
|
1077
1140
|
<html lang="en">
|
|
1078
1141
|
<head>
|
|
@@ -1083,10 +1146,27 @@ export function Page({title, children}${t ? ": {title: string, children: unknown
|
|
|
1083
1146
|
</head>
|
|
1084
1147
|
<body>
|
|
1085
1148
|
<main>{children}</main>
|
|
1149
|
+
{clientUrl && <script src={clientUrl} type="module" />}
|
|
1086
1150
|
</body>
|
|
1087
1151
|
</html>
|
|
1088
1152
|
);
|
|
1089
1153
|
}
|
|
1154
|
+
|
|
1155
|
+
export function *Counter(${t ? "this: Context" : ""}) {
|
|
1156
|
+
let count = 0;
|
|
1157
|
+
const handleClick = () => {
|
|
1158
|
+
count++;
|
|
1159
|
+
this.refresh();
|
|
1160
|
+
};
|
|
1161
|
+
for ({} of this) {
|
|
1162
|
+
yield <button onclick={handleClick}>Clicked: {count}</button>;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
`,
|
|
1166
|
+
[`client.${ext}`]: `import {renderer} from "@b9g/crank/dom";
|
|
1167
|
+
import {Counter} from "./components";
|
|
1168
|
+
|
|
1169
|
+
renderer.hydrate(<Counter />, document.getElementById("counter")${t ? "!" : ""});
|
|
1090
1170
|
`
|
|
1091
1171
|
};
|
|
1092
1172
|
}
|
|
@@ -1095,10 +1175,13 @@ export function Page({title, children}${t ? ": {title: string, children: unknown
|
|
|
1095
1175
|
import {renderer} from "@b9g/crank/html";
|
|
1096
1176
|
import {Router} from "@b9g/router";
|
|
1097
1177
|
import {logger} from "@b9g/router/middleware";
|
|
1098
|
-
import {
|
|
1178
|
+
import {assets} from "@b9g/assets/middleware";
|
|
1179
|
+
import {Page, Counter} from "./components";
|
|
1180
|
+
import clientUrl from "./client.${ext}" with {assetBase: "/assets/"};
|
|
1099
1181
|
|
|
1100
1182
|
const router = new Router();
|
|
1101
1183
|
router.use(logger());
|
|
1184
|
+
router.use(assets());
|
|
1102
1185
|
|
|
1103
1186
|
// API routes
|
|
1104
1187
|
router.route("/api/hello").get(() => {
|
|
@@ -1116,9 +1199,10 @@ router.route("/api/echo").post(async (req) => {
|
|
|
1116
1199
|
// HTML pages
|
|
1117
1200
|
router.route("/").get(async () => {
|
|
1118
1201
|
const html = await renderer.render(jsx\`
|
|
1119
|
-
<\${Page} title="Home">
|
|
1202
|
+
<\${Page} title="Home" clientUrl=\${clientUrl}>
|
|
1120
1203
|
<h1>Welcome to ${config.name}</h1>
|
|
1121
1204
|
<p>Edit <code>src/server.${ext}</code> to get started.</p>
|
|
1205
|
+
<div id="counter"><\${Counter} /></div>
|
|
1122
1206
|
<ul>
|
|
1123
1207
|
<li><a href="/about">About</a></li>
|
|
1124
1208
|
<li><a href="/api/hello">API: /api/hello</a></li>
|
|
@@ -1148,12 +1232,11 @@ self.addEventListener("fetch", (event) => {
|
|
|
1148
1232
|
});
|
|
1149
1233
|
`,
|
|
1150
1234
|
[`components.${ext}`]: `import {jsx} from "@b9g/crank/standalone";
|
|
1151
|
-
|
|
1152
|
-
const css = \`
|
|
1235
|
+
${t ? 'import type {Context} from "@b9g/crank/standalone";\n\n' : "\n"}const css = \`
|
|
1153
1236
|
${css}
|
|
1154
1237
|
\`;
|
|
1155
1238
|
|
|
1156
|
-
export function Page({title, children}${t ? ": {title: string, children: unknown}" : ""}) {
|
|
1239
|
+
export function Page({title, children, clientUrl}${t ? ": {title: string, children: unknown, clientUrl?: string}" : ""}) {
|
|
1157
1240
|
return jsx\`
|
|
1158
1241
|
<html lang="en">
|
|
1159
1242
|
<head>
|
|
@@ -1164,10 +1247,27 @@ export function Page({title, children}${t ? ": {title: string, children: unknown
|
|
|
1164
1247
|
</head>
|
|
1165
1248
|
<body>
|
|
1166
1249
|
<main>\${children}</main>
|
|
1250
|
+
\${clientUrl && jsx\`<script src=\${clientUrl} type="module" />\`}
|
|
1167
1251
|
</body>
|
|
1168
1252
|
</html>
|
|
1169
1253
|
\`;
|
|
1170
1254
|
}
|
|
1255
|
+
|
|
1256
|
+
export function *Counter(${t ? "this: Context" : ""}) {
|
|
1257
|
+
let count = 0;
|
|
1258
|
+
const handleClick = () => {
|
|
1259
|
+
count++;
|
|
1260
|
+
this.refresh();
|
|
1261
|
+
};
|
|
1262
|
+
for ({} of this) {
|
|
1263
|
+
yield jsx\`<button onclick=\${handleClick}>Clicked: \${count}</button>\`;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
`,
|
|
1267
|
+
[`client.${ext}`]: `import {jsx, renderer} from "@b9g/crank/standalone";
|
|
1268
|
+
import {Counter} from "./components";
|
|
1269
|
+
|
|
1270
|
+
renderer.hydrate(jsx\`<\${Counter} />\`, document.getElementById("counter")${t ? "!" : ""});
|
|
1171
1271
|
`
|
|
1172
1272
|
};
|
|
1173
1273
|
}
|
|
@@ -1182,7 +1282,7 @@ function generateReadme(config) {
|
|
|
1182
1282
|
vanilla: "",
|
|
1183
1283
|
htmx: " using [HTMX](https://htmx.org)",
|
|
1184
1284
|
alpine: " using [Alpine.js](https://alpinejs.dev)",
|
|
1185
|
-
crank: " using [Crank.js](https://crank.js.org)"
|
|
1285
|
+
crank: " using [Crank.js](https://crank.js.org) with hydration"
|
|
1186
1286
|
};
|
|
1187
1287
|
const ext = config.uiFramework === "crank" && config.useJSX ? config.typescript ? "tsx" : "jsx" : config.typescript ? "ts" : "js";
|
|
1188
1288
|
const isCrank = config.uiFramework === "crank";
|
|
@@ -1191,7 +1291,8 @@ function generateReadme(config) {
|
|
|
1191
1291
|
projectTree = `${config.name}/
|
|
1192
1292
|
\u251C\u2500\u2500 src/
|
|
1193
1293
|
\u2502 \u251C\u2500\u2500 server.${ext} # Application entry point
|
|
1194
|
-
\u2502 \
|
|
1294
|
+
\u2502 \u251C\u2500\u2500 components.${ext} # Page components
|
|
1295
|
+
\u2502 \u2514\u2500\u2500 client.${ext} # Client-side hydration
|
|
1195
1296
|
\u251C\u2500\u2500 eslint.config.js
|
|
1196
1297
|
\u251C\u2500\u2500 package.json
|
|
1197
1298
|
${config.typescript ? "\u251C\u2500\u2500 tsconfig.json\n" : ""}\u2514\u2500\u2500 README.md`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/shovel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
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": {
|
|
@@ -24,8 +24,6 @@
|
|
|
24
24
|
"@b9g/platform-cloudflare": "^0.1.15",
|
|
25
25
|
"@b9g/platform-node": "^0.1.17",
|
|
26
26
|
"@clack/prompts": "^0.7.0",
|
|
27
|
-
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
|
28
|
-
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
|
29
27
|
"@logtape/logtape": "^2.0.0",
|
|
30
28
|
"commander": "^13.1.0",
|
|
31
29
|
"esbuild": "^0.27.2",
|
|
@@ -34,16 +32,19 @@
|
|
|
34
32
|
},
|
|
35
33
|
"devDependencies": {
|
|
36
34
|
"@b9g/assets": "^0.2.1",
|
|
37
|
-
"@b9g/crank": "^0.7.
|
|
38
|
-
"@b9g/libuild": "^0.1.
|
|
39
|
-
"@b9g/router": "^0.2.
|
|
35
|
+
"@b9g/crank": "^0.7.7",
|
|
36
|
+
"@b9g/libuild": "^0.1.24",
|
|
37
|
+
"@b9g/router": "^0.2.3",
|
|
38
|
+
"@eslint/js": "^9.0.0",
|
|
40
39
|
"@logtape/file": "^2.0.0",
|
|
41
40
|
"@types/bun": "^1.3.4",
|
|
42
41
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
43
42
|
"@typescript-eslint/parser": "^8.0.0",
|
|
43
|
+
"esbuild-plugins-node-modules-polyfill": "^1.8.1",
|
|
44
44
|
"eslint": "^9.0.0",
|
|
45
45
|
"eslint-config-prettier": "^10.0.0",
|
|
46
46
|
"eslint-plugin-prettier": "^5.0.0",
|
|
47
|
+
"miniflare": "^4.0.0",
|
|
47
48
|
"mitata": "^1.0.34",
|
|
48
49
|
"typescript": "^5.7.3"
|
|
49
50
|
},
|
|
@@ -53,19 +54,19 @@
|
|
|
53
54
|
"./package.json": "./package.json",
|
|
54
55
|
"./bin/cli": {
|
|
55
56
|
"types": "./src/undefined.d.ts",
|
|
56
|
-
"import": "./
|
|
57
|
+
"import": "./bin/cli.js"
|
|
57
58
|
},
|
|
58
59
|
"./bin/cli.js": {
|
|
59
60
|
"types": "./src/undefined.d.ts",
|
|
60
|
-
"import": "./
|
|
61
|
+
"import": "./bin/cli.js"
|
|
61
62
|
},
|
|
62
63
|
"./bin/create": {
|
|
63
|
-
"types": "./
|
|
64
|
-
"import": "./
|
|
64
|
+
"types": "./bin/create.d.ts",
|
|
65
|
+
"import": "./bin/create.js"
|
|
65
66
|
},
|
|
66
67
|
"./bin/create.js": {
|
|
67
|
-
"types": "./
|
|
68
|
-
"import": "./
|
|
68
|
+
"types": "./bin/create.d.ts",
|
|
69
|
+
"import": "./bin/create.js"
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -21,14 +21,14 @@ import { join, basename, extname, relative, dirname } from "path";
|
|
|
21
21
|
import mime from "mime";
|
|
22
22
|
import * as ESBuild from "esbuild";
|
|
23
23
|
import { getLogger } from "@logtape/logtape";
|
|
24
|
-
import {
|
|
25
|
-
import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill";
|
|
24
|
+
import { nodeModulesPolyfillPlugin } from "esbuild-plugins-node-modules-polyfill";
|
|
26
25
|
var TRANSPILABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
26
|
+
".js",
|
|
27
27
|
".ts",
|
|
28
28
|
".tsx",
|
|
29
29
|
".jsx",
|
|
30
|
-
".
|
|
31
|
-
".
|
|
30
|
+
".mjs",
|
|
31
|
+
".mts"
|
|
32
32
|
]);
|
|
33
33
|
var CSS_EXTENSIONS = /* @__PURE__ */ new Set([".css"]);
|
|
34
34
|
var logger = getLogger(["shovel", "assets"]);
|
|
@@ -101,10 +101,8 @@ function assetsPlugin(options = {}) {
|
|
|
101
101
|
let chunkFiles = [];
|
|
102
102
|
if (needsTranspilation) {
|
|
103
103
|
const defaultPlugins = [
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
process: true,
|
|
107
|
-
buffer: true
|
|
104
|
+
nodeModulesPolyfillPlugin({
|
|
105
|
+
globals: { process: true, Buffer: true }
|
|
108
106
|
})
|
|
109
107
|
];
|
|
110
108
|
const plugins = options.plugins ? [...options.plugins, ...defaultPlugins] : defaultPlugins;
|