jskelet 0.5.5 → 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 +95 -0
- 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 +40 -18
- package/docs/08-build.md +15 -9
- 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 +40 -20
- package/docs/en/08-build.md +16 -10
- 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 +11 -1
- 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 +1 -1
- 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
|
|
|
@@ -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
|
|
|
@@ -555,8 +563,9 @@ When `remote.allowHosts` is set, also proxies remote images at runtime
|
|
|
555
563
|
|
|
556
564
|
If `false` is given, neither surface runs. The build step requires `sharp` and
|
|
557
565
|
never runs on a watch pass. With remote enabled, `sharp` is also needed at
|
|
558
|
-
**runtime**; without it the optimizer 302-redirects to the source URL.
|
|
559
|
-
|
|
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).
|
|
560
569
|
|
|
561
570
|
```js
|
|
562
571
|
images: {
|
|
@@ -588,7 +597,10 @@ that is not in the list returns `undefined` instead of crashing.
|
|
|
588
597
|
clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
589
598
|
```
|
|
590
599
|
|
|
591
|
-
**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).
|
|
592
604
|
|
|
593
605
|
## `headers()`
|
|
594
606
|
|
|
@@ -598,6 +610,7 @@ clientEnv: ["PUBLIC_WS_URL", "PUBLIC_CDN_ORIGIN"]
|
|
|
598
610
|
Response headers by path pattern. The framework only writes long-lived cache
|
|
599
611
|
headers for static files; every other header (CSP, COOP, HSTS,
|
|
600
612
|
X-Frame-Options…) comes from here and takes precedence over the defaults.
|
|
613
|
+
Production sites should at least define the security headers below.
|
|
601
614
|
|
|
602
615
|
**All** matching rules are applied (unlike redirects, it does not stop at the
|
|
603
616
|
first match), in order; if two rules write the same header, the later one wins.
|
|
@@ -612,11 +625,18 @@ async headers() {
|
|
|
612
625
|
source: "/:path*",
|
|
613
626
|
headers: [
|
|
614
627
|
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
|
|
628
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
615
629
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
630
|
+
{
|
|
631
|
+
key: "Permissions-Policy",
|
|
632
|
+
value: "camera=(), microphone=(), geolocation=()",
|
|
633
|
+
},
|
|
616
634
|
{
|
|
617
635
|
key: "Content-Security-Policy",
|
|
618
|
-
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'",
|
|
619
637
|
},
|
|
638
|
+
// Only when you terminate HTTPS yourself:
|
|
639
|
+
// { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
|
|
620
640
|
],
|
|
621
641
|
},
|
|
622
642
|
{
|
|
@@ -1106,7 +1126,7 @@ and no warning is printed.
|
|
|
1106
1126
|
| Variable | Who reads it | Default | Meaning |
|
|
1107
1127
|
| --- | --- | --- | --- |
|
|
1108
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. |
|
|
1109
|
-
| `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 |
|
|
1110
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 |
|
|
1111
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) |
|
|
1112
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
|
|
|
@@ -285,7 +289,7 @@ Local file names:
|
|
|
285
289
|
`0 0 256 256` (recommended for Phosphor / `icon()` compatibility).
|
|
286
290
|
- The scanned directories default to `views`, `client`, `routes`, `lib`,
|
|
287
291
|
`features`, `shared`; they can be changed with `icons.scan`. Scanned
|
|
288
|
-
extensions: `.ejs`, `.jsk`, `.js`, `.mjs`.
|
|
292
|
+
extensions: `.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
|
|
289
293
|
- Weights: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. An
|
|
290
294
|
unrecognised weight counts as `regular`.
|
|
291
295
|
|
|
@@ -355,7 +359,9 @@ and `image()` falls back to the original file. It never runs on a watch pass.
|
|
|
355
359
|
When `images.remote.allowHosts` is set, `createApp` mounts `/_jskelet/image`.
|
|
356
360
|
CMS / CDN covers never enter the build, so `image()` rewrites those host URLs to
|
|
357
361
|
`?url=&w=`; the endpoint encodes webp with sharp and stores files under
|
|
358
|
-
`.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).
|
|
359
365
|
|
|
360
366
|
## Precompress
|
|
361
367
|
|
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 |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A framework that feels like no framework: Express 5 + build-time .jsk (
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "A framework that feels like no framework: Express 5 + build-time .jsk SSR (optional EJS peer), vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Ayberk Enis",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"ssr",
|
|
18
18
|
"islands",
|
|
19
19
|
"express",
|
|
20
|
-
"
|
|
20
|
+
"jsk",
|
|
21
21
|
"esbuild",
|
|
22
22
|
"tailwind",
|
|
23
23
|
"seo",
|
|
@@ -28,19 +28,42 @@
|
|
|
28
28
|
},
|
|
29
29
|
"main": "./src/index.js",
|
|
30
30
|
"exports": {
|
|
31
|
-
".":
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"./
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./types/index.d.ts",
|
|
33
|
+
"default": "./src/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./server": {
|
|
36
|
+
"types": "./types/index.d.ts",
|
|
37
|
+
"default": "./src/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./client": {
|
|
40
|
+
"types": "./types/client/index.d.ts",
|
|
41
|
+
"default": "./src/client/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./html": {
|
|
44
|
+
"types": "./types/views/helpers/html.d.ts",
|
|
45
|
+
"default": "./src/views/helpers/html.js"
|
|
46
|
+
},
|
|
47
|
+
"./tags": {
|
|
48
|
+
"types": "./types/views/helpers/tags.d.ts",
|
|
49
|
+
"default": "./src/views/helpers/tags.js"
|
|
50
|
+
},
|
|
51
|
+
"./cookies": {
|
|
52
|
+
"types": "./types/http/cookies-entry.d.ts",
|
|
53
|
+
"default": "./src/http/cookies-entry.js"
|
|
54
|
+
},
|
|
55
|
+
"./log": {
|
|
56
|
+
"types": "./types/log.d.mts",
|
|
57
|
+
"default": "./src/log.mjs"
|
|
58
|
+
},
|
|
38
59
|
"./register": "./src/runtime/register.mjs",
|
|
39
|
-
"./layout": "./src/templates/layout.
|
|
60
|
+
"./layout": "./src/templates/layout.jsk",
|
|
61
|
+
"./layout/ejs": "./src/templates/layout.ejs"
|
|
40
62
|
},
|
|
41
63
|
"files": [
|
|
42
64
|
"bin",
|
|
43
65
|
"src",
|
|
66
|
+
"types",
|
|
44
67
|
"docs",
|
|
45
68
|
"README.md",
|
|
46
69
|
"AGENTS.md",
|
|
@@ -53,12 +76,16 @@
|
|
|
53
76
|
"scripts": {
|
|
54
77
|
"lint": "eslint",
|
|
55
78
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
79
|
+
"types": "tsc -p tsconfig.types.json",
|
|
80
|
+
"prepublishOnly": "npm run types",
|
|
81
|
+
"test:framework-layout": "node scripts/compile-framework-layout.mjs --check",
|
|
56
82
|
"example:minimal": "npm --prefix examples/minimal run dev",
|
|
57
83
|
"example:blog": "npm --prefix examples/blog run dev",
|
|
58
84
|
"example:dashboard": "npm --prefix examples/dashboard run dev"
|
|
59
85
|
},
|
|
60
86
|
"dependencies": {
|
|
61
|
-
"
|
|
87
|
+
"@babel/parser": "^8.0.6",
|
|
88
|
+
"@babel/types": "^8.0.6",
|
|
62
89
|
"esbuild": "^0.28.2",
|
|
63
90
|
"express": "^5.2.1",
|
|
64
91
|
"tailwind-merge": "^3.5.0"
|
|
@@ -66,6 +93,7 @@
|
|
|
66
93
|
"peerDependencies": {
|
|
67
94
|
"@phosphor-icons/core": "^2.1.1",
|
|
68
95
|
"@tailwindcss/postcss": "^4.3.3",
|
|
96
|
+
"ejs": "^6.0.1",
|
|
69
97
|
"ioredis": "^5.4.0 || ^6.0.0",
|
|
70
98
|
"lightningcss": "^1.32.0",
|
|
71
99
|
"postcss": "^8.5.26",
|
|
@@ -79,6 +107,9 @@
|
|
|
79
107
|
"@tailwindcss/postcss": {
|
|
80
108
|
"optional": true
|
|
81
109
|
},
|
|
110
|
+
"ejs": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
82
113
|
"ioredis": {
|
|
83
114
|
"optional": true
|
|
84
115
|
},
|
|
@@ -97,7 +128,11 @@
|
|
|
97
128
|
},
|
|
98
129
|
"devDependencies": {
|
|
99
130
|
"@eslint/js": "^9",
|
|
131
|
+
"@types/express": "^5.0.6",
|
|
132
|
+
"@types/node": "^26.6.1",
|
|
133
|
+
"ejs": "^6.0.1",
|
|
100
134
|
"eslint": "^9",
|
|
101
|
-
"globals": "^16"
|
|
135
|
+
"globals": "^16",
|
|
136
|
+
"typescript": "^7.0.2"
|
|
102
137
|
}
|
|
103
|
-
}
|
|
138
|
+
}
|