evikit 2.12.0 → 2.14.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/README.md +156 -2
- package/index.css +717 -0
- package/package.json +13 -3
- package/real-world-classes.js +55 -0
- package/server.d.ts +1 -1
- package/server.js +128 -19
package/README.md
CHANGED
|
@@ -59,8 +59,10 @@ Our language will be standard modern JavaScript as supported by Node.js directly
|
|
|
59
59
|
- [File upload](#file-upload)
|
|
60
60
|
- [Translation](#translation)
|
|
61
61
|
- [Styling](#styling)
|
|
62
|
+
- [EviKit CSS](#evikit-css)
|
|
62
63
|
- _TODO_ [Testing](#testing)
|
|
63
64
|
- [Linting](#linting)
|
|
65
|
+
- [esbuild](#esbuild)
|
|
64
66
|
|
|
65
67
|
### Create project
|
|
66
68
|
|
|
@@ -288,7 +290,7 @@ The following commands should work now:
|
|
|
288
290
|
# Run dev server with hot reload for Preact and full restart on API change.
|
|
289
291
|
npx vite dev
|
|
290
292
|
|
|
291
|
-
# Build
|
|
293
|
+
# Build client for production.
|
|
292
294
|
npx tsc
|
|
293
295
|
npx vite build
|
|
294
296
|
```
|
|
@@ -880,7 +882,9 @@ export function acceptLanguages() {
|
|
|
880
882
|
|
|
881
883
|
### Styling
|
|
882
884
|
|
|
883
|
-
|
|
885
|
+
#### Tailwind and daisyUI
|
|
886
|
+
|
|
887
|
+
I've had good experience styling apps with [daisyUI](https://daisyui.com/). Let's see how to set it up.
|
|
884
888
|
|
|
885
889
|
```sh
|
|
886
890
|
npm i -D @tailwindcss/vite daisyui
|
|
@@ -924,6 +928,126 @@ Change your `index.html` like this:
|
|
|
924
928
|
|
|
925
929
|
Style your app however you like.
|
|
926
930
|
|
|
931
|
+
#### EviKit CSS
|
|
932
|
+
|
|
933
|
+
There's a stylesheet with a CSS reset and a small number of utility classes named like Tailwind and daisyUI. I use it to build my EviKit-based apps [with esbuild](#esbuild) and no plugins:
|
|
934
|
+
|
|
935
|
+
- [EviKit's own landing page](https://codeberg.org/nykula/create-evikit)
|
|
936
|
+
- [Lanquiz trivia game](https://codeberg.org/nykula/lanquiz)
|
|
937
|
+
- [Yoself container monitoring tool](https://codeberg.org/nykula/yoself)
|
|
938
|
+
|
|
939
|
+
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.
|
|
940
|
+
|
|
941
|
+
To try them, import EviKit in `index.css`:
|
|
942
|
+
|
|
943
|
+
```css
|
|
944
|
+
@import "evikit/css";
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
And change your `index.html` like in the above instructions for Tailwind.
|
|
948
|
+
|
|
949
|
+
Margin:
|
|
950
|
+
|
|
951
|
+
- m-8
|
|
952
|
+
- mb-2 mb-4 sm:mb-4 -mb-4 mb-6 mb-8
|
|
953
|
+
- mt-0 mt-2 mt-4 -mt-4 mt-6
|
|
954
|
+
- mx-0 mx-auto
|
|
955
|
+
- my-2 my-4 my-8
|
|
956
|
+
|
|
957
|
+
Padding:
|
|
958
|
+
|
|
959
|
+
- p-4 p-6 p-8 lg:p-8
|
|
960
|
+
- pb-8
|
|
961
|
+
|
|
962
|
+
Gap:
|
|
963
|
+
|
|
964
|
+
- gap-2 gap-4 gap-8
|
|
965
|
+
- gap-x-4
|
|
966
|
+
- gap-y-6
|
|
967
|
+
|
|
968
|
+
Width:
|
|
969
|
+
|
|
970
|
+
- max-w-full sm:max-w-sm max-w-lg max-w-2xl max-w-5xl
|
|
971
|
+
- w-32 w-auto w-fit w-full
|
|
972
|
+
|
|
973
|
+
Height:
|
|
974
|
+
|
|
975
|
+
- h-auto
|
|
976
|
+
- min-h-screen
|
|
977
|
+
|
|
978
|
+
Font:
|
|
979
|
+
|
|
980
|
+
- font-thin font-semibold font-bold
|
|
981
|
+
- tabular-nums
|
|
982
|
+
- text-base
|
|
983
|
+
- text-sm lg:text-sm
|
|
984
|
+
- text-lg
|
|
985
|
+
- text-xl lg:text-xl
|
|
986
|
+
- text-2xl
|
|
987
|
+
- lg:text-3xl
|
|
988
|
+
- text-4xl lg:text-4xl
|
|
989
|
+
- tracking-wider
|
|
990
|
+
- uppercase
|
|
991
|
+
- whitespace-nowrap
|
|
992
|
+
|
|
993
|
+
Image:
|
|
994
|
+
|
|
995
|
+
- bg-center
|
|
996
|
+
- bg-cover
|
|
997
|
+
- object-contain
|
|
998
|
+
|
|
999
|
+
Layout:
|
|
1000
|
+
|
|
1001
|
+
- absolute right-2 top-2
|
|
1002
|
+
- contents
|
|
1003
|
+
- flex flex-col flex-wrap
|
|
1004
|
+
- grid
|
|
1005
|
+
- grid-cols-1
|
|
1006
|
+
- grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-2
|
|
1007
|
+
- lg:grid-cols-3
|
|
1008
|
+
- lg:grid-cols-4
|
|
1009
|
+
- 2xl:grid-cols-6
|
|
1010
|
+
- hidden lg:hidden
|
|
1011
|
+
- items-center justify-center
|
|
1012
|
+
- overflow-auto overflow-hidden
|
|
1013
|
+
- row-span-2
|
|
1014
|
+
- text-center sm:text-center md:text-center
|
|
1015
|
+
|
|
1016
|
+
Border:
|
|
1017
|
+
|
|
1018
|
+
- border lg:border
|
|
1019
|
+
- border-b
|
|
1020
|
+
- !border-none lg:border-none
|
|
1021
|
+
- rounded-lg lg:rounded-lg
|
|
1022
|
+
- rounded-xl
|
|
1023
|
+
- shadow-sm lg:shadow-sm
|
|
1024
|
+
|
|
1025
|
+
Component:
|
|
1026
|
+
|
|
1027
|
+
- badge badge-info
|
|
1028
|
+
- btn btn-ghost btn-disabled
|
|
1029
|
+
- fieldset fieldset-legend label input
|
|
1030
|
+
- link
|
|
1031
|
+
- lg:table table-zebra
|
|
1032
|
+
|
|
1033
|
+
Color:
|
|
1034
|
+
|
|
1035
|
+
- opacity-0 opacity-50
|
|
1036
|
+
- bg-gray-50 bg-gray-100
|
|
1037
|
+
- !bg-transparent
|
|
1038
|
+
- border-gray-100
|
|
1039
|
+
- text-green-400
|
|
1040
|
+
- text-orange-400
|
|
1041
|
+
- text-red-400
|
|
1042
|
+
- fill-amber-300/[50%] stroke-amber-500 text-amber-400
|
|
1043
|
+
- fill-cyan-300/[50%] stroke-cyan-500 text-cyan-400
|
|
1044
|
+
- fill-fuchsia-300/[50%] stroke-fuchsia-500 text-fuchsia-400
|
|
1045
|
+
- fill-indigo-300/[50%] stroke-indigo-500 text-indigo-400
|
|
1046
|
+
- fill-lime-300/[50%] stroke-lime-500 text-lime-400
|
|
1047
|
+
- fill-purple-300/[50%] stroke-purple-500 text-purple-400
|
|
1048
|
+
- fill-teal-300/[50%] stroke-teal-500 text-teal-400
|
|
1049
|
+
- fill-yellow-300/[50%] stroke-yellow-500 text-yellow-400
|
|
1050
|
+
|
|
927
1051
|
### Background worker
|
|
928
1052
|
|
|
929
1053
|
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.
|
|
@@ -1004,6 +1128,36 @@ npx eslint .
|
|
|
1004
1128
|
|
|
1005
1129
|
You can add these commands (without npx) as `format` and `lint` scripts in your `package.json`.
|
|
1006
1130
|
|
|
1131
|
+
## esbuild
|
|
1132
|
+
|
|
1133
|
+
If you don't need Tailwind (e.g. [EviKit CSS](#evikit-css) is enough for you) or bundle analyzer, and are ok with full-page refresh instead of hot module replacement, you can have EviKit use [esbuild](https://esbuild.github.io/) as a smaller alternative to Vite.
|
|
1134
|
+
|
|
1135
|
+
```sh
|
|
1136
|
+
npm i -D esbuild
|
|
1137
|
+
npm install-scripts approve esbuild
|
|
1138
|
+
npm rm @preact/preset-vite vite vite-bundle-analyzer
|
|
1139
|
+
rm -f vite.config.js
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
Change the `production` call in your `index.js`:
|
|
1143
|
+
|
|
1144
|
+
```js
|
|
1145
|
+
app.use(production({ prerender }));
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
The following commands should work now:
|
|
1149
|
+
|
|
1150
|
+
```sh
|
|
1151
|
+
# Run dev server with watch mode.
|
|
1152
|
+
node --env-file=.env index.js dev
|
|
1153
|
+
|
|
1154
|
+
# Build client for production.
|
|
1155
|
+
npx tsc
|
|
1156
|
+
node --env-file=.env index.js build
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
You can change the `dev` and `build` scripts in your `package.json` accordingly.
|
|
1160
|
+
|
|
1007
1161
|
## License
|
|
1008
1162
|
|
|
1009
1163
|
SPDX-License-Identifier: MIT
|
package/index.css
ADDED
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
* {
|
|
2
|
+
color: inherit;
|
|
3
|
+
font: inherit;
|
|
4
|
+
list-style: none;
|
|
5
|
+
margin: 0;
|
|
6
|
+
padding: 0;
|
|
7
|
+
text-align: inherit;
|
|
8
|
+
text-decoration: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
html {
|
|
12
|
+
font-family: system-ui, sans-serif;
|
|
13
|
+
line-height: 1.5rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
b {
|
|
17
|
+
font-weight: bold;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
code {
|
|
21
|
+
font-family: monospace;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pre {
|
|
25
|
+
font-family: monospace;
|
|
26
|
+
font-size: 1rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
summary {
|
|
30
|
+
list-style: revert;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Margin. */
|
|
34
|
+
|
|
35
|
+
.m-8 {
|
|
36
|
+
margin: 2rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.mb-2 {
|
|
40
|
+
margin-bottom: 0.5rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.mb-4 {
|
|
44
|
+
margin-bottom: 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (min-width: 640px) {
|
|
48
|
+
.sm\:mb-4 {
|
|
49
|
+
margin-bottom: 1rem;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.\-mb-4 {
|
|
54
|
+
margin-bottom: -1rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.mb-6 {
|
|
58
|
+
margin-bottom: 1.5rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.mb-8 {
|
|
62
|
+
margin-bottom: 2rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.mt-0 {
|
|
66
|
+
margin-top: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.mt-2 {
|
|
70
|
+
margin-top: 0.5rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.mt-4 {
|
|
74
|
+
margin-top: 1rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.-mt-4 {
|
|
78
|
+
margin-top: -1rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.mt-6 {
|
|
82
|
+
margin-top: 1.5rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.mx-0 {
|
|
86
|
+
margin-left: 0;
|
|
87
|
+
margin-right: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.mx-auto {
|
|
91
|
+
margin-left: auto;
|
|
92
|
+
margin-right: auto;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.my-2 {
|
|
96
|
+
margin-bottom: 0.5rem;
|
|
97
|
+
margin-top: 0.5rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.my-4 {
|
|
101
|
+
margin-bottom: 1rem;
|
|
102
|
+
margin-top: 1rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.my-8 {
|
|
106
|
+
margin-bottom: 2rem;
|
|
107
|
+
margin-top: 2rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Padding. */
|
|
111
|
+
|
|
112
|
+
.p-4 {
|
|
113
|
+
padding: 1rem;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.p-6 {
|
|
117
|
+
padding: 1.5rem;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.p-8 {
|
|
121
|
+
padding: 2rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@media (min-width: 992px) {
|
|
125
|
+
.lg\:p-8 {
|
|
126
|
+
padding: 2rem;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.pb-8 {
|
|
131
|
+
padding-bottom: 2rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Gap. */
|
|
135
|
+
|
|
136
|
+
.gap-2 {
|
|
137
|
+
gap: 0.5rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.gap-4 {
|
|
141
|
+
gap: 1rem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.gap-8 {
|
|
145
|
+
gap: 2rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.gap-x-4 {
|
|
149
|
+
column-gap: 1rem;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.gap-y-6 {
|
|
153
|
+
row-gap: 1.5rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Width. */
|
|
157
|
+
|
|
158
|
+
.max-w-full {
|
|
159
|
+
max-width: 100%;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@media (min-width: 640px) {
|
|
163
|
+
.sm\:max-w-sm {
|
|
164
|
+
max-width: 22rem;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.max-w-lg {
|
|
169
|
+
max-width: 32rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.max-w-2xl {
|
|
173
|
+
max-width: 40rem;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.max-w-5xl {
|
|
177
|
+
max-width: 60rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.w-32 {
|
|
181
|
+
width: 8rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.w-auto {
|
|
185
|
+
width: auto;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.w-fit {
|
|
189
|
+
width: fit-content;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.w-full {
|
|
193
|
+
width: 100%;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Height. */
|
|
197
|
+
|
|
198
|
+
.h-auto {
|
|
199
|
+
height: auto;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.min-h-screen {
|
|
203
|
+
min-height: 100vh;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Font. */
|
|
207
|
+
|
|
208
|
+
.font-thin {
|
|
209
|
+
font-weight: 300;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.font-semibold {
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.font-bold {
|
|
217
|
+
font-weight: 700;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.tabular-nums {
|
|
221
|
+
font-variant-numeric: tabular-nums;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.text-base {
|
|
225
|
+
font-size: 1rem;
|
|
226
|
+
line-height: 1.5rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.text-sm {
|
|
230
|
+
font-size: 0.875rem;
|
|
231
|
+
line-height: 1.25rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@media (min-width: 992px) {
|
|
235
|
+
.lg\:text-sm {
|
|
236
|
+
font-size: 0.875rem;
|
|
237
|
+
line-height: 1.25rem;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.text-lg {
|
|
242
|
+
font-size: 1.125rem;
|
|
243
|
+
line-height: 1.625rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.text-xl {
|
|
247
|
+
font-size: 1.25rem;
|
|
248
|
+
line-height: 1.75rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@media (min-width: 992px) {
|
|
252
|
+
.lg\:text-xl {
|
|
253
|
+
font-size: 1.25rem;
|
|
254
|
+
line-height: 1.75rem;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.text-2xl {
|
|
259
|
+
font-size: 1.5rem;
|
|
260
|
+
line-height: 2.125rem;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@media (min-width: 992px) {
|
|
264
|
+
.lg\:text-3xl {
|
|
265
|
+
font-size: 1.875rem;
|
|
266
|
+
line-height: 2.25rem;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.text-4xl {
|
|
271
|
+
font-size: 2.25rem;
|
|
272
|
+
line-height: 2.75rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@media (min-width: 992px) {
|
|
276
|
+
.lg\:text-4xl {
|
|
277
|
+
font-size: 2.25rem;
|
|
278
|
+
line-height: 2.75rem;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.tracking-wider {
|
|
283
|
+
letter-spacing: 0.05rem;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.uppercase {
|
|
287
|
+
text-transform: uppercase;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.whitespace-nowrap {
|
|
291
|
+
white-space: nowrap;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Image. */
|
|
295
|
+
|
|
296
|
+
.bg-center {
|
|
297
|
+
background-position: center;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.bg-cover {
|
|
301
|
+
background-size: cover;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.object-contain {
|
|
305
|
+
object-fit: contain;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Layout. */
|
|
309
|
+
|
|
310
|
+
.absolute {
|
|
311
|
+
position: absolute;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.contents {
|
|
315
|
+
display: contents;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.flex {
|
|
319
|
+
display: flex;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.flex-col {
|
|
323
|
+
flex-direction: column;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.flex-wrap {
|
|
327
|
+
flex-wrap: wrap;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.grid {
|
|
331
|
+
display: grid;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.grid-cols-1 {
|
|
335
|
+
grid-template-columns: 1fr;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.grid-cols-2 {
|
|
339
|
+
grid-template-columns: 1fr 1fr;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@media (min-width: 640px) {
|
|
343
|
+
.sm\:grid-cols-2 {
|
|
344
|
+
grid-template-columns: 1fr 1fr;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@media (min-width: 768px) {
|
|
349
|
+
.md\:grid-cols-2 {
|
|
350
|
+
grid-template-columns: 1fr 1fr;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
@media (min-width: 992px) {
|
|
355
|
+
.lg\:grid-cols-2 {
|
|
356
|
+
grid-template-columns: 1fr 1fr;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@media (min-width: 992px) {
|
|
361
|
+
.lg\:grid-cols-3 {
|
|
362
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@media (min-width: 992px) {
|
|
367
|
+
.lg\:grid-cols-4 {
|
|
368
|
+
grid-template-columns: 1fr 1fr 1fr 1fr;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@media (min-width: 1200px) {
|
|
373
|
+
.\2xl\:grid-cols-6 {
|
|
374
|
+
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.hidden {
|
|
379
|
+
display: none;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@media (min-width: 992px) {
|
|
383
|
+
.lg\:hidden {
|
|
384
|
+
display: none;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.items-center {
|
|
389
|
+
align-items: center;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.justify-center {
|
|
393
|
+
justify-content: center;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.overflow-auto {
|
|
397
|
+
overflow: auto;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.overflow-hidden {
|
|
401
|
+
overflow: hidden;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.right-2 {
|
|
405
|
+
right: 0.5rem;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.row-span-2 {
|
|
409
|
+
grid-row: 2;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.text-center {
|
|
413
|
+
text-align: center;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@media (min-width: 640px) {
|
|
417
|
+
.sm\:text-center {
|
|
418
|
+
text-align: center;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
@media (min-width: 768px) {
|
|
423
|
+
.md\:text-center {
|
|
424
|
+
text-align: center;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.top-2 {
|
|
429
|
+
top: 0.5rem;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* Border. */
|
|
433
|
+
|
|
434
|
+
.border {
|
|
435
|
+
border: 1px solid;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@media (min-width: 992px) {
|
|
439
|
+
.lg\:border {
|
|
440
|
+
border: 1px solid;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.border-b {
|
|
445
|
+
border-bottom: 1px solid;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.\!border-none {
|
|
449
|
+
border: none !important;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@media (min-width: 992px) {
|
|
453
|
+
.lg\:border-none {
|
|
454
|
+
border: none;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.rounded-lg {
|
|
459
|
+
border-radius: 0.5rem;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
@media (min-width: 992px) {
|
|
463
|
+
.lg\:rounded-lg {
|
|
464
|
+
border-radius: 0.5rem;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.rounded-xl {
|
|
469
|
+
border-radius: 1rem;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.shadow-sm {
|
|
473
|
+
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.1);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@media (min-width: 992px) {
|
|
477
|
+
.lg\:shadow-sm {
|
|
478
|
+
box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.1);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
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
|
+
/* Color. */
|
|
586
|
+
|
|
587
|
+
.opacity-0 {
|
|
588
|
+
opacity: 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.opacity-50 {
|
|
592
|
+
opacity: 0.5;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.bg-gray-50 {
|
|
596
|
+
background-color: rgba(0, 0, 0, 0.015625);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.bg-gray-100 {
|
|
600
|
+
background-color: rgba(0, 0, 0, 0.03125);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.\!bg-transparent {
|
|
604
|
+
background-color: transparent !important;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.border-gray-100 {
|
|
608
|
+
border-color: rgba(0, 0, 0, 0.03125);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.fill-amber-300\/\[50\%\] {
|
|
612
|
+
fill: color-mix(in srgb, chocolate 25%, transparent);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.fill-cyan-300\/\[50\%\] {
|
|
616
|
+
fill: color-mix(in srgb, darkcyan 25%, transparent);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.fill-fuchsia-300\/\[50\%\] {
|
|
620
|
+
fill: color-mix(in srgb, palevioletred 25%, transparent);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.fill-indigo-300\/\[50\%\] {
|
|
624
|
+
fill: color-mix(in srgb, darkolivegreen 25%, transparent);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.fill-lime-300\/\[50\%\] {
|
|
628
|
+
fill: color-mix(in srgb, green 25%, transparent);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.fill-purple-300\/\[50\%\] {
|
|
632
|
+
fill: color-mix(in srgb, darkorchid 25%, transparent);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.fill-teal-300\/\[50\%\] {
|
|
636
|
+
fill: color-mix(in srgb, darkturquoise 25%, transparent);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.fill-yellow-300\/\[50\%\] {
|
|
640
|
+
fill: color-mix(in srgb, yellowgreen 25%, transparent);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.stroke-amber-500 {
|
|
644
|
+
stroke: chocolate;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.stroke-cyan-500 {
|
|
648
|
+
stroke: darkcyan;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.stroke-fuchsia-500 {
|
|
652
|
+
stroke: palevioletred;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.stroke-indigo-500 {
|
|
656
|
+
stroke: darkolivegreen;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.stroke-lime-500 {
|
|
660
|
+
stroke: green;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.stroke-purple-500 {
|
|
664
|
+
stroke: darkorchid;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.stroke-teal-500 {
|
|
668
|
+
stroke: darkturquoise;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.stroke-yellow-500 {
|
|
672
|
+
stroke: yellowgreen;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.text-amber-400 {
|
|
676
|
+
color: chocolate;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.text-cyan-400 {
|
|
680
|
+
color: darkcyan;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.text-fuchsia-400 {
|
|
684
|
+
color: palevioletred;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.text-green-400 {
|
|
688
|
+
color: limegreen;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.text-indigo-400 {
|
|
692
|
+
color: darkolivegreen;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.text-lime-400 {
|
|
696
|
+
color: green;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.text-orange-400 {
|
|
700
|
+
color: orange;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.text-purple-400 {
|
|
704
|
+
color: darkorchid;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.text-red-400 {
|
|
708
|
+
color: red;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.text-teal-400 {
|
|
712
|
+
color: darkturquoise;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.text-yellow-400 {
|
|
716
|
+
color: yellowgreen;
|
|
717
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evikit",
|
|
3
3
|
"description": "Preact/SQLite SSR framework for small web apps",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.14.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
|
+
"./css": "./index.css",
|
|
8
9
|
"./db": "./db.js",
|
|
9
10
|
"./server": "./server.js",
|
|
10
|
-
"./sqlite": "./sqlite.js"
|
|
11
|
-
"./testing": "./testing.js"
|
|
11
|
+
"./sqlite": "./sqlite.js"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
14
|
"evikit-extract": "bin/evikit-extract.js"
|
|
@@ -32,15 +32,22 @@
|
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"accept-language": "^3.0.20",
|
|
35
|
+
"esbuild": "^0.28.2",
|
|
35
36
|
"hoofd": "^1.7.3",
|
|
36
37
|
"preact": "^10.29.2",
|
|
37
38
|
"preact-iso": "^2.12.0",
|
|
38
39
|
"valibot": "^1.4.0"
|
|
39
40
|
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"esbuild": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
40
46
|
"devDependencies": {
|
|
41
47
|
"@eslint/compat": "^2.1.0",
|
|
42
48
|
"@eslint/js": "^10.0.1",
|
|
43
49
|
"@types/node": "^24.13.3",
|
|
50
|
+
"esbuild": "^0.28.2",
|
|
44
51
|
"eslint": "^10.4.0",
|
|
45
52
|
"eslint-config-prettier": "^10.1.8",
|
|
46
53
|
"eslint-plugin-perfectionist": "^5.9.0",
|
|
@@ -48,5 +55,8 @@
|
|
|
48
55
|
"prettier": "^3.8.3",
|
|
49
56
|
"typescript": "^6.0.3",
|
|
50
57
|
"typescript-eslint": "^8.59.4"
|
|
58
|
+
},
|
|
59
|
+
"allowScripts": {
|
|
60
|
+
"esbuild": true
|
|
51
61
|
}
|
|
52
62
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
const html = await Promise.all(
|
|
4
|
+
[
|
|
5
|
+
"https://evikit.js.org",
|
|
6
|
+
"https://evikit.js.org/todo",
|
|
7
|
+
"https://yoself.nykula.com",
|
|
8
|
+
"https://yoself.nykula.com/404",
|
|
9
|
+
].map(async (url) => await (await fetch(url)).text()),
|
|
10
|
+
);
|
|
11
|
+
const classes = [
|
|
12
|
+
...new Set([
|
|
13
|
+
...html.flatMap((html) =>
|
|
14
|
+
[...html.matchAll(/class=(?:"([^"]+)"|'([^']+)'|([^ ]+))/g)].flatMap(
|
|
15
|
+
([, double, single, none]) =>
|
|
16
|
+
(double || single || none || "").split(" "),
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
...(
|
|
20
|
+
await (
|
|
21
|
+
await fetch(
|
|
22
|
+
"https://codeberg.org/nykula/lanquiz/raw/branch/main/classes.log",
|
|
23
|
+
)
|
|
24
|
+
).text()
|
|
25
|
+
)
|
|
26
|
+
.split("\n")
|
|
27
|
+
.filter(Boolean),
|
|
28
|
+
]),
|
|
29
|
+
]
|
|
30
|
+
.map((className) => ({
|
|
31
|
+
base: className.split(/[:!]|^!?-/).at(-1) || "",
|
|
32
|
+
className,
|
|
33
|
+
selector: `.${className.replace(/(\[|\]|[:/#!%])/g, "\\$1")}`,
|
|
34
|
+
}))
|
|
35
|
+
.filter(({ base }) => base)
|
|
36
|
+
.sort((a, b) => a.base.localeCompare(b.base));
|
|
37
|
+
const css = await readFile("./index.css", "utf-8");
|
|
38
|
+
const missing = classes
|
|
39
|
+
.filter(({ selector }) => !css.includes(selector))
|
|
40
|
+
.map(({ className }) => className)
|
|
41
|
+
.join("\n");
|
|
42
|
+
const extra = [...css.matchAll(/\n *(\.[^ ,]+)/g)]
|
|
43
|
+
.map(([, selector]) => (selector || "").replace(/:focus|:hover/, ""))
|
|
44
|
+
.filter((selector) => !classes.some((c) => c.selector == selector))
|
|
45
|
+
.map((selector) => selector?.replace(/[.\\]/g, ""))
|
|
46
|
+
.join("\n");
|
|
47
|
+
if (missing) {
|
|
48
|
+
console.log(`Missing:\n${missing}`);
|
|
49
|
+
}
|
|
50
|
+
if (missing && extra) {
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
if (extra) {
|
|
54
|
+
console.log(`Extra:\n${extra}`);
|
|
55
|
+
}
|
package/server.d.ts
CHANGED
package/server.js
CHANGED
|
@@ -4,7 +4,8 @@ import acceptLanguage from "accept-language";
|
|
|
4
4
|
import { po } from "gettext-parser";
|
|
5
5
|
import cluster from "node:cluster";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import {
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
8
9
|
import { availableParallelism } from "node:os";
|
|
9
10
|
import { h } from "preact";
|
|
10
11
|
import { renderToString } from "preact-render-to-string";
|
|
@@ -207,10 +208,104 @@ export function json(output) {
|
|
|
207
208
|
};
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
let esbuild = false;
|
|
212
|
+
|
|
210
213
|
/** @param {{ listen: (port: number, callback: () => void) => { close(): void, on(event: "error", callback: (error: unknown) => void ): void } }} app */
|
|
211
|
-
export function listen(app) {
|
|
214
|
+
export async function listen(app) {
|
|
215
|
+
if (esbuild && cluster.isPrimary) {
|
|
216
|
+
let esbuildPort = 0;
|
|
217
|
+
|
|
218
|
+
async function makeHtml() {
|
|
219
|
+
await writeFile(
|
|
220
|
+
"./dist/index.html",
|
|
221
|
+
(await readFile("./index.html", "utf-8"))
|
|
222
|
+
.replace(
|
|
223
|
+
"/index.css",
|
|
224
|
+
"/index.css?" + (await sha256("./dist/index.css")),
|
|
225
|
+
)
|
|
226
|
+
.replace(
|
|
227
|
+
/<script[^>]+><\/script>/,
|
|
228
|
+
'<script src="/src/prerender.js?' +
|
|
229
|
+
(await sha256("./dist/src/prerender.js")) +
|
|
230
|
+
'"></script>' +
|
|
231
|
+
(esbuildPort
|
|
232
|
+
? '<script>new EventSource("http://localhost:' +
|
|
233
|
+
esbuildPort +
|
|
234
|
+
'/esbuild").addEventListener("change", () => location.reload());</script>'
|
|
235
|
+
: ""),
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (process.argv[2] == "build") {
|
|
241
|
+
const { build } = await import("esbuild");
|
|
242
|
+
if (existsSync("./dist/api.js")) {
|
|
243
|
+
await unlink("./dist/api.js");
|
|
244
|
+
}
|
|
245
|
+
await build({
|
|
246
|
+
bundle: true,
|
|
247
|
+
entryPoints: ["./index.css", "./src/prerender.js"],
|
|
248
|
+
outdir: "./dist",
|
|
249
|
+
});
|
|
250
|
+
await makeHtml();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (process.argv[2] == "dev" && cluster.isPrimary) {
|
|
255
|
+
const { context } = await import("esbuild");
|
|
256
|
+
|
|
257
|
+
const { watch: watchApi } = await context({
|
|
258
|
+
bundle: true,
|
|
259
|
+
entryPoints: ["./src/api.js"],
|
|
260
|
+
external: ["esbuild"],
|
|
261
|
+
outdir: "./dist",
|
|
262
|
+
platform: "node",
|
|
263
|
+
});
|
|
264
|
+
await watchApi();
|
|
265
|
+
|
|
266
|
+
const { serve: servePrerender, watch: watchPrerender } = await context({
|
|
267
|
+
bundle: true,
|
|
268
|
+
entryPoints: ["./index.css", "./src/prerender.js"],
|
|
269
|
+
outdir: "./dist",
|
|
270
|
+
});
|
|
271
|
+
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
|
+
|
|
280
|
+
let subprocess = cluster.fork();
|
|
281
|
+
let lastHash =
|
|
282
|
+
(await sha256("./dist/api.js")) +
|
|
283
|
+
(await sha256("./dist/index.css")) +
|
|
284
|
+
(await sha256("./dist/src/prerender.js"));
|
|
285
|
+
await makeHtml();
|
|
286
|
+
setInterval(async () => {
|
|
287
|
+
const hash =
|
|
288
|
+
(await sha256("./dist/api.js")) +
|
|
289
|
+
(await sha256("./dist/index.css")) +
|
|
290
|
+
(await sha256("./dist/src/prerender.js"));
|
|
291
|
+
if (hash != lastHash) {
|
|
292
|
+
lastHash = hash;
|
|
293
|
+
await makeHtml();
|
|
294
|
+
subprocess.kill();
|
|
295
|
+
subprocess = cluster.fork();
|
|
296
|
+
}
|
|
297
|
+
}, 1000);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await makeHtml();
|
|
301
|
+
}
|
|
302
|
+
|
|
212
303
|
const port = Number(process.env["PORT"]) || 5173;
|
|
213
|
-
if (
|
|
304
|
+
if (
|
|
305
|
+
cluster.isPrimary &&
|
|
306
|
+
parallelism() > 1 &&
|
|
307
|
+
(!esbuild || !process.argv[2])
|
|
308
|
+
) {
|
|
214
309
|
/** @type {{ on(event: "message", callback: (message: any) => void): void, send(message: any, _: any, options: { keepOpen: boolean }): void }[]} */
|
|
215
310
|
const forks = [];
|
|
216
311
|
for (let i = 0; i < parallelism(); i++) {
|
|
@@ -226,7 +321,7 @@ export function listen(app) {
|
|
|
226
321
|
}
|
|
227
322
|
});
|
|
228
323
|
}
|
|
229
|
-
} else {
|
|
324
|
+
} else if ((esbuild && !process.argv[2]) || cluster.isPrimary) {
|
|
230
325
|
const server = app.listen(port, () => {
|
|
231
326
|
if (cluster.isPrimary || process.env["i"] == "0") {
|
|
232
327
|
console.log(
|
|
@@ -244,14 +339,20 @@ export function listen(app) {
|
|
|
244
339
|
}
|
|
245
340
|
}
|
|
246
341
|
|
|
247
|
-
/** @param {{ html
|
|
342
|
+
/** @param {{ html?: string, prerender({ cookie, lang, url }: { cookie?: string | undefined, lang: string, url: string }): Promise<{ data: {}, head: { elements: Set<{ props: { [k: string]: string }, type: string }>, lang: string, title: string }, html: string }> }} props */
|
|
248
343
|
export function production({ html, prerender }) {
|
|
344
|
+
if (!html) {
|
|
345
|
+
esbuild = true;
|
|
346
|
+
}
|
|
249
347
|
/**
|
|
250
348
|
* @param {import("node:http").IncomingMessage} req
|
|
251
349
|
* @param {import("node:http").ServerResponse<import("node:http").IncomingMessage>} res
|
|
252
350
|
*/
|
|
253
351
|
return async function onRequest(req, res) {
|
|
254
352
|
try {
|
|
353
|
+
if (!html) {
|
|
354
|
+
html = await readFile("./dist/index.html", "utf-8");
|
|
355
|
+
}
|
|
255
356
|
const rendered = await prerender({
|
|
256
357
|
cookie: req.headers.cookie,
|
|
257
358
|
lang: acceptLanguage.get(req.headers["accept-language"]) || "en",
|
|
@@ -271,9 +372,7 @@ export function production({ html, prerender }) {
|
|
|
271
372
|
res.end(
|
|
272
373
|
html
|
|
273
374
|
.replace(/lang=".+?"/, () => `lang="${rendered.head.lang}"`)
|
|
274
|
-
.replace(/<title>.+<\/title>/,
|
|
275
|
-
renderToString(h("title", null, rendered.head.title)),
|
|
276
|
-
)
|
|
375
|
+
.replace(/<title>.+<\/title>/, "")
|
|
277
376
|
.replace(/<!--app-head-->[\s\S]+?<body/, (match) =>
|
|
278
377
|
match
|
|
279
378
|
.replace(/<link[^>]+>/g, (link) =>
|
|
@@ -283,19 +382,20 @@ export function production({ html, prerender }) {
|
|
|
283
382
|
)
|
|
284
383
|
.replace(
|
|
285
384
|
`<!--app-head-->`,
|
|
286
|
-
|
|
287
|
-
.
|
|
288
|
-
(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
385
|
+
renderToString(h("title", null, rendered.head.title)) +
|
|
386
|
+
[...rendered.head.elements.values()]
|
|
387
|
+
.map(
|
|
388
|
+
({ props, type }) =>
|
|
389
|
+
`<${type} ` +
|
|
390
|
+
Object.entries(props)
|
|
391
|
+
.map(([name, value]) => `${name}="${value}"`)
|
|
392
|
+
.join(" ") +
|
|
393
|
+
(type == "script" ? `</${type}>` : " />"),
|
|
394
|
+
)
|
|
395
|
+
.join(""),
|
|
296
396
|
)
|
|
297
397
|
.replace(
|
|
298
|
-
/(<div id="app">)[\s\S]
|
|
398
|
+
/(<div id="app">)[\s\S]*<!--app-html-->/,
|
|
299
399
|
(_, div) =>
|
|
300
400
|
div +
|
|
301
401
|
rendered.html +
|
|
@@ -666,3 +766,12 @@ function ArrayOf(input) {
|
|
|
666
766
|
function parallelism() {
|
|
667
767
|
return Number(process.env["PARALLELISM"]) || availableParallelism();
|
|
668
768
|
}
|
|
769
|
+
|
|
770
|
+
/** @param {string} file */
|
|
771
|
+
async function sha256(file) {
|
|
772
|
+
return existsSync(file)
|
|
773
|
+
? createHash("sha256")
|
|
774
|
+
.update(await readFile(file))
|
|
775
|
+
.digest("hex")
|
|
776
|
+
: "";
|
|
777
|
+
}
|