evikit 2.14.1 → 2.14.3

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.
@@ -24,13 +24,19 @@ steps:
24
24
  - (cd lanquiz/node_modules; git clone ../.. evikit)
25
25
  - (cd lanquiz; npm run build)
26
26
  - (cd lanquiz; npm test -- --test-concurrency=1)
27
- # Do other real-world applications build? 1/2
27
+ # Do other real-world applications build? 1/3
28
28
  - git clone https://codeberg.org/nykula/mobhub
29
29
  - (cd mobhub; npm i)
30
30
  - rm -rf mobhub/node_modules/evikit
31
31
  - (cd mobhub/node_modules; git clone ../.. evikit)
32
32
  - (cd mobhub; npm run build)
33
- # Do other real-world applications build? 2/2
33
+ # Do other real-world applications build? 2/3
34
+ - git clone https://codeberg.org/nykula/powerup
35
+ - (cd powerup; npm i)
36
+ - rm -rf powerup/node_modules/evikit
37
+ - (cd powerup/node_modules; git clone ../.. evikit)
38
+ - (cd powerup; npm run build)
39
+ # Do other real-world applications build? 3/3
34
40
  - git clone https://codeberg.org/nykula/yoself
35
41
  - (cd yoself; npm i)
36
42
  - rm -rf yoself/node_modules/evikit
package/README.md CHANGED
@@ -335,7 +335,7 @@ PORT=8000
335
335
  Run it:
336
336
 
337
337
  ```sh
338
- npx cross-env NODE_ENV=production node --env-file=.env index.js
338
+ npx cross-env NODE_ENV=production node --env-file-if-exists=.env index.js
339
339
  ```
340
340
 
341
341
  You can add this command (without npx) as `start` script to your `package.json`.
@@ -934,6 +934,7 @@ There's a stylesheet with a CSS reset and a small number of utility classes name
934
934
 
