evikit 2.3.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.woodpecker/test.yaml +26 -0
- package/README.md +39 -0
- package/index.d.ts +19 -0
- package/index.js +68 -100
- package/package.json +1 -1
- package/server.d.ts +2 -0
- package/server.js +37 -12
- package/html.js +0 -49
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
when:
|
|
2
|
+
- event: push
|
|
3
|
+
branch: main
|
|
4
|
+
- event: pull_request
|
|
5
|
+
|
|
6
|
+
steps:
|
|
7
|
+
- name: build
|
|
8
|
+
image: node:24-alpine
|
|
9
|
+
commands:
|
|
10
|
+
- apk add git
|
|
11
|
+
# Are types correct?
|
|
12
|
+
- npm i
|
|
13
|
+
- npm run lint
|
|
14
|
+
- npx tsc
|
|
15
|
+
# Does todo app example build?
|
|
16
|
+
- npm create evikit my-app
|
|
17
|
+
- rm -rf my-app/node_modules/evikit
|
|
18
|
+
- (cd my-app/node_modules; git clone ../.. evikit)
|
|
19
|
+
- (cd my-app; npm run build)
|
|
20
|
+
# Does real-world application pass tests?
|
|
21
|
+
- git clone https://codeberg.org/nykula/lanquiz
|
|
22
|
+
- (cd lanquiz; npm i)
|
|
23
|
+
- rm -rf lanquiz/node_modules/evikit
|
|
24
|
+
- (cd lanquiz/node_modules; git clone ../.. evikit)
|
|
25
|
+
- (cd lanquiz; npm run build)
|
|
26
|
+
- (cd lanquiz; npm test -- --test-concurrency=1)
|
package/README.md
CHANGED
|
@@ -14,9 +14,26 @@ Vite is a good foundation for building a server-rendered monolith SPA with an JS
|
|
|
14
14
|
- Have SEO meta descriptions
|
|
15
15
|
- Generate API documentation with a Swagger overview
|
|
16
16
|
- Send multipart not urlencoded when there's a file
|
|
17
|
+
- Distribute requests between listeners for each CPU core
|
|
17
18
|
|
|
18
19
|
_TODO_ Add more simple language docs near code, don't overwhelm readers.
|
|
19
20
|
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
> 😉 If you are new to Preact and the concept of Virtual DOM, it is a good idea to complete the [Preact tutorial](https://preactjs.com/tutorial/) first.
|
|
24
|
+
|
|
25
|
+
Run the [create-evikit](https://codeberg.org/nykula/create-evikit) initializer to start with a todo app example that you can see on the [EviKit website](https://evikit.js.org/):
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm init evikit my-app
|
|
29
|
+
cd my-app
|
|
30
|
+
npm run dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then open http://localhost:5173/, try editing the example routes and see changes live.
|
|
34
|
+
|
|
35
|
+
The initializer automates some of the steps from this tutorial, such as installing dependencies, adding scripts to your `package.json`, and creating typical files such as `index.html` and `vite.config.js`.
|
|
36
|
+
|
|
20
37
|
## Documentation
|
|
21
38
|
|
|
22
39
|
Let's see how to build a basic poll application using this framework. The application will be a site that lets people view polls, suggest their options and vote.
|
|
@@ -847,6 +864,8 @@ form(
|
|
|
847
864
|
|
|
848
865
|
For the Ukrainian language, which has the `uk` [two-letter language code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes), create an empty file `po/uk.po`. It will use the [gettext](https://www.gnu.org/software/gettext/) format, which is well-known to professional translators.
|
|
849
866
|
|
|
867
|
+
Install the `gettext` package from your distribution repositories or from [here for Windows](https://mlocati.github.io/articles/gettext-iconv-windows.html).
|
|
868
|
+
|
|
850
869
|
Extract translatable strings from code:
|
|
851
870
|
|
|
852
871
|
```sh
|
|
@@ -913,6 +932,26 @@ Change your `index.html` like this:
|
|
|
913
932
|
|
|
914
933
|
Style your app however you like.
|
|
915
934
|
|
|
935
|
+
### Background worker
|
|
936
|
+
|
|
937
|
+
You can run something repeatedly in main process, not in listeners for each CPU core. In development, it will restart whenever you edit api routes.
|
|
938
|
+
|
|
939
|
+
Add to `src/lib/db/index.js`:
|
|
940
|
+
|
|
941
|
+
```js
|
|
942
|
+
import { daemon } from "evikit/server";
|
|
943
|
+
|
|
944
|
+
// ...
|
|
945
|
+
|
|
946
|
+
const unique = crypto.randomUUID();
|
|
947
|
+
daemon(() => {
|
|
948
|
+
const interval = setInterval(() => {
|
|
949
|
+
console.log({ now: Date.now(), unique });
|
|
950
|
+
}, 1000);
|
|
951
|
+
return () => clearInterval(interval);
|
|
952
|
+
});
|
|
953
|
+
```
|
|
954
|
+
|
|
916
955
|
### Testing
|
|
917
956
|
|
|
918
957
|
A practical integration test for EviKit itself is [Lanquiz](https://codeberg.org/nykula/lanquiz), an app to host quizzes in LAN from a laptop, which is built on top of EviKit.
|
package/index.d.ts
CHANGED
|
@@ -83,13 +83,18 @@ export function useQueryClient(): {
|
|
|
83
83
|
export function useStatus(error: unknown): string;
|
|
84
84
|
|
|
85
85
|
export const a: H<"a">;
|
|
86
|
+
export const address: H<"address">;
|
|
86
87
|
export const article: H<"article">;
|
|
88
|
+
export const aside: H<"aside">;
|
|
89
|
+
export const audio: H<"audio">;
|
|
87
90
|
export const b: H<"b">;
|
|
88
91
|
export const blockquote: H<"blockquote">;
|
|
89
92
|
export const br: H<"br">;
|
|
90
93
|
export const button: H<"button">;
|
|
94
|
+
export const canvas: H<"canvas">;
|
|
91
95
|
export const circle: H<"circle">;
|
|
92
96
|
export const code: H<"code">;
|
|
97
|
+
export const del: H<"del">;
|
|
93
98
|
export const details: H<"details">;
|
|
94
99
|
export const dialog: H<"dialog">;
|
|
95
100
|
export const div: H<"div">;
|
|
@@ -106,10 +111,13 @@ export const h4: H<"h4">;
|
|
|
106
111
|
export const h5: H<"h5">;
|
|
107
112
|
export const h6: H<"h6">;
|
|
108
113
|
export const header: H<"header">;
|
|
114
|
+
export const hgroup: H<"hgroup">;
|
|
109
115
|
export const hr: H<"hr">;
|
|
110
116
|
export const i: H<"i">;
|
|
117
|
+
export const iframe: H<"iframe">;
|
|
111
118
|
export const img: H<"img">;
|
|
112
119
|
export const input: H<"input">;
|
|
120
|
+
export const ins: H<"ins">;
|
|
113
121
|
export const label: H<"label">;
|
|
114
122
|
export const legend: H<"legend">;
|
|
115
123
|
export const li: H<"li">;
|
|
@@ -119,16 +127,27 @@ export const ol: H<"ol">;
|
|
|
119
127
|
export const option: H<"option">;
|
|
120
128
|
export const p: H<"p">;
|
|
121
129
|
export const path: H<"path">;
|
|
130
|
+
export const picture: H<"picture">;
|
|
122
131
|
export const pre: H<"pre">;
|
|
123
132
|
export const section: H<"section">;
|
|
124
133
|
export const select: H<"select">;
|
|
125
134
|
export const small: H<"small">;
|
|
135
|
+
export const source: H<"source">;
|
|
126
136
|
export const span: H<"span">;
|
|
127
137
|
export const strong: H<"strong">;
|
|
138
|
+
export const sub: H<"sub">;
|
|
128
139
|
export const summary: H<"summary">;
|
|
140
|
+
export const sup: H<"sup">;
|
|
129
141
|
export const svg: H<"svg">;
|
|
142
|
+
export const table: H<"table">;
|
|
143
|
+
export const tbody: H<"tbody">;
|
|
144
|
+
export const td: H<"td">;
|
|
145
|
+
export const textarea: H<"textarea">;
|
|
146
|
+
export const thead: H<"thead">;
|
|
130
147
|
export const title: H<"title">;
|
|
148
|
+
export const tr: H<"tr">;
|
|
131
149
|
export const ul: H<"ul">;
|
|
150
|
+
export const video: H<"video">;
|
|
132
151
|
|
|
133
152
|
export { flatten, primary, references, relations };
|
|
134
153
|
export { useApi, useEnhance };
|
package/index.js
CHANGED
|
@@ -11,55 +11,7 @@ import { locationStub } from "preact-iso/prerender";
|
|
|
11
11
|
import { useState } from "preact/hooks";
|
|
12
12
|
|
|
13
13
|
import { flatten, primary, references, relations } from "./db.js";
|
|
14
|
-
import {
|
|
15
|
-
a,
|
|
16
|
-
article,
|
|
17
|
-
b,
|
|
18
|
-
blockquote,
|
|
19
|
-
br,
|
|
20
|
-
button,
|
|
21
|
-
circle,
|
|
22
|
-
code,
|
|
23
|
-
details,
|
|
24
|
-
dialog,
|
|
25
|
-
div,
|
|
26
|
-
em,
|
|
27
|
-
fieldset,
|
|
28
|
-
figcaption,
|
|
29
|
-
figure,
|
|
30
|
-
footer,
|
|
31
|
-
form,
|
|
32
|
-
h1,
|
|
33
|
-
h2,
|
|
34
|
-
h3,
|
|
35
|
-
h4,
|
|
36
|
-
h5,
|
|
37
|
-
h6,
|
|
38
|
-
header,
|
|
39
|
-
hr,
|
|
40
|
-
i,
|
|
41
|
-
img,
|
|
42
|
-
input,
|
|
43
|
-
label,
|
|
44
|
-
legend,
|
|
45
|
-
li,
|
|
46
|
-
main,
|
|
47
|
-
nav,
|
|
48
|
-
ol,
|
|
49
|
-
option,
|
|
50
|
-
p,
|
|
51
|
-
path,
|
|
52
|
-
pre,
|
|
53
|
-
section,
|
|
54
|
-
select,
|
|
55
|
-
small,
|
|
56
|
-
span,
|
|
57
|
-
strong,
|
|
58
|
-
summary,
|
|
59
|
-
svg,
|
|
60
|
-
title,
|
|
61
|
-
ul,
|
|
62
|
-
} from "./html.js";
|
|
14
|
+
import { HTMLElement } from "./html-element.js";
|
|
63
15
|
import { getI18n, Po, RawI18nProvider, useI18n } from "./i18n.js";
|
|
64
16
|
import {
|
|
65
17
|
dehydrate,
|
|
@@ -188,56 +140,72 @@ export async function ssr(App, data) {
|
|
|
188
140
|
};
|
|
189
141
|
}
|
|
190
142
|
|
|
191
|
-
export
|
|
143
|
+
export const a = HTMLElement("a");
|
|
144
|
+
export const address = HTMLElement("address");
|
|
145
|
+
export const article = HTMLElement("article");
|
|
146
|
+
export const aside = HTMLElement("aside");
|
|
147
|
+
export const audio = HTMLElement("audio");
|
|
148
|
+
export const b = HTMLElement("b");
|
|
149
|
+
export const blockquote = HTMLElement("blockquote");
|
|
150
|
+
export const br = HTMLElement("br");
|
|
151
|
+
export const button = HTMLElement("button");
|
|
152
|
+
export const canvas = HTMLElement("canvas");
|
|
153
|
+
export const circle = HTMLElement("circle");
|
|
154
|
+
export const code = HTMLElement("code");
|
|
155
|
+
export const del = HTMLElement("del");
|
|
156
|
+
export const details = HTMLElement("details");
|
|
157
|
+
export const dialog = HTMLElement("dialog");
|
|
158
|
+
export const div = HTMLElement("div");
|
|
159
|
+
export const em = HTMLElement("em");
|
|
160
|
+
export const fieldset = HTMLElement("fieldset");
|
|
161
|
+
export const figcaption = HTMLElement("figcaption");
|
|
162
|
+
export const figure = HTMLElement("figure");
|
|
163
|
+
export const footer = HTMLElement("footer");
|
|
164
|
+
export const form = HTMLElement("form");
|
|
165
|
+
export const h1 = HTMLElement("h1");
|
|
166
|
+
export const h2 = HTMLElement("h2");
|
|
167
|
+
export const h3 = HTMLElement("h3");
|
|
168
|
+
export const h4 = HTMLElement("h4");
|
|
169
|
+
export const h5 = HTMLElement("h5");
|
|
170
|
+
export const h6 = HTMLElement("h6");
|
|
171
|
+
export const header = HTMLElement("header");
|
|
172
|
+
export const hgroup = HTMLElement("hgroup");
|
|
173
|
+
export const hr = HTMLElement("hr");
|
|
174
|
+
export const i = HTMLElement("i");
|
|
175
|
+
export const iframe = HTMLElement("iframe");
|
|
176
|
+
export const img = HTMLElement("img");
|
|
177
|
+
export const input = HTMLElement("input");
|
|
178
|
+
export const ins = HTMLElement("ins");
|
|
179
|
+
export const label = HTMLElement("label");
|
|
180
|
+
export const legend = HTMLElement("legend");
|
|
181
|
+
export const li = HTMLElement("li");
|
|
182
|
+
export const main = HTMLElement("main");
|
|
183
|
+
export const nav = HTMLElement("nav");
|
|
184
|
+
export const ol = HTMLElement("ol");
|
|
185
|
+
export const option = HTMLElement("option");
|
|
186
|
+
export const p = HTMLElement("p");
|
|
187
|
+
export const path = HTMLElement("path");
|
|
188
|
+
export const picture = HTMLElement("picture");
|
|
189
|
+
export const pre = HTMLElement("pre");
|
|
190
|
+
export const section = HTMLElement("section");
|
|
191
|
+
export const select = HTMLElement("select");
|
|
192
|
+
export const small = HTMLElement("small");
|
|
193
|
+
export const source = HTMLElement("source");
|
|
194
|
+
export const span = HTMLElement("span");
|
|
195
|
+
export const strong = HTMLElement("strong");
|
|
196
|
+
export const sub = HTMLElement("sub");
|
|
197
|
+
export const summary = HTMLElement("summary");
|
|
198
|
+
export const sup = HTMLElement("sup");
|
|
199
|
+
export const svg = HTMLElement("svg");
|
|
200
|
+
export const table = HTMLElement("table");
|
|
201
|
+
export const tbody = HTMLElement("tbody");
|
|
202
|
+
export const td = HTMLElement("td");
|
|
203
|
+
export const textarea = HTMLElement("textarea");
|
|
204
|
+
export const thead = HTMLElement("thead");
|
|
205
|
+
export const title = HTMLElement("title");
|
|
206
|
+
export const tr = HTMLElement("tr");
|
|
207
|
+
export const ul = HTMLElement("ul");
|
|
208
|
+
export const video = HTMLElement("video");
|
|
192
209
|
|
|
210
|
+
export { flatten, primary, references, relations };
|
|
193
211
|
export { getI18n, useApi, useEnhance, useI18n, useQueryClient, useStatus };
|
|
194
|
-
|
|
195
|
-
export {
|
|
196
|
-
a,
|
|
197
|
-
article,
|
|
198
|
-
b,
|
|
199
|
-
blockquote,
|
|
200
|
-
br,
|
|
201
|
-
button,
|
|
202
|
-
circle,
|
|
203
|
-
code,
|
|
204
|
-
details,
|
|
205
|
-
dialog,
|
|
206
|
-
div,
|
|
207
|
-
em,
|
|
208
|
-
fieldset,
|
|
209
|
-
figcaption,
|
|
210
|
-
figure,
|
|
211
|
-
footer,
|
|
212
|
-
form,
|
|
213
|
-
h1,
|
|
214
|
-
h2,
|
|
215
|
-
h3,
|
|
216
|
-
h4,
|
|
217
|
-
h5,
|
|
218
|
-
h6,
|
|
219
|
-
header,
|
|
220
|
-
hr,
|
|
221
|
-
i,
|
|
222
|
-
img,
|
|
223
|
-
input,
|
|
224
|
-
label,
|
|
225
|
-
legend,
|
|
226
|
-
li,
|
|
227
|
-
main,
|
|
228
|
-
nav,
|
|
229
|
-
ol,
|
|
230
|
-
option,
|
|
231
|
-
p,
|
|
232
|
-
path,
|
|
233
|
-
pre,
|
|
234
|
-
section,
|
|
235
|
-
select,
|
|
236
|
-
small,
|
|
237
|
-
span,
|
|
238
|
-
strong,
|
|
239
|
-
summary,
|
|
240
|
-
svg,
|
|
241
|
-
title,
|
|
242
|
-
ul,
|
|
243
|
-
};
|
package/package.json
CHANGED
package/server.d.ts
CHANGED
package/server.js
CHANGED
|
@@ -2,7 +2,9 @@ import { Busboy } from "@fastify/busboy";
|
|
|
2
2
|
import { toJsonSchema } from "@valibot/to-json-schema";
|
|
3
3
|
import acceptLanguage from "accept-language";
|
|
4
4
|
import { po } from "gettext-parser";
|
|
5
|
+
import cluster from "node:cluster";
|
|
5
6
|
import { readFile } from "node:fs/promises";
|
|
7
|
+
import { availableParallelism } from "node:os";
|
|
6
8
|
import { h } from "preact";
|
|
7
9
|
import { renderToString } from "preact-render-to-string";
|
|
8
10
|
import {
|
|
@@ -18,6 +20,21 @@ import {
|
|
|
18
20
|
|
|
19
21
|
import { Po } from "./i18n.js";
|
|
20
22
|
|
|
23
|
+
/** @param {() => () => void} callback */
|
|
24
|
+
export function daemon(callback) {
|
|
25
|
+
if (
|
|
26
|
+
(process.env["npm_command"] == "start" && cluster.isPrimary) ||
|
|
27
|
+
process.env["npm_lifecycle_event"] == "dev"
|
|
28
|
+
) {
|
|
29
|
+
/** @type {any} */
|
|
30
|
+
const self = globalThis;
|
|
31
|
+
if (self.clearDaemon) {
|
|
32
|
+
self.clearDaemon();
|
|
33
|
+
}
|
|
34
|
+
self.clearDaemon = callback();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
21
38
|
/** @type {import("./server-generic.js").ServerEndpoint} */
|
|
22
39
|
export function endpoint({ input, outputs }, handler) {
|
|
23
40
|
const arrayOf = ArrayOf(input);
|
|
@@ -191,18 +208,26 @@ export function json(output) {
|
|
|
191
208
|
/** @param {{ listen: (port: number, callback: () => void) => { close(): void, on(event: "error", callback: (error: unknown) => void ): void } }} app */
|
|
192
209
|
export function listen(app) {
|
|
193
210
|
const port = Number(process.env["PORT"]) || 5173;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
211
|
+
if (cluster.isPrimary && availableParallelism() > 1) {
|
|
212
|
+
for (let i = 0; i < availableParallelism(); i++) {
|
|
213
|
+
cluster.fork({ i });
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
const server = app.listen(port, () => {
|
|
217
|
+
if (cluster.isPrimary || process.env["i"] == "0") {
|
|
218
|
+
console.log(
|
|
219
|
+
`Server started at ${process.env["ORIGIN"] || `http://localhost:${port}`}`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
server.on("error", (error) => {
|
|
224
|
+
console.error(error);
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
});
|
|
227
|
+
process.on("SIGTERM", () => {
|
|
228
|
+
server.close();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
206
231
|
}
|
|
207
232
|
|
|
208
233
|
/** @param {{ html: string, prerender({ lang, url }: { lang: string, url: string }): Promise<{ data: {}, head: { elements: Set<{ props: { [k: string]: string }, type: string }>, lang: string, title: string }, html: string }> }} props */
|
package/html.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { HTMLElement } from "./html-element.js";
|
|
2
|
-
|
|
3
|
-
export const a = HTMLElement("a");
|
|
4
|
-
export const article = HTMLElement("article");
|
|
5
|
-
export const b = HTMLElement("b");
|
|
6
|
-
export const blockquote = HTMLElement("blockquote");
|
|
7
|
-
export const br = HTMLElement("br");
|
|
8
|
-
export const button = HTMLElement("button");
|
|
9
|
-
export const circle = HTMLElement("circle");
|
|
10
|
-
export const code = HTMLElement("code");
|
|
11
|
-
export const details = HTMLElement("details");
|
|
12
|
-
export const dialog = HTMLElement("dialog");
|
|
13
|
-
export const div = HTMLElement("div");
|
|
14
|
-
export const em = HTMLElement("em");
|
|
15
|
-
export const fieldset = HTMLElement("fieldset");
|
|
16
|
-
export const figcaption = HTMLElement("figcaption");
|
|
17
|
-
export const figure = HTMLElement("figure");
|
|
18
|
-
export const footer = HTMLElement("footer");
|
|
19
|
-
export const form = HTMLElement("form");
|
|
20
|
-
export const header = HTMLElement("header");
|
|
21
|
-
export const h1 = HTMLElement("h1");
|
|
22
|
-
export const h2 = HTMLElement("h2");
|
|
23
|
-
export const h3 = HTMLElement("h3");
|
|
24
|
-
export const h4 = HTMLElement("h4");
|
|
25
|
-
export const h5 = HTMLElement("h5");
|
|
26
|
-
export const h6 = HTMLElement("h6");
|
|
27
|
-
export const hr = HTMLElement("hr");
|
|
28
|
-
export const i = HTMLElement("i");
|
|
29
|
-
export const img = HTMLElement("img");
|
|
30
|
-
export const input = HTMLElement("input");
|
|
31
|
-
export const label = HTMLElement("label");
|
|
32
|
-
export const legend = HTMLElement("legend");
|
|
33
|
-
export const li = HTMLElement("li");
|
|
34
|
-
export const main = HTMLElement("main");
|
|
35
|
-
export const nav = HTMLElement("nav");
|
|
36
|
-
export const ol = HTMLElement("ol");
|
|
37
|
-
export const option = HTMLElement("option");
|
|
38
|
-
export const p = HTMLElement("p");
|
|
39
|
-
export const path = HTMLElement("path");
|
|
40
|
-
export const pre = HTMLElement("pre");
|
|
41
|
-
export const section = HTMLElement("section");
|
|
42
|
-
export const select = HTMLElement("select");
|
|
43
|
-
export const small = HTMLElement("small");
|
|
44
|
-
export const span = HTMLElement("span");
|
|
45
|
-
export const strong = HTMLElement("strong");
|
|
46
|
-
export const summary = HTMLElement("summary");
|
|
47
|
-
export const svg = HTMLElement("svg");
|
|
48
|
-
export const title = HTMLElement("title");
|
|
49
|
-
export const ul = HTMLElement("ul");
|