jskelet 0.5.4 → 0.6.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/AGENTS.md +18 -13
- package/CHANGELOG.md +387 -385
- package/README.md +9 -7
- package/bin/jskelet.mjs +24 -10
- package/docs/01-baslangic.md +4 -3
- package/docs/02-mimari.md +4 -3
- package/docs/03-routing.md +11 -6
- package/docs/04-render-ve-sablonlar.md +35 -43
- package/docs/05-islands.md +12 -8
- package/docs/07-yapilandirma.md +53 -25
- package/docs/08-build.md +40 -18
- package/docs/09-dev-araclari.md +5 -1
- package/docs/10-dagitim.md +6 -1
- package/docs/11-tasima.md +51 -17
- package/docs/12-panel-ve-oturum.md +10 -4
- package/docs/README.md +7 -5
- package/docs/en/01-getting-started.md +4 -3
- package/docs/en/02-architecture.md +5 -5
- package/docs/en/03-routing.md +12 -7
- package/docs/en/04-rendering.md +47 -59
- package/docs/en/05-islands.md +13 -8
- package/docs/en/07-configuration.md +55 -27
- package/docs/en/08-build.md +43 -21
- package/docs/en/09-dev-tools.md +6 -1
- package/docs/en/10-deployment.md +6 -1
- package/docs/en/11-migration.md +51 -16
- package/docs/en/12-dashboards-and-sessions.md +9 -4
- package/docs/en/README.md +7 -5
- package/package.json +49 -14
- package/src/build/tasks/client.mjs +91 -10
- package/src/build/tasks/icons.mjs +152 -18
- package/src/client/index.js +2 -2
- package/src/compile/codegen.js +4 -0
- package/src/compile/compile-all.js +12 -21
- package/src/compile/expr.js +5 -0
- package/src/compile/parse.js +64 -8
- package/src/compile/resolve.js +3 -0
- package/src/config/defaults.js +12 -2
- package/src/config/index.js +31 -3
- package/src/dev-server.mjs +26 -3
- package/src/http/cookies-entry.js +1 -0
- package/src/http/cookies.js +18 -0
- package/src/logo.png +0 -0
- package/src/migrate/apply.mjs +262 -0
- package/src/migrate/babel.mjs +79 -0
- package/src/migrate/classify.mjs +155 -0
- package/src/migrate/config.mjs +126 -0
- package/src/migrate/fs-walk.mjs +191 -0
- package/src/migrate/parse.mjs +26 -0
- package/src/migrate/scan.mjs +177 -0
- package/src/migrate/transform/expr-source.mjs +168 -0
- package/src/migrate/transform/island.mjs +67 -0
- package/src/migrate/transform/jsx-to-component.mjs +302 -0
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -0
- package/src/migrate/transform/page-split.mjs +435 -0
- package/src/migrate/write.mjs +81 -0
- package/src/migrate.mjs +171 -0
- package/src/server/auth/handoff.js +94 -11
- package/src/server/create-app.js +28 -10
- package/src/server/ejs-adapter.js +59 -0
- package/src/server/image-optimizer.js +94 -26
- package/src/server/port-guard.js +255 -0
- package/src/server/render.js +27 -9
- package/src/server/status-page.js +105 -4
- package/src/start.mjs +18 -3
- package/src/templates/layout.ejs +8 -28
- package/src/templates/layout.jsk +30 -0
- package/src/templates/layout.render.js +41 -0
- package/src/views/helpers/tags.js +86 -3
- package/types/build/resolve-peer.d.mts +13 -0
- package/types/client/dom.d.ts +55 -0
- package/types/client/form.d.ts +19 -0
- package/types/client/index.d.ts +20 -0
- package/types/client/registry.d.ts +53 -0
- package/types/client/safe-image.d.ts +19 -0
- package/types/client/shared-cookie.d.ts +82 -0
- package/types/client/store.d.ts +18 -0
- package/types/client/swap.d.ts +46 -0
- package/types/compile/codegen.d.ts +32 -0
- package/types/compile/compile-all.d.ts +42 -0
- package/types/compile/errors.d.ts +30 -0
- package/types/compile/expr.d.ts +67 -0
- package/types/compile/index.d.ts +10 -0
- package/types/compile/parse.d.ts +82 -0
- package/types/compile/resolve.d.ts +46 -0
- package/types/compile/scan-exports.d.ts +9 -0
- package/types/config/defaults.d.ts +449 -0
- package/types/config/index.d.ts +299 -0
- package/types/config/pattern.d.ts +38 -0
- package/types/http/control-flow.d.ts +45 -0
- package/types/http/cookies-entry.d.ts +5 -0
- package/types/http/cookies.d.ts +113 -0
- package/types/http/request-cache.d.ts +13 -0
- package/types/http/request-context.d.ts +67 -0
- package/types/http/shared-cookie.d.ts +73 -0
- package/types/index.d.ts +30 -0
- package/types/log.d.mts +153 -0
- package/types/server/admin/actions.d.ts +16 -0
- package/types/server/admin/auth.d.ts +52 -0
- package/types/server/admin/event-log.d.ts +38 -0
- package/types/server/admin/gate.d.ts +43 -0
- package/types/server/admin/inventory.d.ts +40 -0
- package/types/server/admin/mount.d.ts +6 -0
- package/types/server/admin/router.d.ts +6 -0
- package/types/server/admin/snapshot.d.ts +6 -0
- package/types/server/assets.d.ts +47 -0
- package/types/server/auth/handoff.d.ts +12 -0
- package/types/server/cache-deps.d.ts +16 -0
- package/types/server/cache-vary.d.ts +30 -0
- package/types/server/cloudflare.d.ts +163 -0
- package/types/server/create-app.d.ts +25 -0
- package/types/server/data-cache.d.ts +116 -0
- package/types/server/dev/devtools.d.ts +44 -0
- package/types/server/dev/report.d.ts +229 -0
- package/types/server/dev/socket.d.ts +17 -0
- package/types/server/dev/version-check.d.mts +15 -0
- package/types/server/ejs-adapter.d.ts +11 -0
- package/types/server/head-hints.d.ts +40 -0
- package/types/server/html-cache.d.ts +173 -0
- package/types/server/image-optimizer.d.ts +68 -0
- package/types/server/logs/access-middleware.d.ts +7 -0
- package/types/server/logs/file-sink.d.ts +17 -0
- package/types/server/logs/pipeline.d.ts +37 -0
- package/types/server/logs/s3-put.d.ts +85 -0
- package/types/server/logs/s3-sink.d.ts +26 -0
- package/types/server/metadata.d.ts +38 -0
- package/types/server/middleware/compression.d.ts +17 -0
- package/types/server/middleware/csrf.d.ts +4 -0
- package/types/server/middleware/dev-gate.d.ts +2 -0
- package/types/server/middleware/headers.d.ts +2 -0
- package/types/server/middleware/redirects.d.ts +2 -0
- package/types/server/middleware/static-precompressed.d.ts +5 -0
- package/types/server/middleware/trailing-slash.d.ts +11 -0
- package/types/server/middleware/upstream-proxy.d.ts +21 -0
- package/types/server/og-image.d.ts +149 -0
- package/types/server/port-guard.d.ts +50 -0
- package/types/server/prewarm.d.ts +128 -0
- package/types/server/redis.d.ts +163 -0
- package/types/server/render.d.ts +101 -0
- package/types/server/router.d.ts +5 -0
- package/types/server/status-page.d.ts +24 -0
- package/types/server/upstream-limiter.d.ts +123 -0
- package/types/server/upstream-tracking.d.ts +42 -0
- package/types/shared/cookie-domain.d.ts +29 -0
- package/types/templates/layout.render.d.ts +7 -0
- package/types/version.d.mts +10 -0
- package/types/views/components/loader.d.ts +5 -0
- package/types/views/helpers/html.d.ts +39 -0
- package/types/views/helpers/tags.d.ts +127 -0
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
lang: "tr",
|
|
56
56
|
},
|
|
57
57
|
|
|
58
|
-
layout: "views/layout.
|
|
58
|
+
layout: "views/layout.jsk",
|
|
59
59
|
routes: ["./routes/10-pages.mjs", "./routes/99-catch-all.mjs"],
|
|
60
60
|
trailingSlash: false,
|
|
61
61
|
|
|
@@ -92,7 +92,7 @@ export default {
|
|
|
92
92
|
watch: ["data"],
|
|
93
93
|
|
|
94
94
|
fonts: [{ family: "Inter", weights: [400, 600, 700] }],
|
|
95
|
-
icons: { scan: ["views", "client", "routes", "lib"] },
|
|
95
|
+
icons: { dir: "icons", scan: ["views", "client", "routes", "lib"] },
|
|
96
96
|
images: { widths: [400, 800, 1200], quality: 78, skip: ["downloads"] },
|
|
97
97
|
clientEnv: ["PUBLIC_WS_URL"],
|
|
98
98
|
|
|
@@ -213,27 +213,32 @@ cross-subdomain handoff bridge for a short session id.
|
|
|
213
213
|
|
|
214
214
|
| Field | Type | Default | Meaning |
|
|
215
215
|
| --- | --- | --- | --- |
|
|
216
|
-
| `crossSubdomainHandoff` | `boolean \| object` | `false` |
|
|
216
|
+
| `crossSubdomainHandoff` | `boolean \| object` | `false` | When on: `POST /_jskelet/auth/handoff` + `?handoff=` redeem. Object: `allowedCookieNames` (required), `ttlSeconds?`, `path?`, `maxValueBytes?`, `maxPendingTickets?`, `maxMintsPerIpPerMinute?` |
|
|
217
217
|
|
|
218
218
|
```js
|
|
219
219
|
auth: {
|
|
220
|
-
crossSubdomainHandoff: {
|
|
220
|
+
crossSubdomainHandoff: {
|
|
221
|
+
allowedCookieNames: ["sid"],
|
|
222
|
+
ttlSeconds: 60,
|
|
223
|
+
},
|
|
221
224
|
},
|
|
222
225
|
```
|
|
223
226
|
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
The mint endpoint is mounted **after** the CSRF middleware (origin checks).
|
|
228
|
+
Cookie names outside the allowlist or that are not RFC 6265 tokens get 400.
|
|
229
|
+
Details: [12-dashboards-and-sessions.md](./12-dashboards-and-sessions.md).
|
|
226
230
|
|
|
227
231
|
## `layout`
|
|
228
232
|
|
|
229
233
|
**Type:** `string` — **Default:** none (automatic resolution)
|
|
230
234
|
|
|
231
|
-
Path of the layout `.ejs`
|
|
232
|
-
**parent directory of the views directory**, so with the default
|
|
233
|
-
`"views/custom.
|
|
235
|
+
Path of the layout file (`.jsk` or legacy `.ejs`). The value given is resolved
|
|
236
|
+
relative to the **parent directory of the views directory**, so with the default
|
|
237
|
+
`views`, `"views/custom.jsk"` → `<root>/views/custom.jsk`.
|
|
234
238
|
|
|
235
|
-
If not given, in order: `views/layout.ejs`
|
|
236
|
-
framework's
|
|
239
|
+
If not given, in order: `views/layout.jsk`, `views/layout.ejs` (legacy),
|
|
240
|
+
otherwise the framework's `src/templates/layout.jsk` default. Details:
|
|
241
|
+
[04-rendering.md](./04-rendering.md).
|
|
237
242
|
|
|
238
243
|
## `routes`
|
|
239
244
|
|
|
@@ -335,7 +340,7 @@ field reference.
|
|
|
335
340
|
| `trustProxy` | `boolean` | `true` | Express's `trust proxy` setting. Needed behind a reverse proxy for the correct protocol and client IP. |
|
|
336
341
|
| `cookieSecret` | `string \| null` | `null` | The signed cookie secret. When absent, `JSKELET_SECRET` is read. |
|
|
337
342
|
| `csrf.enabled` | `boolean` | `true` | The origin / `Sec-Fetch-Site` check. |
|
|
338
|
-
| `csrf.token` | `boolean` | `false` | The double-submit token layer. |
|
|
343
|
+
| `csrf.token` | `boolean` | `false` | The double-submit token layer. **Turn on** for cookie-session forms. |
|
|
339
344
|
| `csrf.allowedOrigins` | `string[]` | `[]` | Origins accepted alongside our own host. |
|
|
340
345
|
| `csrf.exclude` | `string[]` | `[]` | Paths exempt from the check; `source` pattern syntax. |
|
|
341
346
|
| `csrf.cookieName` | `string` | `"csrf_token"` | Name of the token cookie. |
|
|
@@ -343,14 +348,17 @@ field reference.
|
|
|
343
348
|
| `csrf.headerName` | `string` | `"x-csrf-token"` | Header the token is also accepted in. |
|
|
344
349
|
|
|
345
350
|
`trustProxy` should be **turned off** on a server exposed directly to the
|
|
346
|
-
internet: while it is on, a client can forge
|
|
347
|
-
|
|
351
|
+
internet: while it is on, a client can forge `X-Forwarded-For` /
|
|
352
|
+
`X-Forwarded-Proto` / Host, and rate limits, admin IP allowlists, Secure
|
|
353
|
+
cookies, and cache `vary.host` see the wrong address. Behind a reverse proxy
|
|
354
|
+
(nginx, Caddy, Cloudflare), `true` is the right default.
|
|
348
355
|
|
|
349
356
|
The CSRF check only rejects requests that are **known** to be cross-site — when
|
|
350
357
|
`Origin` does not match or `Sec-Fetch-Site: cross-site` arrives. If neither is
|
|
351
358
|
present the request passes, because browsers always send `Origin` on a
|
|
352
|
-
cross-origin POST while webhooks never do.
|
|
353
|
-
|
|
359
|
+
cross-origin POST while webhooks never do. For cookie-session dashboards,
|
|
360
|
+
enable `csrf.token: true` and `csrfField()` as a second layer; put webhook
|
|
361
|
+
paths in `csrf.exclude`.
|
|
354
362
|
|
|
355
363
|
## `navigation`
|
|
356
364
|
|
|
@@ -500,21 +508,29 @@ fonts: [
|
|
|
500
508
|
|
|
501
509
|
## `icons`
|
|
502
510
|
|
|
503
|
-
**Type:** `{ scan?: string[] } | false` — **Default:** `{}`
|
|
511
|
+
**Type:** `{ scan?: string[], dir?: string } | false` — **Default:** `{ dir: "icons" }`
|
|
504
512
|
|
|
505
|
-
|
|
513
|
+
SVG icon sprite generation. The source is chosen **XOR**: if the `icons.dir`
|
|
514
|
+
directory exists, only the flat SVGs there are used; otherwise
|
|
515
|
+
`@phosphor-icons/core` (when installed).
|
|
506
516
|
|
|
507
517
|
| Value | Result |
|
|
508
518
|
| --- | --- |
|
|
509
|
-
| `{}` (default) |
|
|
519
|
+
| `{}` (default) | `dir: "icons"`; scanned directories are `["views", "client", "routes", "lib", "features", "shared"]` |
|
|
520
|
+
| `{ dir: "assets/icons" }` | Changes the local SVG root |
|
|
510
521
|
| `{ scan: [...] }` | Changes the scanned directories |
|
|
511
522
|
| `false` | The sprite step is skipped entirely |
|
|
512
523
|
|
|
513
|
-
|
|
514
|
-
|
|
524
|
+
A local directory (when present) uses flat file names: `house.svg` →
|
|
525
|
+
`house:regular`, `house-bold.svg` → `house:bold`. An empty `icons/` directory
|
|
526
|
+
does not fall back to Phosphor — delete the directory to open the fallback.
|
|
527
|
+
Details: [08-build.md](./08-build.md).
|
|
515
528
|
|
|
516
529
|
```js
|
|
517
|
-
icons: {
|
|
530
|
+
icons: {
|
|
531
|
+
dir: "icons",
|
|
532
|
+
scan: ["views", "client", "routes", "lib", "content"],
|
|
533
|
+
}
|
|
518
534
|
```
|
|
519
535
|
|
|
520
536
|
## `images`
|
|
@@ -547,8 +563,9 @@ When `remote.allowHosts` is set, also proxies remote images at runtime
|
|
|
547
563
|
|
|
548
564
|
If `false` is given, neither surface runs. The build step requires `sharp` and
|
|
549
565
|
never runs on a watch pass. With remote enabled, `sharp` is also needed at
|
|
550
|
-
**runtime**; without it the optimizer 302-redirects to the source URL.
|
|
551
|
-
|
|
566
|
+
**runtime**; without it the optimizer 302-redirects to the source URL. Fetch
|
|
567
|
+
does not auto-follow redirects: every hop is re-checked against `allowHosts`
|
|
568
|
+
and private addresses. Details: [08-build.md](./08-build.md).
|
|
552
569
|
|
|
553
570
|
```js
|
|
554
571
|
images: {
|
|
@@ -580,7 +597,10 @@ that is not in the list returns `undefined` instead of crashing.
|
|
|
580
597
|
clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
581
598
|
```
|
|
582
599
|
|
|
583
|
-
**Do not put secrets here** — the values sit in the bundle in plain text.
|
|
600
|
+
**Do not put secrets here** — the values sit in the bundle in plain text. Keys
|
|
601
|
+
whose names look secret-like (`SECRET`, `PASSWORD`, `TOKEN`, `API_KEY`,
|
|
602
|
+
`PRIVATE`, …) are **rejected at build time** (`PUBLIC` / `PUBLISHABLE` names
|
|
603
|
+
are exempt).
|
|
584
604
|
|
|
585
605
|
## `headers()`
|
|
586
606
|
|
|
@@ -590,6 +610,7 @@ clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
|
590
610
|
Response headers by path pattern. The framework only writes long-lived cache
|
|
591
611
|
headers for static files; every other header (CSP, COOP, HSTS,
|
|
592
612
|
X-Frame-Options…) comes from here and takes precedence over the defaults.
|
|
613
|
+
Production sites should at least define the security headers below.
|
|
593
614
|
|
|
594
615
|
**All** matching rules are applied (unlike redirects, it does not stop at the
|
|
595
616
|
first match), in order; if two rules write the same header, the later one wins.
|
|
@@ -604,11 +625,18 @@ async headers() {
|
|
|
604
625
|
source: "/:path*",
|
|
605
626
|
headers: [
|
|
606
627
|
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
628
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
607
629
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
630
|
+
{
|
|
631
|
+
key: "Permissions-Policy",
|
|
632
|
+
value: "camera=(), microphone=(), geolocation=()",
|
|
633
|
+
},
|
|
608
634
|
{
|
|
609
635
|
key: "Content-Security-Policy",
|
|
610
|
-
value: "default-src 'self'; img-src 'self' https://cdn.example.com data
|
|
636
|
+
value: "default-src 'self'; img-src 'self' https://cdn.example.com data:; script-src 'self'",
|
|
611
637
|
},
|
|
638
|
+
// Only when you terminate HTTPS yourself:
|
|
639
|
+
// { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
|
|
612
640
|
],
|
|
613
641
|
},
|
|
614
642
|
{
|
|
@@ -1098,7 +1126,7 @@ and no warning is printed.
|
|
|
1098
1126
|
| Variable | Who reads it | Default | Meaning |
|
|
1099
1127
|
| --- | --- | --- | --- |
|
|
1100
1128
|
| `NODE_ENV` | everywhere | `production` (start/build), `development` (dev) | Determines the dev overlay, EJS cache, manifest re-reading, route error behaviour and prewarm defaults. `jskelet dev` sets it itself — `cross-env` is not needed. |
|
|
1101
|
-
| `PORT` | `startServer` | `3000` | Port to listen on |
|
|
1129
|
+
| `PORT` | `startServer` | `3000` | Port to listen on. If busy, the process refuses to start; `jskelet start|dev --murder` kills the listener |
|
|
1102
1130
|
| `HOST` | `startServer` | `::` | Interface to bind to. The default listens dual-stack (IPv6 + IPv4); it falls back to `0.0.0.0` where IPv6 is unavailable |
|
|
1103
1131
|
| `JSKELET_SECRET` | `jskelet/cookies` | — | The signed cookie secret. Read when `security.cookieSecret` is not set; if neither exists, the signed cookie API throws. [12](./12-dashboards-and-sessions.md) |
|
|
1104
1132
|
| `DEV_TOKEN` | `devGate`, `prewarm` | — | If set, every request without a token gets a 404. Prewarming carries the token as a cookie. [09](./09-dev-tools.md) |
|
package/docs/en/08-build.md
CHANGED
|
@@ -174,8 +174,10 @@ a new utility is written. Changes are coalesced over 120 ms.
|
|
|
174
174
|
|
|
175
175
|
## Client JS — esbuild
|
|
176
176
|
|
|
177
|
-
Every
|
|
178
|
-
|
|
177
|
+
Every source file under `client/entries/*.{js,ts,mts}` is an entry (no `.tsx`).
|
|
178
|
+
The manifest key is always `*.js` (`main.ts` → `main.js`). Multiple extensions
|
|
179
|
+
for the same stem fail the build. If the directory does not exist or is empty,
|
|
180
|
+
the step is skipped.
|
|
179
181
|
|
|
180
182
|
esbuild settings:
|
|
181
183
|
|
|
@@ -185,7 +187,7 @@ esbuild settings:
|
|
|
185
187
|
| `format` | `esm` | `type="module"` scripts |
|
|
186
188
|
| `target` | `chrome111`, `edge111`, `firefox111`, `safari16.4` | The lower bound of the ESM + dynamic import + `IntersectionObserver` island model; transpiling to anything older grows the output without winning a single visitor |
|
|
187
189
|
| `minify` | `true` | — |
|
|
188
|
-
| `sourcemap` | `
|
|
190
|
+
| `sourcemap` | only when `NODE_ENV=development` | Production builds do not publish `.map` files under `public/assets` |
|
|
189
191
|
| `entryNames` | `[name].[hash]` | `immutable` cache |
|
|
190
192
|
| `chunkNames` | `chunks/[name].[hash]` | — |
|
|
191
193
|
| `legalComments` | `none` | — |
|
|
@@ -196,17 +198,19 @@ The output lands under `public/assets/js/` and is cleaned first on every pass.
|
|
|
196
198
|
### The `@/` alias
|
|
197
199
|
|
|
198
200
|
On the esbuild side, `@/` resolves to the project root and extension completion
|
|
199
|
-
is performed (`.js`, `.mjs`, `.json`, `/index.js`).
|
|
200
|
-
`alias-hooks.mjs`
|
|
201
|
-
|
|
201
|
+
is performed (`.js`, `.mjs`, `.ts`, `.mts`, `.json`, `/index.js`, `/index.ts`).
|
|
202
|
+
Node `alias-hooks.mjs` resolves only `.js` / `.mjs` / `.json` on the server, so
|
|
203
|
+
shared `@/lib` modules must stay `.js`. Client-only `.ts` imports work on the
|
|
204
|
+
esbuild path.
|
|
202
205
|
|
|
203
206
|
### Inlining `clientEnv`
|
|
204
207
|
|
|
205
208
|
There is no `process` in the browser; modules shared with the server still read
|
|
206
209
|
`process.env`. The keys declared through `config.clientEnv` plus `NODE_ENV` are
|
|
207
210
|
defined as a single object at build time, which means that reading a key not in
|
|
208
|
-
the list returns `undefined` instead of crashing.
|
|
209
|
-
|
|
211
|
+
the list returns `undefined` instead of crashing. Secret-like key names
|
|
212
|
+
(`SECRET`, `API_KEY`, …) fail the build; names containing `PUBLIC` /
|
|
213
|
+
`PUBLISHABLE` are exempt. Details: [07-configuration.md](./07-configuration.md).
|
|
210
214
|
|
|
211
215
|
### Manifest keys
|
|
212
216
|
|
|
@@ -259,17 +263,33 @@ Because the `.woff2` extension and the `/fonts/` prefix are in the default
|
|
|
259
263
|
|
|
260
264
|
## Icon sprite
|
|
261
265
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
+
Produces a `<symbol>` set for **only the icons actually used in the source**.
|
|
267
|
+
Shipping the whole set means 1500+ icons, i.e. several megabytes; usage scanning
|
|
268
|
+
typically keeps the sprite at 10-30 symbols. The hashed `sprite.svg` is written
|
|
269
|
+
under `public/assets/` and is covered by precompress.
|
|
270
|
+
|
|
271
|
+
The source is chosen **XOR** — the two are never merged:
|
|
272
|
+
|
|
273
|
+
1. If `icons.dir` (default `icons/`) **exists as a directory**, only the flat
|
|
274
|
+
SVGs there. An empty directory does not fall back to Phosphor; delete the
|
|
275
|
+
directory to open the fallback.
|
|
276
|
+
2. Otherwise `@phosphor-icons/core` (from the application's `node_modules`). If
|
|
277
|
+
it is not installed, the step is silently skipped.
|
|
278
|
+
|
|
279
|
+
Local file names:
|
|
280
|
+
|
|
281
|
+
| File | Sprite key |
|
|
282
|
+
| --- | --- |
|
|
283
|
+
| `icons/house.svg` | `house:regular` |
|
|
284
|
+
| `icons/house-regular.svg` | `house:regular` |
|
|
285
|
+
| `icons/arrow-right-bold.svg` | `arrow-right:bold` |
|
|
266
286
|
|
|
267
287
|
- Symbol id: `<kebab-name>-<weight>`, e.g. `arrow-right-bold`.
|
|
268
|
-
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
288
|
+
- `viewBox` is copied from the source SVG onto the `<symbol>`; if missing,
|
|
289
|
+
`0 0 256 256` (recommended for Phosphor / `icon()` compatibility).
|
|
290
|
+
- The scanned directories default to `views`, `client`, `routes`, `lib`,
|
|
291
|
+
`features`, `shared`; they can be changed with `icons.scan`. Scanned
|
|
292
|
+
extensions: `.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
|
|
273
293
|
- Weights: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. An
|
|
274
294
|
unrecognised weight counts as `regular`.
|
|
275
295
|
|
|
@@ -296,8 +316,8 @@ If you see this warning, either write the name as a constant, or add the relevan
|
|
|
296
316
|
directory to the `icons.scan` list, or keep the name in a configuration field in
|
|
297
317
|
the form `icon: "XLogo"`.
|
|
298
318
|
|
|
299
|
-
Names that cannot be found in
|
|
300
|
-
of the build: `N icons missing → …`
|
|
319
|
+
Names that cannot be found in the chosen source are warned about as a summary at
|
|
320
|
+
the end of the build: `N icons missing → …`
|
|
301
321
|
|
|
302
322
|
## Image optimisation
|
|
303
323
|
|
|
@@ -339,7 +359,9 @@ and `image()` falls back to the original file. It never runs on a watch pass.
|
|
|
339
359
|
When `images.remote.allowHosts` is set, `createApp` mounts `/_jskelet/image`.
|
|
340
360
|
CMS / CDN covers never enter the build, so `image()` rewrites those host URLs to
|
|
341
361
|
`?url=&w=`; the endpoint encodes webp with sharp and stores files under
|
|
342
|
-
`.jskelet/image-cache/`.
|
|
362
|
+
`.jskelet/image-cache/`. Upstream fetch follows redirects manually: every hop is
|
|
363
|
+
re-checked against the allowlist and private IP / DNS rules (open-redirect SSRF
|
|
364
|
+
is closed). Details: [07-configuration.md](./07-configuration.md).
|
|
343
365
|
|
|
344
366
|
## Precompress
|
|
345
367
|
|
|
@@ -373,7 +395,7 @@ copy, the request is handed over to `express.static`
|
|
|
373
395
|
| `tailwindcss` | CSS (peer) | Tailwind directives cannot be resolved |
|
|
374
396
|
| `lightningcss` | CSS minification | Tailwind's output is used, a few kB bigger |
|
|
375
397
|
| `sharp` | Image optimisation | The step is skipped; `image()` uses the original |
|
|
376
|
-
| `@phosphor-icons/core` | Icon sprite | The step is skipped; `icon()` produces an empty `<use>` |
|
|
398
|
+
| `@phosphor-icons/core` | Icon sprite (when no local `icons/` dir) | The step is skipped; `icon()` produces an empty `<use>` |
|
|
377
399
|
|
|
378
400
|
If you are not going to use CSS, simply never create the `paths.styles` file: the
|
|
379
401
|
step is skipped with a warning and postcss is not needed.
|
package/docs/en/09-dev-tools.md
CHANGED
|
@@ -23,6 +23,11 @@ jskelet dev
|
|
|
23
23
|
(suppresses the build banner) and, if a TTY is present, `JSKELET_COLOR=1`
|
|
24
24
|
(forces color on piped output).
|
|
25
25
|
|
|
26
|
+
If the listen port (`PORT`, default `3000`) is already taken, the server
|
|
27
|
+
**does not start**; the error line includes the PID and a `--murder` hint.
|
|
28
|
+
`jskelet dev --murder` kills the listener and binds the same port (for a
|
|
29
|
+
process left running in another terminal).
|
|
30
|
+
|
|
26
31
|
Startup order: banner → build steps → server ready → `Ready` summary. The
|
|
27
32
|
summary is printed once both the build and the server are ready; otherwise it
|
|
28
33
|
got buried among the build lines arriving afterwards.
|
|
@@ -75,7 +80,7 @@ WATCH_DIRS = [
|
|
|
75
80
|
The `jskelet.config.mjs` file itself is watched as well: when the config
|
|
76
81
|
changes, both the server and the build must come up with the new settings.
|
|
77
82
|
|
|
78
|
-
Watched extensions: `.js`, `.mjs`, `.json`, `.ejs`.
|
|
83
|
+
Watched extensions: `.js`, `.mjs`, `.json`, `.jsk`, `.ejs`.
|
|
79
84
|
|
|
80
85
|
`views` is watched too, because most components live in
|
|
81
86
|
`views/components/**.js` and, since those modules are imported into the server
|
package/docs/en/10-deployment.md
CHANGED
|
@@ -23,6 +23,10 @@ runs the build itself. In a Docker image the build has already happened, so this
|
|
|
23
23
|
is a no-op; the point is that someone running `npm start` directly does not end
|
|
24
24
|
up with an unstyled page.
|
|
25
25
|
|
|
26
|
+
If the listen port is already taken, the process **does not start** (PID + hint).
|
|
27
|
+
`jskelet start --murder` kills that listener and binds — useful for a leftover
|
|
28
|
+
dev process; production orchestrators usually do not need it.
|
|
29
|
+
|
|
26
30
|
When the server is ready it prints a single line:
|
|
27
31
|
|
|
28
32
|
```
|
|
@@ -64,7 +68,8 @@ Using both sources together blurs which value actually applies; not shipping a
|
|
|
64
68
|
`.env` in the prod image is the cleanest option.
|
|
65
69
|
|
|
66
70
|
**Secret keys must not go into the `clientEnv` list:** those values are embedded
|
|
67
|
-
into the client bundle as plain text ([08-build.md](./08-build.md)).
|
|
71
|
+
into the client bundle as plain text ([08-build.md](./08-build.md)). Secret-like
|
|
72
|
+
names (`SECRET`, `API_KEY`, …) now fail the build.
|
|
68
73
|
|
|
69
74
|
## Docker
|
|
70
75
|
|
package/docs/en/11-migration.md
CHANGED
|
@@ -8,6 +8,29 @@ modeled on the subset of Next that people actually use — concepts like the
|
|
|
8
8
|
will feel familiar. The *reasons* behind the differences are in
|
|
9
9
|
[02-architecture.md](./02-architecture.md).
|
|
10
10
|
|
|
11
|
+
## `jskelet migrate` (codemod)
|
|
12
|
+
|
|
13
|
+
Run the codemod against an App Router tree. Babel (`@babel/parser`,
|
|
14
|
+
`@babel/types`) ships with JSkelet — no extra install.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx jskelet migrate scan ../my-next-app
|
|
18
|
+
npx jskelet migrate apply ../my-next-app --out . --write
|
|
19
|
+
npx jskelet migrate config ../my-next-app --write
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Command | What it does |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| `migrate` / `migrate scan` | Inventory pages, layouts, `"use client"` modules, blockers (nested layouts, Server Actions, Suspense). |
|
|
25
|
+
| `migrate apply` | **Automatic convert:** `page.*` → feature controller + `.jsk`; presentational components → `views/components/*.js`; clients → island `mount()` stubs. Default is dry-run; pass `--write`. Never overwrites (conflicts get a `.migrate` suffix). |
|
|
26
|
+
| `migrate config` | Draft `jskelet.config.mjs` from `next.config` (`headers` / `redirects` / `rewrites`, `images.widths`, `NEXT_PUBLIC_*` → `clientEnv`). |
|
|
27
|
+
|
|
28
|
+
Flags: `--out <dir>`, `--only pages,components,islands`, `--json`, `--strict` (exit 1 on partial/skipped).
|
|
29
|
+
|
|
30
|
+
**Converted automatically:** `className`, `{{ }}` / `{{{ }}}`, `{#if}` / `{#each}`, `next/image` → `<Image />`, `next/link` → `<Link />`, `dangerouslySetInnerHTML`, `revalidate`, simple controller prelude (`await` data + `notFound()`).
|
|
31
|
+
|
|
32
|
+
**Not converted (reported):** React hooks, Server Actions, nested layout flattening, Streaming/Suspense, client-side routing. Confidence per file is `ok` / `partial` / `skipped`.
|
|
33
|
+
|
|
11
34
|
## Equivalence table
|
|
12
35
|
|
|
13
36
|
### Configuration
|
|
@@ -31,8 +54,8 @@ will feel familiar. The *reasons* behind the differences are in
|
|
|
31
54
|
| `app/page.js` (file-based routing) | `app.get(...)` inside `routes/*.mjs` | The order is written explicitly ([03](./03-routing.md)) |
|
|
32
55
|
| `app/[slug]/page.js` | `app.get("/:slug", route(...))` | Express pattern syntax |
|
|
33
56
|
| `params`, `searchParams` | `ctx.params`, `ctx.query` | The controller's single argument |
|
|
34
|
-
| `layout.js` | `views/layout.
|
|
35
|
-
| Server component (RSC) | Controller +
|
|
57
|
+
| `layout.js` | `views/layout.jsk` + `hooks.layoutContext()` | A single layout; no nested layouts |
|
|
58
|
+
| Server component (RSC) | Controller + `.jsk` template + `views/components/**` | A function returns an HTML string |
|
|
36
59
|
| Client component (`"use client"`) | Island (`data-island` + `mount`) | The whole page is not hydrated ([05](./05-islands.md)) |
|
|
37
60
|
| `notFound()` | `notFound()` | Same name, same control flow |
|
|
38
61
|
| `redirect()` | `redirect()` (307) | For permanent, `permanentRedirect()` (308) |
|
|
@@ -88,8 +111,11 @@ Account for these from the start in your migration plan:
|
|
|
88
111
|
|
|
89
112
|
- **React itself.** Components turn into functions that return HTML strings. No
|
|
90
113
|
JSX, no hooks, no virtual DOM.
|
|
91
|
-
- **TypeScript.**
|
|
92
|
-
|
|
114
|
+
- **TypeScript.** Framework source is plain JS + JSDoc and publishes `.d.ts` for
|
|
115
|
+
consumers. Client entries and islands may be `.ts` / `.mts` (esbuild strips
|
|
116
|
+
types; the manifest key stays `*.js`). Server routes, hooks and
|
|
117
|
+
`jskelet.config.mjs` remain Node ESM JavaScript — use `checkJs: true` in
|
|
118
|
+
`jsconfig.json` for editor checking there.
|
|
93
119
|
- **Nested layouts.** There is a single layout; you share common sections with
|
|
94
120
|
EJS `include` or component functions.
|
|
95
121
|
- **Streaming / Suspense / partial prerendering.** The response is produced as a
|
|
@@ -172,12 +198,12 @@ export default function register(app, { route, notFound }) {
|
|
|
172
198
|
}
|
|
173
199
|
```
|
|
174
200
|
|
|
175
|
-
```
|
|
176
|
-
|
|
201
|
+
```jsk
|
|
202
|
+
{# views/pages/article.jsk #}
|
|
177
203
|
<article class="wrapper">
|
|
178
|
-
<h1 class="text-3xl font-bold"
|
|
179
|
-
|
|
180
|
-
<div
|
|
204
|
+
<h1 class="text-3xl font-bold">{{ article.title }}</h1>
|
|
205
|
+
<Image :src="article.cover" :alt="article.title" priority :width="1200" :height="630" />
|
|
206
|
+
<div>{{{ article.body }}}</div>
|
|
181
207
|
</article>
|
|
182
208
|
```
|
|
183
209
|
|
|
@@ -190,7 +216,8 @@ single upstream request is made ([06-caching.md](./06-caching.md)).
|
|
|
190
216
|
### 1. Set up the skeleton (half a day)
|
|
191
217
|
|
|
192
218
|
Run `npx jskelet init` in a new directory and watch `jskelet dev` come up. Leave
|
|
193
|
-
the existing Next project as it is; let the migration run in parallel.
|
|
219
|
+
the existing Next project as it is; let the migration run in parallel. Optionally
|
|
220
|
+
run `jskelet migrate scan <next-root>` first to list pages and blockers.
|
|
194
221
|
|
|
195
222
|
Carry over the `paths` aliases from your `jsconfig.json` — prefixes like `@/`
|
|
196
223
|
work the same way both on the server and in the bundle
|
|
@@ -198,6 +225,8 @@ work the same way both on the server and in the bundle
|
|
|
198
225
|
|
|
199
226
|
### 2. Translate `next.config.mjs` (1-2 hours)
|
|
200
227
|
|
|
228
|
+
`jskelet migrate config <next-root> --write` drafts most of this. Then review:
|
|
229
|
+
|
|
201
230
|
The `headers()`, `redirects()` and `rewrites()` sections are copied almost
|
|
202
231
|
verbatim. Check the pattern syntax: JSkelet supports the `:slug`, `:path*`,
|
|
203
232
|
`/a-:b` and `/:path*.svg` forms; more complex `path-to-regexp` expressions are
|
|
@@ -218,9 +247,9 @@ they are copied as-is. Make two changes:
|
|
|
218
247
|
|
|
219
248
|
### 4. Set up the layout (half a day)
|
|
220
249
|
|
|
221
|
-
Translate `app/layout.jsx` into `views/layout.
|
|
222
|
-
default layout (`
|
|
223
|
-
is the fastest path.
|
|
250
|
+
Translate `app/layout.jsx` into `views/layout.jsk` (or let `migrate apply` draft
|
|
251
|
+
it). Copying the framework's default layout (`jskelet/layout` → `.jsk`) and
|
|
252
|
+
editing it is the fastest path.
|
|
224
253
|
|
|
225
254
|
If you fetch data inside `layout.jsx` (navigation, site settings), move it into
|
|
226
255
|
`hooks.layoutContext()`: it runs in parallel with the body render, and every
|
|
@@ -231,6 +260,10 @@ into `hooks.metadata()`.
|
|
|
231
260
|
|
|
232
261
|
### 5. Translate the components (the longest step)
|
|
233
262
|
|
|
263
|
+
`jskelet migrate apply --only components --write` converts presentational
|
|
264
|
+
components that are props + JSX with no hooks. Everything else you finish by
|
|
265
|
+
hand:
|
|
266
|
+
|
|
234
267
|
Every React component turns into a function:
|
|
235
268
|
|
|
236
269
|
```jsx
|
|
@@ -264,8 +297,9 @@ Keep components small and pure; leave data fetching in the controller.
|
|
|
264
297
|
|
|
265
298
|
### 6. Migrate the pages (hours per page)
|
|
266
299
|
|
|
267
|
-
|
|
268
|
-
|
|
300
|
+
`jskelet migrate apply --only pages --write` splits each `page.*` into a
|
|
301
|
+
feature controller plus a `.jsk` template. Review `partial` / `skipped` rows,
|
|
302
|
+
then finish the TODO markers. File them with the order in mind:
|
|
269
303
|
|
|
270
304
|
```
|
|
271
305
|
routes/
|
|
@@ -342,7 +376,8 @@ especially for verifying that the redirect rules are correct.
|
|
|
342
376
|
## Common mistakes during migration
|
|
343
377
|
|
|
344
378
|
- **Forgetting `esc()`.** Writing `${value}` out of JSX habit means XSS. In
|
|
345
|
-
templates
|
|
379
|
+
`.jsk` templates use `{{ }}` (escaped) vs `{{{ }}}` (raw); in components call
|
|
380
|
+
`esc()` yourself.
|
|
346
381
|
- **Opening a new directory without adding `@source`.** The classes are silently
|
|
347
382
|
dropped.
|
|
348
383
|
- **Putting the catch-all route in the wrong order.** `/:slug` always goes last.
|
|
@@ -145,7 +145,9 @@ export default {
|
|
|
145
145
|
sharedCookieRoots: [".investvio.com", ".localhost"],
|
|
146
146
|
},
|
|
147
147
|
auth: {
|
|
148
|
-
crossSubdomainHandoff:
|
|
148
|
+
crossSubdomainHandoff: {
|
|
149
|
+
allowedCookieNames: ["sid"], // required allowlist
|
|
150
|
+
},
|
|
149
151
|
},
|
|
150
152
|
};
|
|
151
153
|
```
|
|
@@ -210,17 +212,20 @@ a **read-back** runs; if the browser rejected the Domain, `handoff: true`.
|
|
|
210
212
|
|
|
211
213
|
With `auth.crossSubdomainHandoff` on:
|
|
212
214
|
|
|
213
|
-
1. `POST /_jskelet/auth/handoff` `{ name, value, next }` → `{ url }` (with
|
|
215
|
+
1. `POST /_jskelet/auth/handoff` `{ name, value, next }` → `{ url }` (with
|
|
216
|
+
`?handoff=`). Mint is mounted **after** the CSRF middleware; `name` must be
|
|
217
|
+
in `allowedCookieNames` and an RFC 6265 token.
|
|
214
218
|
2. On the target host a GET middleware redeems the one-time ticket, sets the
|
|
215
219
|
cookie (shared Domain first, else host-only), and 303-redirects without
|
|
216
220
|
`handoff`
|
|
217
221
|
|
|
218
222
|
`next` must be under the same `sharedCookieRoots`. Tickets live ~60s in process
|
|
219
|
-
memory. Do not put a JWT in the URL.
|
|
223
|
+
memory, with pending-ticket and per-IP mint limits. Do not put a JWT in the URL.
|
|
220
224
|
|
|
221
225
|
The `window.name` bridge is the cookie-less fallback:
|
|
222
226
|
`handoffViaWindowName` on the source page, `consumeWindowNameHandoff` on the
|
|
223
|
-
target.
|
|
227
|
+
target. Prefer the server handoff when possible — `window.name` remains readable
|
|
228
|
+
across origins in the same tab.
|
|
224
229
|
|
|
225
230
|
## CSRF
|
|
226
231
|
|
package/docs/en/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
JSkelet is a framework that "feels frameworkless", built for SEO- and
|
|
4
4
|
speed-focused sites: it produces complete HTML on the server with Express 5 +
|
|
5
|
-
EJS
|
|
6
|
-
|
|
7
|
-
lives in process memory with
|
|
8
|
-
plain JavaScript
|
|
5
|
+
build-time `.jsk` (EJS is an optional legacy peer), adds interactivity with
|
|
6
|
+
vanilla JS islands, compiles CSS into a single stylesheet with Tailwind v4, and
|
|
7
|
+
instead of ISR uses an HTML TTL cache that lives in process memory with
|
|
8
|
+
stale-while-revalidate. No React; the framework source is plain JavaScript with
|
|
9
|
+
JSDoc. Apps may write client islands and entries in TypeScript, and the
|
|
10
|
+
published package ships declaration files.
|
|
9
11
|
|
|
10
12
|
This directory is the full reference for the framework. To read it in order,
|
|
11
13
|
start from the beginning; if you are looking for a specific topic, go straight
|
|
@@ -22,7 +24,7 @@ change one, change the other.
|
|
|
22
24
|
| [01-getting-started.md](./01-getting-started.md) | Installation, `jskelet init`, first route, first island, directory structure, CLI commands |
|
|
23
25
|
| [02-architecture.md](./02-architecture.md) | Architectural decisions and their rationale: the island model, complete server HTML, cache strategy, middleware order |
|
|
24
26
|
| [03-routing.md](./03-routing.md) | The route module contract, load order, the controller contract, `ctx`, `notFound`/`redirect`, config redirects/rewrites |
|
|
25
|
-
| [04-rendering.md](./04-rendering.md) |
|
|
27
|
+
| [04-rendering.md](./04-rendering.md) | `.jsk` layout/pages, automatic component registration, `html`/`tags`, metadata → `<head>`, hooks; EJS legacy |
|
|
26
28
|
| [05-islands.md](./05-islands.md) | The `data-island` contract, hydration strategies, `client/entries/*`, `createStore`, DOM helpers, `startSafeImages` |
|
|
27
29
|
| [06-caching.md](./06-caching.md) | `withHtmlCache`, `revalidate`, stale-while-revalidate, the cache key, `X-JSkelet-Cache`, in-request cache, degraded render, prewarm |
|
|
28
30
|
| [07-configuration.md](./07-configuration.md) | Full `jskelet.config.mjs` reference, the `source` pattern syntax, environment variable table |
|