935
935
  - [EviKit's own landing page](https://codeberg.org/nykula/create-evikit)
936
936
  - [Lanquiz trivia game](https://codeberg.org/nykula/lanquiz)
937
+ - [Powerup electricity availability check](https://codeberg.org/nykula/powerup)
937
938
  - [Yoself container monitoring tool](https://codeberg.org/nykula/yoself)
938
939
 
939
940
  The styles are written from scratch and aren't meant to replicate Tailwind exactly, just make apps look acceptable. In particular, they use a different palette, [X11 colors](https://www.w3.org/TR/css-color-3/) as provided by CSS itself; think late 90s, not web3.
@@ -1028,7 +1029,7 @@ Component:
1028
1029
  - btn btn-ghost btn-disabled
1029
1030
  - fieldset fieldset-legend label input
1030
1031
  - link
1031
- - lg:table table-zebra
1032
+ - table lg:table table-zebra
1032
1033
 
1033
1034
  Color:
1034
1035
 
@@ -1149,11 +1150,11 @@ The following commands should work now:
1149
1150
 
1150
1151
  ```sh
1151
1152
  # Run dev server with watch mode.
1152
- node --env-file=.env index.js dev
1153
+ node --env-file-if-exists=.env index.js dev
1153
1154
 
1154
1155
  # Build client for production.
1155
1156
  npx tsc
1156
- node --env-file=.env index.js build
1157
+ node --env-file-if-exists=.env index.js build
1157
1158
  ```
1158
1159
 
1159
1160
  You can change the `dev` and `build` scripts in your `package.json` accordingly.
package/index.css CHANGED
@@ -30,6 +30,107 @@ summary {
30
30
  list-style: revert;
31
31
  }
32
32
 
33
+ /* Component. */
34
+
35
+ .badge {
36
+ border-radius: 0.5rem;
37
+ display: inline-flex;
38
+ gap: 0.5rem;
39
+ font-size: 0.875rem;
40
+ padding: 0.0625rem 0.75rem;
41
+ }
42
+
43
+ .badge-info {
44
+ background-color: color-mix(in srgb, darkturquoise 6.25%, transparent);
45
+ }
46
+
47
+ .btn {
48
+ background-color: rgba(0, 0, 0, 0.03125);
49
+ border: 1px solid rgba(0, 0, 0, 0.0625);
50
+ border-radius: 0.25rem;
51
+ cursor: pointer;
52
+ display: inline-block;
53
+ font-size: 0.875rem;
54
+ font-weight: 600;
55
+ padding: 0.5rem 1rem;
56
+ }
57
+
58
+ .btn:focus,
59
+ .btn:hover {
60
+ background-color: rgba(0, 0, 0, 0.125);
61
+ }
62
+
63
+ .btn-ghost:not(:focus):not(:hover) {
64
+ background-color: transparent;
65
+ border-color: transparent;
66
+ }
67
+
68
+ .btn-disabled {
69
+ background-color: rgba(0, 0, 0, 0.03125);
70
+ border-color: rgba(0, 0, 0, 0.03125);
71
+ }
72
+
73
+ .fieldset {
74
+ border: none;
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 0.25rem;
78
+ padding-bottom: 0.25rem;
79
+ padding-top: 0.25rem;
80
+ }
81
+
82
+ .fieldset-legend {
83
+ font-size: 0.75rem;
84
+ font-weight: bold;
85
+ }
86
+
87
+ .label {
88
+ display: table;
89
+ font-size: 0.75rem;
90
+ }
91
+
92
+ .input {
93
+ border: 1px solid rgba(0, 0, 0, 0.25);
94
+ border-radius: 0.25rem;
95
+ font-size: 0.875rem;
96
+ max-width: 100%;
97
+ padding: 0.5rem 0.75rem;
98
+ width: 20rem;
99
+ }
100
+
101
+ .link {
102
+ text-decoration: underline;
103
+ }
104
+
105
+ .table,
106
+ .lg\:table {
107
+ border-collapse: collapse;
108
+ font-size: 0.875rem;
109
+ width: 100%;
110
+ }
111
+
112
+ .table thead th,
113
+ .lg\:table thead th {
114
+ font-weight: 700;
115
+ opacity: 0.5;
116
+ }
117
+
118
+ .table tr > *,
119
+ .lg\:table tr > * {
120
+ padding: 0.75rem 1rem;
121
+ }
122
+
123
+ .table-zebra tr:nth-child(even) {
124
+ background-color: rgba(0, 0, 0, 0.025);
125
+ border-bottom: 1px solid rgba(0, 0, 0, 0.075);
126
+ }
127
+
128
+ @media (prefers-color-scheme: dark) {
129
+ .table-zebra tr:nth-child(even) {
130
+ background-color: rgba(255, 255, 255, 0.05);
131
+ }
132
+ }
133
+
33
134
  /* Margin. */
34
135
 
35
136
  .m-8 {
@@ -87,6 +188,11 @@ summary {
87
188
  margin-right: 0;
88
189
  }
89
190
 
191
+ .-mx-4 {
192
+ margin-left: -1rem;
193
+ margin-right: -1rem;
194
+ }
195
+
90
196
  .mx-auto {
91
197
  margin-left: auto;
92
198
  margin-right: auto;
@@ -409,6 +515,16 @@ summary {
409
515
  grid-row: span 2;
410
516
  }
411
517
 
518
+ .table {
519
+ display: table;
520
+ }
521
+
522
+ @media (min-width: 992px) {
523
+ .lg\:table {
524
+ display: table;
525
+ }
526
+ }
527
+
412
528
  .text-center {
413
529
  text-align: center;
414
530
  }
@@ -479,109 +595,6 @@ summary {
479
595
  }
480
596
  }
481
597
 
482
- /* Component. */
483
-
484
- .badge {
485
- border-radius: 0.5rem;
486
- display: inline-flex;
487
- gap: 0.5rem;
488
- font-size: 0.875rem;
489
- padding: 0.0625rem 0.75rem;
490
- }
491
-
492
- .badge-info {
493
- background-color: color-mix(in srgb, darkturquoise 6.25%, transparent);
494
- }
495
-
496
- .btn {
497
- background-color: rgba(0, 0, 0, 0.03125);
498
- border: 1px solid rgba(0, 0, 0, 0.0625);
499
- border-radius: 0.25rem;
500
- cursor: pointer;
501
- font-size: 0.875rem;
502
- font-weight: 600;
503
- padding: 0.5rem 1rem;
504
- }
505
-
506
- .btn:focus,
507
- .btn:hover {
508
- background-color: rgba(0, 0, 0, 0.125);
509
- }
510
-
511
- .btn-ghost:not(:focus):not(:hover) {
512
- background-color: transparent;
513
- border-color: transparent;
514
- }
515
-
516
- .btn-disabled {
517
- background-color: rgba(0, 0, 0, 0.03125);
518
- border-color: rgba(0, 0, 0, 0.03125);
519
- }
520
-
521
- .fieldset {
522
- border: none;
523
- display: flex;
524
- flex-direction: column;
525
- gap: 0.25rem;
526
- padding-bottom: 0.25rem;
527
- padding-top: 0.25rem;
528
- }
529
-
530
- .fieldset-legend {
531
- font-size: 0.75rem;
532
- font-weight: bold;
533
- }
534
-
535
- .label {
536
- display: table;
537
- font-size: 0.75rem;
538
- }
539
-
540
- .input {
541
- border: 1px solid rgba(0, 0, 0, 0.25);
542
- border-radius: 0.25rem;
543
- font-size: 0.875rem;
544
- max-width: 100%;
545
- padding: 0.5rem 0.75rem;
546
- width: 20rem;
547
- }
548
-
549
- .link {
550
- text-decoration: underline;
551
- }
552
-
553
- .lg\:table {
554
- border-collapse: collapse;
555
- font-size: 0.875rem;
556
- width: 100%;
557
- }
558
-
559
- @media (min-width: 992px) {
560
- .lg\:table {
561
- display: table;
562
- }
563
- }
564
-
565
- .lg\:table thead th {
566
- font-weight: 700;
567
- opacity: 0.5;
568
- }
569
-
570
- .lg\:table tr > * {
571
- padding: 0.75rem;
572
- }
573
-
574
- .table-zebra tr:nth-child(even) {
575
- background-color: rgba(0, 0, 0, 0.025);
576
- border-bottom: 1px solid rgba(0, 0, 0, 0.075);
577
- }
578
-
579
- @media (prefers-color-scheme: dark) {
580
- .table-zebra tr:nth-child(even) {
581
- background-color: rgba(255, 255, 255, 0.05);
582
- }
583
- }
584
-
585
598
  /* Color. */
586
599
 
587
600
  .opacity-0 {
package/package.json CHANGED
@@ -1,62 +1,62 @@
1
- {
2
- "name": "evikit",
3
- "description": "Preact/SQLite SSR framework for small web apps",
4
- "version": "2.14.1",
5
- "type": "module",
6
- "exports": {
7
- ".": "./index.js",
8
- "./css": "./index.css",
9
- "./db": "./db.js",
10
- "./server": "./server.js",
11
- "./sqlite": "./sqlite.js"
12
- },
13
- "bin": {
14
- "evikit-extract": "bin/evikit-extract.js"
15
- },
16
- "scripts": {
17
- "build": "tsc",
18
- "format": "prettier --write . && eslint --fix .",
19
- "lint": "prettier --check . && eslint ."
20
- },
21
- "author": "Denys Nykula <nykula@ukr.net>",
22
- "license": "MIT",
23
- "dependencies": {
24
- "@fastify/busboy": "^3.2.0",
25
- "@types/gettext-parser": "^9.0.0",
26
- "@types/gettext.js": "^2.0.0",
27
- "@valibot/to-json-schema": "^1.7.0",
28
- "env-paths": "^4.0.0",
29
- "gettext-parser": "^9.0.2",
30
- "gettext.js": "^2.0.3",
31
- "preact-render-to-string": "^6.7.0"
32
- },
33
- "peerDependencies": {
34
- "accept-language": "^3.0.20",
35
- "esbuild": "^0.28.2",
36
- "hoofd": "^1.7.3",
37
- "preact": "^10.29.2",
38
- "preact-iso": "^2.12.0",
39
- "valibot": "^1.4.0"
40
- },
41
- "peerDependenciesMeta": {
42
- "esbuild": {
43
- "optional": true
44
- }
45
- },
46
- "devDependencies": {
47
- "@eslint/compat": "^2.1.0",
48
- "@eslint/js": "^10.0.1",
49
- "@types/node": "^24.13.3",
50
- "esbuild": "^0.28.2",
51
- "eslint": "^10.4.0",
52
- "eslint-config-prettier": "^10.1.8",
53
- "eslint-plugin-perfectionist": "^5.9.0",
54
- "globals": "^17.6.0",
55
- "prettier": "^3.8.3",
56
- "typescript": "^6.0.3",
57
- "typescript-eslint": "^8.59.4"
58
- },
59
- "allowScripts": {
60
- "esbuild": true
61
- }
62
- }
1
+ {
2
+ "name": "evikit",
3
+ "description": "Preact/SQLite SSR framework for small web apps",
4
+ "version": "2.14.3",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./index.js",
8
+ "./css": "./index.css",
9
+ "./db": "./db.js",
10
+ "./server": "./server.js",
11
+ "./sqlite": "./sqlite.js"
12
+ },
13
+ "bin": {
14
+ "evikit-extract": "bin/evikit-extract.js"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "format": "prettier --write . && eslint --fix .",
19
+ "lint": "prettier --check . && eslint ."
20
+ },
21
+ "author": "Denys Nykula <nykula@ukr.net>",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@fastify/busboy": "^3.2.0",
25
+ "@types/gettext-parser": "^9.0.0",
26
+ "@types/gettext.js": "^2.0.0",
27
+ "@valibot/to-json-schema": "^1.7.0",
28
+ "env-paths": "^4.0.0",
29
+ "gettext-parser": "^9.0.2",
30
+ "gettext.js": "^2.0.3",
31
+ "preact-render-to-string": "^6.7.0"
32
+ },
33
+ "peerDependencies": {
34
+ "accept-language": "^3.0.20",
35
+ "esbuild": "^0.28.2",
36
+ "hoofd": "^1.7.3",
37
+ "preact": "^10.29.2",
38
+ "preact-iso": "^2.12.0",
39
+ "valibot": "^1.4.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "esbuild": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@eslint/compat": "^2.1.0",
48
+ "@eslint/js": "^10.0.1",
49
+ "@types/node": "^24.13.3",
50
+ "esbuild": "^0.28.2",
51
+ "eslint": "^10.4.0",
52
+ "eslint-config-prettier": "^10.1.8",
53
+ "eslint-plugin-perfectionist": "^5.9.0",
54
+ "globals": "^17.6.0",
55
+ "prettier": "^3.8.3",
56
+ "typescript": "^6.0.3",
57
+ "typescript-eslint": "^8.59.4"
58
+ },
59
+ "allowScripts": {
60
+ "esbuild": true
61
+ }
62
+ }
package/server.js CHANGED
@@ -213,8 +213,6 @@ let esbuild = false;
213
213
  /** @param {{ listen: (port: number, callback: () => void) => { close(): void, on(event: "error", callback: (error: unknown) => void ): void } }} app */
214
214
  export async function listen(app) {
215
215
  if (esbuild && cluster.isPrimary) {
216
- let esbuildPort = 0;
217
-
218
216
  async function makeHtml() {
219
217
  await writeFile(
220
218
  "./dist/index.html",
@@ -228,10 +226,22 @@ export async function listen(app) {
228
226
  '<script src="/src/prerender.js?' +
229
227
  (await sha256("./dist/src/prerender.js")) +
230
228
  '"></script>' +
231
- (esbuildPort
232
- ? '<script>new EventSource("http://localhost:' +
233
- esbuildPort +
234
- '/esbuild").addEventListener("change", () => location.reload());</script>'
229
+ (process.argv[2] == "dev"
230
+ ? `<script>
231
+ (async () => {
232
+ let nav;
233
+ addEventListener("beforeunload", () => {
234
+ nav = true;
235
+ });
236
+ try {
237
+ await (await fetch("/never-end")).text();
238
+ } catch (error) {
239
+ if (!nav) {
240
+ location.reload();
241
+ }
242
+ }
243
+ })();
244
+ </script>`
235
245
  : ""),
236
246
  ),
237
247
  );
@@ -251,7 +261,7 @@ export async function listen(app) {
251
261
  return;
252
262
  }
253
263
 
254
- if (process.argv[2] == "dev" && cluster.isPrimary) {
264
+ if (process.argv[2] == "dev") {
255
265
  const { context } = await import("esbuild");
256
266
 
257
267
  const { watch: watchApi } = await context({
@@ -263,26 +273,19 @@ export async function listen(app) {
263
273
  });
264
274
  await watchApi();
265
275
 
266
- const { serve: servePrerender, watch: watchPrerender } = await context({
276
+ const { watch: watchPrerender } = await context({
267
277
  bundle: true,
268
278
  entryPoints: ["./index.css", "./src/prerender.js"],
269
279
  outdir: "./dist",
270
280
  });
271
281
  await watchPrerender();
272
- ({ port: esbuildPort } = await servePrerender({
273
- cors: {
274
- origin:
275
- process.env["ORIGIN"] ||
276
- `http://localhost:${Number(process.env["PORT"]) || 5173}`,
277
- },
278
- }));
279
282
 
280
- let subprocess = cluster.fork();
281
283
  let lastHash =
282
284
  (await sha256("./dist/api.js")) +
283
285
  (await sha256("./dist/index.css")) +
284
286
  (await sha256("./dist/src/prerender.js"));
285
287
  await makeHtml();
288
+ let subprocess = cluster.fork();
286
289
  setInterval(async () => {
287
290
  const hash =
288
291
  (await sha256("./dist/api.js")) +
@@ -294,7 +297,7 @@ export async function listen(app) {
294
297
  subprocess.kill();
295
298
  subprocess = cluster.fork();
296
299
  }
297
- }, 1000);
300
+ }, 100);
298
301
  }
299
302
 
300
303
  await makeHtml();
@@ -321,7 +324,11 @@ export async function listen(app) {
321
324
  }
322
325
  });
323
326
  }
324
- } else if ((esbuild && !process.argv[2]) || cluster.isPrimary) {
327
+ } else if (
328
+ (esbuild && process.argv[2] == "dev" && !cluster.isPrimary) ||
329
+ (esbuild && !process.argv[2] && cluster.isPrimary) ||
330
+ (!esbuild && cluster.isPrimary)
331
+ ) {
325
332
  const server = app.listen(port, () => {
326
333
  if (cluster.isPrimary || process.env["i"] == "0") {
327
334
  console.log(
@@ -349,8 +356,17 @@ export function production({ html, prerender }) {
349
356
  * @param {import("node:http").ServerResponse<import("node:http").IncomingMessage>} res
350
357
  */
351
358
  return async function onRequest(req, res) {
359
+ if (esbuild && process.argv[2] == "dev" && req.url == "/never-end") {
360
+ res.write(".");
361
+ return;
362
+ }
352
363
  try {
353
364
  if (!html) {
365
+ if (!existsSync("./dist/index.html")) {
366
+ res.statusCode = 502;
367
+ res.end("502");
368
+ return;
369
+ }
354
370
  html = await readFile("./dist/index.html", "utf-8");
355
371
  }
356
372
  const rendered = await prerender({