jskelet 0.6.1 → 0.6.3

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.
Files changed (155) hide show
  1. package/AGENTS.md +136 -136
  2. package/CHANGELOG.md +620 -596
  3. package/LICENSE +21 -21
  4. package/bin/jskelet.mjs +130 -130
  5. package/docs/01-baslangic.md +291 -291
  6. package/docs/02-mimari.md +310 -309
  7. package/docs/03-routing.md +515 -515
  8. package/docs/04-render-ve-sablonlar.md +661 -661
  9. package/docs/05-islands.md +486 -486
  10. package/docs/06-cache.md +1443 -1423
  11. package/docs/07-yapilandirma.md +12 -6
  12. package/docs/08-build.md +429 -428
  13. package/docs/09-dev-araclari.md +364 -364
  14. package/docs/10-dagitim.md +338 -338
  15. package/docs/12-panel-ve-oturum.md +478 -478
  16. package/docs/README.md +83 -83
  17. package/docs/en/01-getting-started.md +298 -298
  18. package/docs/en/02-architecture.md +329 -328
  19. package/docs/en/03-routing.md +531 -531
  20. package/docs/en/04-rendering.md +669 -669
  21. package/docs/en/05-islands.md +497 -497
  22. package/docs/en/06-caching.md +1453 -1431
  23. package/docs/en/07-configuration.md +1219 -1214
  24. package/docs/en/08-build.md +447 -446
  25. package/docs/en/09-dev-tools.md +373 -373
  26. package/docs/en/10-deployment.md +340 -340
  27. package/docs/en/11-migration.md +398 -398
  28. package/docs/en/12-dashboards-and-sessions.md +488 -488
  29. package/docs/en/README.md +87 -87
  30. package/package.json +137 -137
  31. package/src/build/ensure-build.mjs +19 -19
  32. package/src/build/paths.mjs +153 -153
  33. package/src/build/resolve-peer.mjs +36 -36
  34. package/src/build/tasks/client.mjs +349 -349
  35. package/src/build/tasks/css.mjs +235 -235
  36. package/src/build/tasks/fonts.mjs +146 -146
  37. package/src/build/tasks/icons.mjs +357 -357
  38. package/src/build/tasks/images.mjs +244 -244
  39. package/src/build/tasks/precompress.mjs +78 -78
  40. package/src/build/tasks/templates.mjs +20 -20
  41. package/src/client/admin/i18n.js +764 -764
  42. package/src/client/admin/login.html +74 -74
  43. package/src/client/admin/panel.css +809 -809
  44. package/src/client/admin/panel.html +495 -495
  45. package/src/client/admin/panel.js +1251 -1251
  46. package/src/client/devtools/report.html +185 -185
  47. package/src/client/devtools/report.js +745 -745
  48. package/src/client/devtools/seo.js +628 -628
  49. package/src/client/dom.js +95 -95
  50. package/src/client/form.js +192 -192
  51. package/src/client/index.js +45 -45
  52. package/src/client/registry.js +305 -305
  53. package/src/client/safe-image.js +91 -91
  54. package/src/client/shared-cookie.js +225 -225
  55. package/src/client/store.js +36 -36
  56. package/src/client/swap.js +188 -188
  57. package/src/compile/codegen.js +336 -336
  58. package/src/compile/compile-all.js +149 -149
  59. package/src/compile/errors.js +66 -66
  60. package/src/compile/expr.js +409 -409
  61. package/src/compile/index.js +17 -17
  62. package/src/compile/parse.js +541 -541
  63. package/src/compile/resolve.js +211 -211
  64. package/src/compile/scan-exports.js +51 -51
  65. package/src/config/defaults.js +17 -1
  66. package/src/config/index.js +13 -0
  67. package/src/config/pattern.js +107 -107
  68. package/src/generate.mjs +163 -163
  69. package/src/http/control-flow.js +71 -71
  70. package/src/http/cookies-entry.js +21 -21
  71. package/src/http/cookies.js +277 -277
  72. package/src/http/request-cache.js +46 -46
  73. package/src/http/request-context.js +165 -165
  74. package/src/http/shared-cookie.js +178 -178
  75. package/src/index.js +101 -101
  76. package/src/init.mjs +230 -230
  77. package/src/migrate/apply.mjs +262 -262
  78. package/src/migrate/babel.mjs +79 -79
  79. package/src/migrate/classify.mjs +155 -155
  80. package/src/migrate/config.mjs +126 -126
  81. package/src/migrate/fs-walk.mjs +191 -191
  82. package/src/migrate/parse.mjs +26 -26
  83. package/src/migrate/scan.mjs +177 -177
  84. package/src/migrate/transform/expr-source.mjs +168 -168
  85. package/src/migrate/transform/island.mjs +67 -67
  86. package/src/migrate/transform/jsx-to-component.mjs +302 -302
  87. package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
  88. package/src/migrate/transform/page-split.mjs +435 -435
  89. package/src/migrate/write.mjs +81 -81
  90. package/src/migrate.mjs +171 -171
  91. package/src/runtime/alias-hooks.mjs +119 -119
  92. package/src/runtime/register.mjs +4 -4
  93. package/src/server/admin/actions.js +229 -229
  94. package/src/server/admin/auth.js +125 -125
  95. package/src/server/admin/event-log.js +151 -151
  96. package/src/server/admin/gate.js +209 -209
  97. package/src/server/admin/inventory.js +188 -188
  98. package/src/server/admin/mount.js +56 -56
  99. package/src/server/admin/router.js +216 -216
  100. package/src/server/admin/snapshot.js +241 -241
  101. package/src/server/assets.js +147 -147
  102. package/src/server/auth/handoff.js +309 -309
  103. package/src/server/cache-blob.js +70 -0
  104. package/src/server/cache-deps.js +42 -42
  105. package/src/server/cache-vary.js +113 -113
  106. package/src/server/cloudflare.js +607 -607
  107. package/src/server/create-app.js +366 -366
  108. package/src/server/data-cache.js +553 -462
  109. package/src/server/dev/report.js +485 -485
  110. package/src/server/dev/socket.js +170 -170
  111. package/src/server/dev/version-check.mjs +139 -139
  112. package/src/server/disk-cache.js +233 -0
  113. package/src/server/ejs-adapter.js +59 -59
  114. package/src/server/html-cache.js +1196 -1122
  115. package/src/server/image-optimizer.js +500 -407
  116. package/src/server/logs/access-middleware.js +66 -66
  117. package/src/server/logs/file-sink.js +193 -66
  118. package/src/server/logs/pipeline.js +165 -158
  119. package/src/server/logs/s3-put.js +214 -214
  120. package/src/server/logs/s3-sink.js +112 -112
  121. package/src/server/metadata.js +102 -102
  122. package/src/server/middleware/compression.js +205 -205
  123. package/src/server/middleware/csrf.js +134 -134
  124. package/src/server/middleware/dev-gate.js +75 -75
  125. package/src/server/middleware/headers.js +37 -37
  126. package/src/server/middleware/redirects.js +32 -32
  127. package/src/server/middleware/robots-txt.js +341 -341
  128. package/src/server/middleware/static-precompressed.js +121 -100
  129. package/src/server/middleware/trailing-slash.js +53 -53
  130. package/src/server/middleware/upstream-proxy.js +141 -141
  131. package/src/server/og-image.js +356 -356
  132. package/src/server/port-guard.js +255 -255
  133. package/src/server/prewarm.js +1082 -1058
  134. package/src/server/redis.js +588 -569
  135. package/src/server/render.js +4 -4
  136. package/src/server/router.js +157 -157
  137. package/src/server/status-page.js +265 -265
  138. package/src/server/upstream-limiter.js +376 -376
  139. package/src/server/upstream-tracking.js +166 -166
  140. package/src/shared/cookie-domain.js +66 -66
  141. package/src/start.mjs +22 -22
  142. package/src/templates/layout.ejs +30 -30
  143. package/src/templates/layout.jsk +30 -30
  144. package/src/version.mjs +31 -31
  145. package/src/views/components/loader.js +101 -101
  146. package/src/views/helpers/html.js +102 -102
  147. package/src/views/helpers/tags.js +375 -375
  148. package/types/config/defaults.d.ts +15 -1
  149. package/types/config/index.d.ts +8 -0
  150. package/types/server/cache-blob.d.ts +13 -0
  151. package/types/server/data-cache.d.ts +9 -0
  152. package/types/server/disk-cache.d.ts +36 -0
  153. package/types/server/html-cache.d.ts +26 -3
  154. package/types/server/logs/file-sink.d.ts +16 -5
  155. package/types/server/redis.d.ts +2 -1
@@ -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)