@b9g/shovel 0.2.11 → 0.2.13

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 CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  All notable changes to Shovel will be documented in this file.
4
4
 
5
+ ## [0.2.13] - 2026-02-24
6
+
7
+ ### Bug Fixes
8
+
9
+ - **`@b9g/shovel`** - Fixed missing `esbuild-plugins-node-modules-polyfill` dependency causing "Cannot find package" errors at runtime
10
+
11
+ ## [0.2.12] - 2026-02-24
12
+
13
+ ### Bug Fixes
14
+
15
+ - **`@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
16
+ - **`@b9g/shovel`** - Removed `.cts` from asset transpilation list (no CJS in client assets)
17
+ - **`@b9g/shovel`** - Replaced deprecated `@esbuild-plugins/node-modules-polyfill` and `@esbuild-plugins/node-globals-polyfill` with `esbuild-plugins-node-modules-polyfill`, eliminating deprecation warnings and audit vulnerabilities
18
+ - **`@b9g/router`** - Simplified logger middleware to a single response line (`200 GET / (3ms)`) instead of logging both request and response with arrow prefixes
19
+
20
+ ### Improvements
21
+
22
+ - **`create-shovel`** - Added CSS owl selector (`main > * + *`) for consistent vertical spacing, removed inline margin styles
23
+ - **`create-shovel`** - Added interactive counter demo to static-site vanilla template
24
+ - **`create-shovel`** - Added API call demo to full-stack vanilla template
25
+ - **`create-shovel`** - Crank templates now include client-side hydration with `@b9g/assets`
26
+ - **`create-shovel`** - Initial project version is now `0.1.0` instead of `0.0.1`
27
+ - **`create-shovel`** - Bumped generated eslint to `^10.0.0` (fixes audit vulnerabilities in generated projects)
28
+
29
+ ### Dependencies
30
+
31
+ - **`@b9g/crank`** `^0.7.2` → `^0.7.7` (type declarations)
32
+
33
+ ## [0.2.11] - 2026-02-21
34
+
35
+ ### Bug Fixes
36
+
37
+ - **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
38
+
39
+ ### Dependencies
40
+
41
+ - **`@b9g/libuild`** `^0.1.22` → `^0.1.24`
42
+ - **`create-shovel`** `0.1.1` - README and metadata updates
43
+
44
+ ## [0.2.10] - 2026-02-21
45
+
46
+ ### Features
47
+
48
+ - **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)
49
+ - **Multi-file Crank templates** - Crank projects generate `server.{ext}` + `components.{ext}` with `@b9g/router` for route-based request handling
50
+ - **`--framework` and `--jsx` CLI flags** - `create-shovel --framework crank --no-jsx` skips prompts for CI/scripting
51
+ - **ESLint config for Crank projects** - Generates `eslint.config.js` with flat config format
52
+ - **Bun-aware next steps** - Post-scaffold instructions show `bun install` / `bun run develop` when bun platform is selected
53
+ - **Removed redundant `env.d.ts`** - `@b9g/platform` already ships `globals.d.ts`; scaffolded projects now use `"types": ["@b9g/platform/globals"]` in tsconfig instead
54
+
5
55
  ## [0.2.9] - 2026-02-20
6
56
 
7
57
  ### 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-N265AMEN.js");
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-O5LLUOND.js");
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 rendered on the server"
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"] = "^9.0.0";
284
- devDependencies["@eslint/js"] = "^9.0.0";
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 packageJson = {
295
+ const packageJSON = {
295
296
  name: config.name,
296
297
  private: true,
297
- version: "0.0.1",
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(packageJson, null, 2)
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 { Router } from "@b9g/router";
392
- import { logger } from "@b9g/router/middleware";
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 { margin-top: 1rem; padding-left: 1.5rem; }
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 style="margin-top: 1rem;"><a href="/about">About</a></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 style="margin-top: 1rem;"><a href="/about">About</a></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 {Page} from "./components";
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}`]: `const css = \`
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 {Page} from "./components";
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 { Router } from "@b9g/router";
793
- import { logger } from "@b9g/router/middleware";
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 { Router } from "@b9g/router";
861
- import { logger } from "@b9g/router/middleware";
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 { Router } from "@b9g/router";
939
- import { logger } from "@b9g/router/middleware";
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 {Page} from "./components";
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}`]: `const css = \`
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 {Page} from "./components";
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 \u2514\u2500\u2500 components.${ext} # Page components
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.11",
3
+ "version": "0.2.13",
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,19 +24,19 @@
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",
30
+ "esbuild-plugins-node-modules-polyfill": "^1.8.1",
32
31
  "mime": "^4.0.4",
33
32
  "zod": "^3.23.0"
34
33
  },
35
34
  "devDependencies": {
36
35
  "@b9g/assets": "^0.2.1",
37
- "@b9g/crank": "^0.7.2",
36
+ "@b9g/crank": "^0.7.7",
38
37
  "@b9g/libuild": "^0.1.24",
39
- "@b9g/router": "^0.2.2",
38
+ "@b9g/router": "^0.2.3",
39
+ "@eslint/js": "^9.0.0",
40
40
  "@logtape/file": "^2.0.0",
41
41
  "@types/bun": "^1.3.4",
42
42
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -44,6 +44,7 @@
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
  },
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ServerBundler,
3
3
  loadPlatformModule
4
- } from "./chunk-DQDUKJQ4.js";
4
+ } from "./chunk-DRCLXHTF.js";
5
5
  import {
6
6
  findProjectRoot,
7
7
  findWorkspaceRoot
@@ -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 { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill";
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
- ".mts",
31
- ".cts"
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
- NodeModulesPolyfillPlugin(),
105
- NodeGlobalsPolyfillPlugin({
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;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ServerBundler,
3
3
  loadPlatformModule
4
- } from "./chunk-DQDUKJQ4.js";
4
+ } from "./chunk-DRCLXHTF.js";
5
5
  import {
6
6
  DEFAULTS
7
7
  } from "./chunk-7GONPLNW.js";