jskelet 0.6.3 → 0.6.4
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 +136 -136
- package/CHANGELOG.md +628 -620
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -310
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +667 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1467 -1443
- package/docs/07-yapilandirma.md +1208 -1197
- package/docs/08-build.md +429 -429
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +348 -338
- package/docs/12-panel-ve-oturum.md +479 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -329
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +675 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1476 -1453
- package/docs/en/07-configuration.md +1229 -1219
- package/docs/en/08-build.md +447 -447
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +351 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +489 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +541 -534
- package/src/config/index.js +1500 -1469
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +232 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -70
- package/src/server/cache-control.js +45 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -553
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -233
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1196
- package/src/server/image-optimizer.js +500 -500
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -193
- package/src/server/logs/pipeline.js +165 -165
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -121
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +369 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1082
- package/src/server/redis.js +588 -588
- package/src/server/render.js +910 -910
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +6 -0
- package/types/config/index.d.ts +6 -0
- package/types/server/cache-control.d.ts +28 -0
- package/types/server/og-image.d.ts +5 -0
|
@@ -1,298 +1,298 @@
|
|
|
1
|
-
# 01 — Getting started
|
|
2
|
-
|
|
3
|
-
This document explains how to get JSkelet running from scratch: installing the
|
|
4
|
-
package, scaffolding the skeleton with `jskelet init`, writing your first route
|
|
5
|
-
and your first island, what the resulting directory layout means, and the CLI
|
|
6
|
-
commands. By the end you will have a page in the browser that is rendered
|
|
7
|
-
on the server, cached, and whose island hydrates on visibility. For the
|
|
8
|
-
*reasons* behind the decisions see
|
|
9
|
-
[02-architecture.md](./02-architecture.md), and for the full reference of every
|
|
10
|
-
config field mentioned here see
|
|
11
|
-
[07-configuration.md](./07-configuration.md).
|
|
12
|
-
|
|
13
|
-
## Requirements
|
|
14
|
-
|
|
15
|
-
- **Node.js 22 or newer.** `package.json` → `engines` enforces this. The
|
|
16
|
-
framework uses new Node surfaces such as `node:async_hooks`,
|
|
17
|
-
`fs.readdirSync(..., { recursive: true })`, `--env-file-if-exists` and
|
|
18
|
-
`module.register()` directly.
|
|
19
|
-
- If you are going to use Tailwind CSS, the `postcss`, `@tailwindcss/postcss`
|
|
20
|
-
and `tailwindcss` packages. These are **optional peer dependencies** of the
|
|
21
|
-
framework; if they are not installed the CSS step is skipped and the site
|
|
22
|
-
stays unstyled but working (details: [08-build.md](./08-build.md)).
|
|
23
|
-
|
|
24
|
-
## Installation
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
mkdir my-site && cd my-site
|
|
28
|
-
npm init -y
|
|
29
|
-
npm pkg set type=module
|
|
30
|
-
npm install jskelet
|
|
31
|
-
npm install -D postcss @tailwindcss/postcss tailwindcss lightningcss
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
`type: "module"` is required: route modules, components and the config file are
|
|
35
|
-
loaded as ESM.
|
|
36
|
-
|
|
37
|
-
Then add the scripts to `package.json`:
|
|
38
|
-
|
|
39
|
-
```json
|
|
40
|
-
{
|
|
41
|
-
"scripts": {
|
|
42
|
-
"dev": "jskelet dev",
|
|
43
|
-
"build": "jskelet build",
|
|
44
|
-
"start": "jskelet start"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## `jskelet init`
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npx jskelet init
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
This command installs a working minimal skeleton into the directory you are in.
|
|
56
|
-
It **does not overwrite existing files**: running it a second time only fills in
|
|
57
|
-
what is missing and prints the number of skipped files as a warning. The goal is
|
|
58
|
-
to skip the "I installed it but nothing works" stage entirely — `jskelet dev`
|
|
59
|
-
runs right afterwards.
|
|
60
|
-
|
|
61
|
-
The files it creates (feature-first + `.jsk`):
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
jskelet.config.mjs config: brand, preconnect, cache(), hooks
|
|
65
|
-
features/home/index.js the "/" route
|
|
66
|
-
features/home/views/pages/home.jsk home page template
|
|
67
|
-
features/home/views/components/button.js example component (<Button />)
|
|
68
|
-
features/home/client/counter.js example island
|
|
69
|
-
features/home/server/.gitkeep
|
|
70
|
-
views/pages/not-found.jsk app-wide 404
|
|
71
|
-
client/entries/main.js island bootstrap
|
|
72
|
-
styles/globals.css Tailwind entry + @source directives
|
|
73
|
-
jsconfig.json checkJs + the "@/*" alias
|
|
74
|
-
.gitignore node_modules/, .jskelet/, public/assets/, .env
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
To grow: `npx jskelet generate feature <name>` (or `page` / `island`).
|
|
78
|
-
|
|
79
|
-
Then:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
npm run dev
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
In the terminal you will see a banner, aligned build lines and a `Ready`
|
|
86
|
-
summary; `http://localhost:3000` serves the page. A dev overlay bubble sits in
|
|
87
|
-
the bottom right corner, opened with `Alt+D`
|
|
88
|
-
([09-dev-tools.md](./09-dev-tools.md)).
|
|
89
|
-
|
|
90
|
-
## Directory layout
|
|
91
|
-
|
|
92
|
-
None of the directory names are fixed; all of them can be overridden via
|
|
93
|
-
`jskelet.config.mjs` → `paths`. The values below are the defaults
|
|
94
|
-
(`src/config/defaults.js`).
|
|
95
|
-
|
|
96
|
-
| Directory | Default | Contents |
|
|
97
|
-
| --- | --- | --- |
|
|
98
|
-
| `views` | `views` | App-wide layout, pages and components |
|
|
99
|
-
| `features` | `features` | Feature slices (`<name>/{server,views,client}`) |
|
|
100
|
-
| `shared` | `shared` | Cross-feature server/views/client |
|
|
101
|
-
| `public` | `public` | Static files; build output is written here too |
|
|
102
|
-
| `client` | `client` | Island runtime sources and entries |
|
|
103
|
-
| `routes` | `routes` | Route modules (loaded before features) |
|
|
104
|
-
| `styles` | `styles/globals.css` | Tailwind/PostCSS entry **file** |
|
|
105
|
-
| `generated` | `.jskelet` | Intermediate build output: `manifest.json`, `metafile.json`, `images.json`, `templates/` |
|
|
106
|
-
|
|
107
|
-
In addition to these the framework always derives two paths and accepts no
|
|
108
|
-
separate setting for them: `public/assets` (hashed build output) and
|
|
109
|
-
`public/fonts` (self-hosted fonts).
|
|
110
|
-
|
|
111
|
-
A typical project (close to what `jskelet init` writes):
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
my-site/
|
|
115
|
-
├── jskelet.config.mjs
|
|
116
|
-
├── jsconfig.json
|
|
117
|
-
├── features/
|
|
118
|
-
│ └── home/
|
|
119
|
-
│ ├── index.js
|
|
120
|
-
│ ├── server/
|
|
121
|
-
│ ├── views/
|
|
122
|
-
│ │ ├── pages/home.jsk
|
|
123
|
-
│ │ └── components/button.js
|
|
124
|
-
│ └── client/counter.js
|
|
125
|
-
├── views/
|
|
126
|
-
│ └── pages/not-found.jsk
|
|
127
|
-
├── client/
|
|
128
|
-
│ └── entries/main.js
|
|
129
|
-
├── styles/
|
|
130
|
-
│ └── globals.css
|
|
131
|
-
├── public/
|
|
132
|
-
│ └── (static files; build → public/assets)
|
|
133
|
-
└── .jskelet/
|
|
134
|
-
├── manifest.json
|
|
135
|
-
└── templates/
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Your first route
|
|
139
|
-
|
|
140
|
-
Route modules **do not derive URLs automatically from the file system**; every
|
|
141
|
-
module writes its own paths explicitly with `app.get(...)`. The module contract:
|
|
142
|
-
a default export or a named export called `register`, with the signature
|
|
143
|
-
`(app, api)`.
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
// features/home/index.js
|
|
147
|
-
export default function register(app, { route }) {
|
|
148
|
-
app.get(
|
|
149
|
-
"/",
|
|
150
|
-
route(
|
|
151
|
-
async () => ({
|
|
152
|
-
view: "pages/home",
|
|
153
|
-
metadata: { title: "Home" },
|
|
154
|
-
data: { message: "JSkelet is running" },
|
|
155
|
-
}),
|
|
156
|
-
{ revalidate: 60 },
|
|
157
|
-
),
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
The `api` object comes with `route`, `renderView`, `renderPage`, `notFound`,
|
|
163
|
-
`redirect` and `permanentRedirect` ready to use, so route files don't have to
|
|
164
|
-
import them one by one from the framework. `route()` wraps the controller: the
|
|
165
|
-
HTML cache, the notFound/redirect control flow, compression and the
|
|
166
|
-
`X-JSkelet-Cache` header all come from it. The controller's only job is to
|
|
167
|
-
return a page definition.
|
|
168
|
-
|
|
169
|
-
If you use `routes/`, the `10-` prefix in the file name determines load order;
|
|
170
|
-
put catch-alls such as `/:slug` in a higher-numbered file. Feature `index.js`
|
|
171
|
-
files are appended alphabetically after the `routes/` scan. Details:
|
|
172
|
-
[03-routing.md](./03-routing.md).
|
|
173
|
-
|
|
174
|
-
The template side is `.jsk` (compiled at build time):
|
|
175
|
-
|
|
176
|
-
```html
|
|
177
|
-
{# features/home/views/pages/home.jsk #}
|
|
178
|
-
<section class="wrapper">
|
|
179
|
-
<h1>{{ metadata.title }}</h1>
|
|
180
|
-
<p>{{ message }}</p>
|
|
181
|
-
<Button text="Example component" />
|
|
182
|
-
<div data-island="counter" data-island-props='{"start":0}'></div>
|
|
183
|
-
</section>
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
`Button` comes from the `button` named export in
|
|
187
|
-
`features/home/views/components/button.js` — PascalCase tag, no import
|
|
188
|
-
([04-rendering.md](./04-rendering.md)).
|
|
189
|
-
|
|
190
|
-
## Your first island
|
|
191
|
-
|
|
192
|
-
An island is a small module that adds behaviour to the HTML the server
|
|
193
|
-
produced. The contract has two parts.
|
|
194
|
-
|
|
195
|
-
**1. A marker in the template:** give an element `data-island="ad"`. Props are
|
|
196
|
-
carried as JSON inside `data-island-props`.
|
|
197
|
-
|
|
198
|
-
```ejs
|
|
199
|
-
<div data-island="counter" data-island-props='{"start":5}'></div>
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**2. A `mount` in the module:** the island provides a named export called
|
|
203
|
-
`mount(element, props)`.
|
|
204
|
-
|
|
205
|
-
```js
|
|
206
|
-
// features/home/client/counter.js
|
|
207
|
-
/**
|
|
208
|
-
* @param {HTMLElement} element
|
|
209
|
-
* @param {{ start?: number }} props
|
|
210
|
-
*/
|
|
211
|
-
export function mount(element, props) {
|
|
212
|
-
let value = props.start ?? 0;
|
|
213
|
-
|
|
214
|
-
const button = document.createElement("button");
|
|
215
|
-
button.type = "button";
|
|
216
|
-
|
|
217
|
-
const paint = () => {
|
|
218
|
-
button.textContent = `Clicks: ${value}`;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
button.addEventListener("click", () => {
|
|
222
|
-
value += 1;
|
|
223
|
-
paint();
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
paint();
|
|
227
|
-
element.append(button);
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
**3. Registration:** `client/entries/main.js` maps the island name to a dynamic
|
|
232
|
-
import and starts the runtime.
|
|
233
|
-
|
|
234
|
-
```js
|
|
235
|
-
import { registerAll, start } from "jskelet/client";
|
|
236
|
-
|
|
237
|
-
registerAll({
|
|
238
|
-
counter: () => import("../../features/home/client/counter.js"),
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
start();
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
It is critical that the values are dynamic imports: the module is downloaded
|
|
245
|
-
only if that island actually exists on the page **and** when the element becomes
|
|
246
|
-
visible. In other words, growing this map does not grow the initial payload.
|
|
247
|
-
Hydration strategies (`data-island-eager`, `data-island-idle`) and the complete
|
|
248
|
-
runtime API are in [05-islands.md](./05-islands.md).
|
|
249
|
-
|
|
250
|
-
## CLI commands
|
|
251
|
-
|
|
252
|
-
`bin/jskelet.mjs` offers these subcommands. Each runs in a separate Node process;
|
|
253
|
-
the reason is that `dev` manages two long-lived processes and the server needs
|
|
254
|
-
ESM resolve hooks (`--import`) at process start.
|
|
255
|
-
|
|
256
|
-
| Command | What it does |
|
|
257
|
-
| --- | --- |
|
|
258
|
-
| `jskelet dev` | Build watch + server, in a single terminal. Live reload, CSS hot-swap, dev overlay. `NODE_ENV=development`. Refuses to start if the port is busy; `--murder` kills the listener and binds. |
|
|
259
|
-
| `jskelet build` | One-shot prod build: fonts → icon sprite → CSS → client JS → images → manifest → precompress. `production` if `NODE_ENV` is not given. |
|
|
260
|
-
| `jskelet start` | Prod server. If there is no build output it produces it first. `production` if `NODE_ENV` is not given. Same port behaviour as `dev` (`--murder`). |
|
|
261
|
-
| `jskelet init` | Installs a feature-first `.jsk` skeleton into the current directory; leaves existing files alone. |
|
|
262
|
-
| `jskelet generate` | Scaffolds a `feature` / `page` / `island`. |
|
|
263
|
-
| `jskelet migrate` | Next.js App Router → JSkelet codemod (`scan` / `apply` / `config`). See [11-migration.md](./11-migration.md). |
|
|
264
|
-
|
|
265
|
-
An unknown command, or a call with no arguments, prints the usage text.
|
|
266
|
-
|
|
267
|
-
Every command runs with two Node flags:
|
|
268
|
-
|
|
269
|
-
- `--env-file=.env` — passed only if the file really exists; otherwise no flag
|
|
270
|
-
is added and no warning is printed.
|
|
271
|
-
- `--import <register.mjs>` — installs the ESM hooks that resolve the
|
|
272
|
-
`compilerOptions.paths` aliases (`@/lib/x`) from `jsconfig.json` /
|
|
273
|
-
`tsconfig.json` and extensionless relative imports (`./cache` →
|
|
274
|
-
`./cache.js`). (`jskelet dev` installs these hooks in its own child
|
|
275
|
-
processes, not in the outer process.)
|
|
276
|
-
|
|
277
|
-
## Import paths
|
|
278
|
-
|
|
279
|
-
The `package.json` → `exports` map defines the stable surface. In examples, use
|
|
280
|
-
only these specifiers:
|
|
281
|
-
|
|
282
|
-
| Specifier | Contents |
|
|
283
|
-
| --- | --- |
|
|
284
|
-
| `jskelet` | Server API: `route`, `renderPage`, `renderView`, `renderNotFound`, `createApp`, `startServer`, `notFound`, `redirect`, `permanentRedirect`, `cache`, `withRequestCache`, `reportUpstreamFailure`, `asset`, `hasAsset`, `optimizedImage`, `getSpriteIds`, `headHints`, `renderHeadMeta`, HTML cache functions, `prewarm`, `createProxy`, `getConfig`, `loadConfig` and the html/tag helpers |
|
|
285
|
-
| `jskelet/server` | The same module as `jskelet` (an alias for readability) |
|
|
286
|
-
| `jskelet/client` | Browser runtime: `register`, `registerAll`, `hydrate`, `observeDocument`, `start`, `createStore`, DOM helpers, `startSafeImages` |
|
|
287
|
-
| `jskelet/html` | `esc`, `attrs`, `cx`, `cn`, `jsonScript` |
|
|
288
|
-
| `jskelet/tags` | `link`, `image`, `icon`, `preloadImage`, `toKebab` |
|
|
289
|
-
| `jskelet/log` | Console output helpers (`banner`, `event`, `task`, `size`, `ms`, …) |
|
|
290
|
-
| `jskelet/register` | Alias + extension hooks via `node --import jskelet/register` |
|
|
291
|
-
| `jskelet/layout` | The path to the framework's default `layout.jsk` file |
|
|
292
|
-
|
|
293
|
-
## What's next
|
|
294
|
-
|
|
295
|
-
- Why it works this way: [02-architecture.md](./02-architecture.md)
|
|
296
|
-
- More routes and catch-all patterns: [03-routing.md](./03-routing.md)
|
|
297
|
-
- Taking over the layout, and metadata: [04-rendering.md](./04-rendering.md)
|
|
298
|
-
- Tuning the cache: [06-caching.md](./06-caching.md)
|
|
1
|
+
# 01 — Getting started
|
|
2
|
+
|
|
3
|
+
This document explains how to get JSkelet running from scratch: installing the
|
|
4
|
+
package, scaffolding the skeleton with `jskelet init`, writing your first route
|
|
5
|
+
and your first island, what the resulting directory layout means, and the CLI
|
|
6
|
+
commands. By the end you will have a page in the browser that is rendered
|
|
7
|
+
on the server, cached, and whose island hydrates on visibility. For the
|
|
8
|
+
*reasons* behind the decisions see
|
|
9
|
+
[02-architecture.md](./02-architecture.md), and for the full reference of every
|
|
10
|
+
config field mentioned here see
|
|
11
|
+
[07-configuration.md](./07-configuration.md).
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- **Node.js 22 or newer.** `package.json` → `engines` enforces this. The
|
|
16
|
+
framework uses new Node surfaces such as `node:async_hooks`,
|
|
17
|
+
`fs.readdirSync(..., { recursive: true })`, `--env-file-if-exists` and
|
|
18
|
+
`module.register()` directly.
|
|
19
|
+
- If you are going to use Tailwind CSS, the `postcss`, `@tailwindcss/postcss`
|
|
20
|
+
and `tailwindcss` packages. These are **optional peer dependencies** of the
|
|
21
|
+
framework; if they are not installed the CSS step is skipped and the site
|
|
22
|
+
stays unstyled but working (details: [08-build.md](./08-build.md)).
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
mkdir my-site && cd my-site
|
|
28
|
+
npm init -y
|
|
29
|
+
npm pkg set type=module
|
|
30
|
+
npm install jskelet
|
|
31
|
+
npm install -D postcss @tailwindcss/postcss tailwindcss lightningcss
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`type: "module"` is required: route modules, components and the config file are
|
|
35
|
+
loaded as ESM.
|
|
36
|
+
|
|
37
|
+
Then add the scripts to `package.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "jskelet dev",
|
|
43
|
+
"build": "jskelet build",
|
|
44
|
+
"start": "jskelet start"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## `jskelet init`
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx jskelet init
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This command installs a working minimal skeleton into the directory you are in.
|
|
56
|
+
It **does not overwrite existing files**: running it a second time only fills in
|
|
57
|
+
what is missing and prints the number of skipped files as a warning. The goal is
|
|
58
|
+
to skip the "I installed it but nothing works" stage entirely — `jskelet dev`
|
|
59
|
+
runs right afterwards.
|
|
60
|
+
|
|
61
|
+
The files it creates (feature-first + `.jsk`):
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
jskelet.config.mjs config: brand, preconnect, cache(), hooks
|
|
65
|
+
features/home/index.js the "/" route
|
|
66
|
+
features/home/views/pages/home.jsk home page template
|
|
67
|
+
features/home/views/components/button.js example component (<Button />)
|
|
68
|
+
features/home/client/counter.js example island
|
|
69
|
+
features/home/server/.gitkeep
|
|
70
|
+
views/pages/not-found.jsk app-wide 404
|
|
71
|
+
client/entries/main.js island bootstrap
|
|
72
|
+
styles/globals.css Tailwind entry + @source directives
|
|
73
|
+
jsconfig.json checkJs + the "@/*" alias
|
|
74
|
+
.gitignore node_modules/, .jskelet/, public/assets/, .env
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
To grow: `npx jskelet generate feature <name>` (or `page` / `island`).
|
|
78
|
+
|
|
79
|
+
Then:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
In the terminal you will see a banner, aligned build lines and a `Ready`
|
|
86
|
+
summary; `http://localhost:3000` serves the page. A dev overlay bubble sits in
|
|
87
|
+
the bottom right corner, opened with `Alt+D`
|
|
88
|
+
([09-dev-tools.md](./09-dev-tools.md)).
|
|
89
|
+
|
|
90
|
+
## Directory layout
|
|
91
|
+
|
|
92
|
+
None of the directory names are fixed; all of them can be overridden via
|
|
93
|
+
`jskelet.config.mjs` → `paths`. The values below are the defaults
|
|
94
|
+
(`src/config/defaults.js`).
|
|
95
|
+
|
|
96
|
+
| Directory | Default | Contents |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| `views` | `views` | App-wide layout, pages and components |
|
|
99
|
+
| `features` | `features` | Feature slices (`<name>/{server,views,client}`) |
|
|
100
|
+
| `shared` | `shared` | Cross-feature server/views/client |
|
|
101
|
+
| `public` | `public` | Static files; build output is written here too |
|
|
102
|
+
| `client` | `client` | Island runtime sources and entries |
|
|
103
|
+
| `routes` | `routes` | Route modules (loaded before features) |
|
|
104
|
+
| `styles` | `styles/globals.css` | Tailwind/PostCSS entry **file** |
|
|
105
|
+
| `generated` | `.jskelet` | Intermediate build output: `manifest.json`, `metafile.json`, `images.json`, `templates/` |
|
|
106
|
+
|
|
107
|
+
In addition to these the framework always derives two paths and accepts no
|
|
108
|
+
separate setting for them: `public/assets` (hashed build output) and
|
|
109
|
+
`public/fonts` (self-hosted fonts).
|
|
110
|
+
|
|
111
|
+
A typical project (close to what `jskelet init` writes):
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
my-site/
|
|
115
|
+
├── jskelet.config.mjs
|
|
116
|
+
├── jsconfig.json
|
|
117
|
+
├── features/
|
|
118
|
+
│ └── home/
|
|
119
|
+
│ ├── index.js
|
|
120
|
+
│ ├── server/
|
|
121
|
+
│ ├── views/
|
|
122
|
+
│ │ ├── pages/home.jsk
|
|
123
|
+
│ │ └── components/button.js
|
|
124
|
+
│ └── client/counter.js
|
|
125
|
+
├── views/
|
|
126
|
+
│ └── pages/not-found.jsk
|
|
127
|
+
├── client/
|
|
128
|
+
│ └── entries/main.js
|
|
129
|
+
├── styles/
|
|
130
|
+
│ └── globals.css
|
|
131
|
+
├── public/
|
|
132
|
+
│ └── (static files; build → public/assets)
|
|
133
|
+
└── .jskelet/
|
|
134
|
+
├── manifest.json
|
|
135
|
+
└── templates/
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Your first route
|
|
139
|
+
|
|
140
|
+
Route modules **do not derive URLs automatically from the file system**; every
|
|
141
|
+
module writes its own paths explicitly with `app.get(...)`. The module contract:
|
|
142
|
+
a default export or a named export called `register`, with the signature
|
|
143
|
+
`(app, api)`.
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
// features/home/index.js
|
|
147
|
+
export default function register(app, { route }) {
|
|
148
|
+
app.get(
|
|
149
|
+
"/",
|
|
150
|
+
route(
|
|
151
|
+
async () => ({
|
|
152
|
+
view: "pages/home",
|
|
153
|
+
metadata: { title: "Home" },
|
|
154
|
+
data: { message: "JSkelet is running" },
|
|
155
|
+
}),
|
|
156
|
+
{ revalidate: 60 },
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The `api` object comes with `route`, `renderView`, `renderPage`, `notFound`,
|
|
163
|
+
`redirect` and `permanentRedirect` ready to use, so route files don't have to
|
|
164
|
+
import them one by one from the framework. `route()` wraps the controller: the
|
|
165
|
+
HTML cache, the notFound/redirect control flow, compression and the
|
|
166
|
+
`X-JSkelet-Cache` header all come from it. The controller's only job is to
|
|
167
|
+
return a page definition.
|
|
168
|
+
|
|
169
|
+
If you use `routes/`, the `10-` prefix in the file name determines load order;
|
|
170
|
+
put catch-alls such as `/:slug` in a higher-numbered file. Feature `index.js`
|
|
171
|
+
files are appended alphabetically after the `routes/` scan. Details:
|
|
172
|
+
[03-routing.md](./03-routing.md).
|
|
173
|
+
|
|
174
|
+
The template side is `.jsk` (compiled at build time):
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
{# features/home/views/pages/home.jsk #}
|
|
178
|
+
<section class="wrapper">
|
|
179
|
+
<h1>{{ metadata.title }}</h1>
|
|
180
|
+
<p>{{ message }}</p>
|
|
181
|
+
<Button text="Example component" />
|
|
182
|
+
<div data-island="counter" data-island-props='{"start":0}'></div>
|
|
183
|
+
</section>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`Button` comes from the `button` named export in
|
|
187
|
+
`features/home/views/components/button.js` — PascalCase tag, no import
|
|
188
|
+
([04-rendering.md](./04-rendering.md)).
|
|
189
|
+
|
|
190
|
+
## Your first island
|
|
191
|
+
|
|
192
|
+
An island is a small module that adds behaviour to the HTML the server
|
|
193
|
+
produced. The contract has two parts.
|
|
194
|
+
|
|
195
|
+
**1. A marker in the template:** give an element `data-island="ad"`. Props are
|
|
196
|
+
carried as JSON inside `data-island-props`.
|
|
197
|
+
|
|
198
|
+
```ejs
|
|
199
|
+
<div data-island="counter" data-island-props='{"start":5}'></div>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**2. A `mount` in the module:** the island provides a named export called
|
|
203
|
+
`mount(element, props)`.
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
// features/home/client/counter.js
|
|
207
|
+
/**
|
|
208
|
+
* @param {HTMLElement} element
|
|
209
|
+
* @param {{ start?: number }} props
|
|
210
|
+
*/
|
|
211
|
+
export function mount(element, props) {
|
|
212
|
+
let value = props.start ?? 0;
|
|
213
|
+
|
|
214
|
+
const button = document.createElement("button");
|
|
215
|
+
button.type = "button";
|
|
216
|
+
|
|
217
|
+
const paint = () => {
|
|
218
|
+
button.textContent = `Clicks: ${value}`;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
button.addEventListener("click", () => {
|
|
222
|
+
value += 1;
|
|
223
|
+
paint();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
paint();
|
|
227
|
+
element.append(button);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**3. Registration:** `client/entries/main.js` maps the island name to a dynamic
|
|
232
|
+
import and starts the runtime.
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
import { registerAll, start } from "jskelet/client";
|
|
236
|
+
|
|
237
|
+
registerAll({
|
|
238
|
+
counter: () => import("../../features/home/client/counter.js"),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
start();
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
It is critical that the values are dynamic imports: the module is downloaded
|
|
245
|
+
only if that island actually exists on the page **and** when the element becomes
|
|
246
|
+
visible. In other words, growing this map does not grow the initial payload.
|
|
247
|
+
Hydration strategies (`data-island-eager`, `data-island-idle`) and the complete
|
|
248
|
+
runtime API are in [05-islands.md](./05-islands.md).
|
|
249
|
+
|
|
250
|
+
## CLI commands
|
|
251
|
+
|
|
252
|
+
`bin/jskelet.mjs` offers these subcommands. Each runs in a separate Node process;
|
|
253
|
+
the reason is that `dev` manages two long-lived processes and the server needs
|
|
254
|
+
ESM resolve hooks (`--import`) at process start.
|
|
255
|
+
|
|
256
|
+
| Command | What it does |
|
|
257
|
+
| --- | --- |
|
|
258
|
+
| `jskelet dev` | Build watch + server, in a single terminal. Live reload, CSS hot-swap, dev overlay. `NODE_ENV=development`. Refuses to start if the port is busy; `--murder` kills the listener and binds. |
|
|
259
|
+
| `jskelet build` | One-shot prod build: fonts → icon sprite → CSS → client JS → images → manifest → precompress. `production` if `NODE_ENV` is not given. |
|
|
260
|
+
| `jskelet start` | Prod server. If there is no build output it produces it first. `production` if `NODE_ENV` is not given. Same port behaviour as `dev` (`--murder`). |
|
|
261
|
+
| `jskelet init` | Installs a feature-first `.jsk` skeleton into the current directory; leaves existing files alone. |
|
|
262
|
+
| `jskelet generate` | Scaffolds a `feature` / `page` / `island`. |
|
|
263
|
+
| `jskelet migrate` | Next.js App Router → JSkelet codemod (`scan` / `apply` / `config`). See [11-migration.md](./11-migration.md). |
|
|
264
|
+
|
|
265
|
+
An unknown command, or a call with no arguments, prints the usage text.
|
|
266
|
+
|
|
267
|
+
Every command runs with two Node flags:
|
|
268
|
+
|
|
269
|
+
- `--env-file=.env` — passed only if the file really exists; otherwise no flag
|
|
270
|
+
is added and no warning is printed.
|
|
271
|
+
- `--import <register.mjs>` — installs the ESM hooks that resolve the
|
|
272
|
+
`compilerOptions.paths` aliases (`@/lib/x`) from `jsconfig.json` /
|
|
273
|
+
`tsconfig.json` and extensionless relative imports (`./cache` →
|
|
274
|
+
`./cache.js`). (`jskelet dev` installs these hooks in its own child
|
|
275
|
+
processes, not in the outer process.)
|
|
276
|
+
|
|
277
|
+
## Import paths
|
|
278
|
+
|
|
279
|
+
The `package.json` → `exports` map defines the stable surface. In examples, use
|
|
280
|
+
only these specifiers:
|
|
281
|
+
|
|
282
|
+
| Specifier | Contents |
|
|
283
|
+
| --- | --- |
|
|
284
|
+
| `jskelet` | Server API: `route`, `renderPage`, `renderView`, `renderNotFound`, `createApp`, `startServer`, `notFound`, `redirect`, `permanentRedirect`, `cache`, `withRequestCache`, `reportUpstreamFailure`, `asset`, `hasAsset`, `optimizedImage`, `getSpriteIds`, `headHints`, `renderHeadMeta`, HTML cache functions, `prewarm`, `createProxy`, `getConfig`, `loadConfig` and the html/tag helpers |
|
|
285
|
+
| `jskelet/server` | The same module as `jskelet` (an alias for readability) |
|
|
286
|
+
| `jskelet/client` | Browser runtime: `register`, `registerAll`, `hydrate`, `observeDocument`, `start`, `createStore`, DOM helpers, `startSafeImages` |
|
|
287
|
+
| `jskelet/html` | `esc`, `attrs`, `cx`, `cn`, `jsonScript` |
|
|
288
|
+
| `jskelet/tags` | `link`, `image`, `icon`, `preloadImage`, `toKebab` |
|
|
289
|
+
| `jskelet/log` | Console output helpers (`banner`, `event`, `task`, `size`, `ms`, …) |
|
|
290
|
+
| `jskelet/register` | Alias + extension hooks via `node --import jskelet/register` |
|
|
291
|
+
| `jskelet/layout` | The path to the framework's default `layout.jsk` file |
|
|
292
|
+
|
|
293
|
+
## What's next
|
|
294
|
+
|
|
295
|
+
- Why it works this way: [02-architecture.md](./02-architecture.md)
|
|
296
|
+
- More routes and catch-all patterns: [03-routing.md](./03-routing.md)
|
|
297
|
+
- Taking over the layout, and metadata: [04-rendering.md](./04-rendering.md)
|
|
298
|
+
- Tuning the cache: [06-caching.md](./06-caching.md)
|