jskelet 0.4.0 → 0.4.2

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/CHANGELOG.md CHANGED
@@ -27,6 +27,17 @@ one is listed under a **Breaking** heading.
27
27
 
28
28
  ### Added
29
29
 
30
+ - VS Code / Cursor extension skeleton under `extensions/vscode-jsk`: `.jsk`
31
+ language id, TextMate highlighting (`{{ }}` / `{#if}` / `{#each}` /
32
+ components), language config, and snippets. Install from that folder or
33
+ launch **JSK: Extension** from the repo root. Bound attrs on HTML tags
34
+ (`:src="… + '/path'"`) highlight nested single-quoted strings.
35
+ - Compile-time known components are discovered from **named exports** in
36
+ `views/components/**/*.js` (plus `.jsk` component files), not from the file
37
+ basename — so `<SectionHead />` resolves when `sectionHead` lives in
38
+ `ui.js` without a stub re-export. Docs cover the `.jsk` template-vs-component
39
+ boundary and a `{ items, error }` loader / `LoadErrorState` pattern so
40
+ upstream failures are not mistaken for empty data.
30
41
  - Build-time `.jsk` templates: declarative HTML-like syntax compiled to ESM
31
42
  render modules under `.jskelet/templates/` (no request-time parse, `eval`, or
32
43
  `new Function`). Coexists with EJS; compiled `.jsk` wins when both exist.
@@ -36,7 +47,8 @@ one is listed under a **Breaking** heading.
36
47
  - Feature-first conventions: `paths.features` / `paths.shared`, multi-root
37
48
  views and components, `features/<name>/index.js` route registration after
38
49
  `routes/`. CLI: `jskelet generate feature|page|island`. `jskelet init`
39
- scaffolds `.jsk` pages.
50
+ scaffolds a feature-first `.jsk` skeleton (`features/home/` with route,
51
+ page, component and island; global `views/pages/not-found.jsk`).
40
52
  - Template compile step in `jskelet build`; icon scan and Tailwind docs cover
41
53
  `.jsk` / `features` / `shared`. Bench: `node scripts/bench-templates.mjs`.
42
54
  - Top-level `logs` config for persistent sinks: daily NDJSON files
@@ -127,8 +139,19 @@ one is listed under a **Breaking** heading.
127
139
 
128
140
  ### Changed
129
141
 
142
+ - README rewritten for the current surface: build-time `.jsk` as the default
143
+ template story (EJS still supported), feature-first `init` examples, `mount`
144
+ island contract, path-based `invalidateHtmlCache` (replacing the outdated
145
+ “no targeted invalidation” claim), Redis / admin / data-cache callouts, and
146
+ bilingual doc links under `docs/` and `docs/en/`.
147
+ - Duplicate component named exports (or the same PascalCase tag in two files)
148
+ now **fail** at build and at server startup instead of warning and letting
149
+ the second definition win. Overwriting `components/index.js` barrel exports
150
+ remains allowed.
130
151
  - `examples/minimal` pages moved to `.jsk`; adds `features/demo` as a
131
152
  co-located route + view sample.
153
+ - Marketing compare/FAQ copy no longer claims targeted invalidation is missing;
154
+ it points at `invalidateHtmlCache()` (and Redis pub/sub for multi-instance).
132
155
 
133
156
  - An adaptive per-host rate limit for upstream calls, `cache().upstream`. It sits
134
157
  in the `fetch` wrapper rather than in the prewarm pass, because what spends the
package/README.md CHANGED
@@ -3,11 +3,12 @@
3
3
  **A framework that feels like no framework** — for sites where SEO and speed are
4
4
  the product.
5
5
 
6
- JSkelet renders **complete HTML** on an Express 5 server with EJS, adds
7
- interactivity through vanilla JS **islands**, compiles CSS into a **single
8
- Tailwind v4 stylesheet**, and instead of ISR keeps an in-process **HTML TTL
9
- cache** with stale-while-revalidate. No React, no TypeScript — plain JavaScript
10
- with JSDoc.
6
+ JSkelet renders **complete HTML** on an Express 5 server from build-time
7
+ **`.jsk` templates** (EJS still works), adds interactivity through vanilla JS
8
+ **islands**, compiles CSS into a **single Tailwind v4 stylesheet**, and instead
9
+ of ISR keeps an in-process **HTML TTL cache** with stale-while-revalidate — plus
10
+ optional Redis sharing and path-based invalidation. No React, no TypeScript —
11
+ plain JavaScript with JSDoc.
11
12
 
12
13
  [![npm version](https://img.shields.io/npm/v/jskelet)](https://www.npmjs.com/package/jskelet)
13
14
  [![Node.js 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)
@@ -46,19 +47,17 @@ Requirements:
46
47
  - **Node.js 22 or newer.**
47
48
  - Everything else is an **optional peer dependency**: `postcss`,
48
49
  `@tailwindcss/postcss`, `tailwindcss` and `lightningcss` for styles,
49
- `@phosphor-icons/core` for the icon sprite, `sharp` for image optimization. If
50
- a package is missing, the matching build step is skipped with a warning and
51
- the site keeps working.
50
+ `@phosphor-icons/core` for the icon sprite, `sharp` for image optimization,
51
+ `ioredis` for the shared Redis cache tier. If a package is missing, the
52
+ matching step is skipped with a warning and the site keeps working.
52
53
 
53
54
  ## What it looks like
54
55
 
55
- A route module receives the app and returns page descriptions. Nothing is
56
- inferred from the file system — URLs are written out.
56
+ A feature (or route) module receives the app and registers URLs explicitly.
57
+ Nothing is inferred from the file system.
57
58
 
58
59
  ```js
59
- // routes/10-pages.mjs
60
- import { getPost, getPosts } from "../lib/posts.js";
61
-
60
+ // features/home/index.js
62
61
  export default function register(app, { route, notFound }) {
63
62
  app.get("/", route(
64
63
  async () => ({
@@ -66,7 +65,7 @@ export default function register(app, { route, notFound }) {
66
65
  metadata: { title: "Home", canonical: "/" },
67
66
  data: { posts: getPosts() },
68
67
  }),
69
- { revalidate: 60 }, // keep this HTML for 60 seconds
68
+ { revalidate: 60 },
70
69
  ));
71
70
 
72
71
  app.get("/blog/:slug", route(async ({ params }) => {
@@ -77,28 +76,28 @@ export default function register(app, { route, notFound }) {
77
76
  }
78
77
  ```
79
78
 
80
- Templates are EJS. Every named export under `views/components/**` becomes a
81
- template local automatically, so components need no imports — they are plain
82
- functions returning HTML strings.
79
+ Templates are `.jsk`: compiled to ESM at build time (no request-time parse or
80
+ `eval`). Named exports under `views/components/**` become PascalCase tags —
81
+ plain functions that return HTML strings.
83
82
 
84
83
  ```html
85
- <!-- views/pages/home.ejs -->
84
+ <!-- features/home/views/pages/home.jsk -->
86
85
  <section class="wrapper">
87
86
  <h1 class="text-3xl font-bold">Latest posts</h1>
88
- <% posts.forEach(function (post) { %>
89
- <%- postCard({ post }) %>
90
- <% }); %>
87
+ {#each posts as post}
88
+ <PostCard :post="post" />
89
+ {/each}
91
90
 
92
- <!-- downloaded and wired up once visible -->
93
91
  <div data-island="newsletter"></div>
94
92
  </section>
95
93
  ```
96
94
 
97
- Islands are modules with a default export that receives their root element.
95
+ Islands export a named `mount(element, props)` and are registered from the
96
+ client entry as dynamic imports.
98
97
 
99
98
  ```js
100
- // client/islands/newsletter.js
101
- export default function newsletter(el) {
99
+ // features/home/client/newsletter.js
100
+ export function mount(el) {
102
101
  const form = el.querySelector("form");
103
102
  form.addEventListener("submit", async (event) => {
104
103
  event.preventDefault();
@@ -114,20 +113,22 @@ numbered comment at the top of `src/server/create-app.js`; changing that order
114
113
  causes silent breakage.
115
114
 
116
115
  1. **Config** (`jskelet.config.mjs`) is loaded once and exposed through
117
- `getConfig()`. `redirects()`, `rewrites()`, `headers()` and `cache()` follow
118
- the subset of `next.config` syntax people actually use. A broken config, a
119
- throwing `headers()` or a failing hook logs a warning and falls back to
120
- defaults — it never takes the site down.
121
- 2. **Static assets** are served from `public/` with hashed filenames and
122
- long-lived cache headers, and precompressed `.br` / `.gz` variants are picked
116
+ `getConfig()`. `redirects()`, `rewrites()`, `headers()`, `cache()` and
117
+ `admin()` follow the subset of `next.config` syntax people actually use. A
118
+ broken config, a throwing `headers()` or a failing hook logs a warning and
119
+ falls back to defaults — it never takes the site down.
120
+ 2. **Build** turns `.jsk` into modules under `.jskelet/templates/`, bundles
121
+ islands, compiles Tailwind, hashes assets and optionally precompresses them.
122
+ 3. **Static assets** are served from `public/` with hashed filenames and
123
+ long-lived cache headers; precompressed `.br` / `.gz` variants are picked
123
124
  automatically.
124
- 3. **`route()`** wraps your controller. It builds a cache key, checks the HTML
125
+ 4. **`route()`** wraps your controller. It builds a cache key, checks the HTML
125
126
  TTL cache, and on a miss renders the page. When a cached entry is stale it is
126
127
  returned immediately while revalidation runs in the background.
127
- 4. **Render** composes metadata, layout context and your view into one HTML
128
+ 5. **Render** composes metadata, layout context and your view into one HTML
128
129
  document. Hooks (`metadata`, `layoutContext`, `notFound`, `prewarmPaths`)
129
130
  are where application knowledge lives — the framework itself carries none.
130
- 5. **Hydration** happens in the browser: the island registry finds
131
+ 6. **Hydration** happens in the browser: the island registry finds
131
132
  `data-island` elements and dynamically imports the matching chunk when it
132
133
  becomes visible (or eagerly / on idle, if asked).
133
134
 
@@ -141,21 +142,35 @@ client.
141
142
  - **Full HTML from the server.** First paint does not wait for JavaScript, and
142
143
  crawlers see the complete document because content is never assembled in the
143
144
  browser.
145
+ - **Build-time `.jsk` templates.** Declarative HTML-like syntax compiled to ESM
146
+ before the server starts; EJS remains supported where both exist, `.jsk`
147
+ wins. A VS Code / Cursor extension under `extensions/vscode-jsk` covers
148
+ highlighting and snippets.
149
+ - **Feature-first layout.** `features/<name>/` co-locates routes, views,
150
+ components and islands; `jskelet generate feature|page|island` scaffolds the
151
+ next slice. URLs stay explicit.
144
152
  - **Islands.** Interactivity attaches to elements carrying `data-island`.
145
153
  Modules are dynamically imported on visibility by default;
146
154
  `data-island-eager` and `data-island-idle` pick a different strategy. A small
147
155
  store handles sharing state between islands.
148
156
  - **HTML TTL cache.** Per-route `revalidate`, stale-while-revalidate on expiry,
149
- and prewarm that fills the cache at boot so the first visitor is not the one
150
- who pays for rendering.
157
+ query allowlists, dependency tracking from `withDataCache`, and prewarm that
158
+ fills the cache at boot. `invalidateHtmlCache()` stales a path, pattern or
159
+ RegExp without flushing everything.
160
+ - **Optional Redis tier.** With `ioredis`, replicas share HTML/data and
161
+ broadcast invalidation over pub/sub so a webhook reaches every process.
162
+ - **Admin panel.** Opt-in at `/_jskelet/admin` (`admin()` or `JSKELET_ADMIN=1`):
163
+ cache inventory, targeted purge, Cloudflare CDN controls, live logs, routes
164
+ and system meters — password printed once per process start.
151
165
  - **Fast navigation.** The `navigation` config section emits Speculation Rules
152
166
  to prefetch or prerender links and enables view transitions — without adding
153
167
  any client runtime.
154
168
  - **Familiar configuration.** `redirects()`, `rewrites()`, `headers()`,
155
- `cache()`, plus `brand`, `images`, `security` and `hooks` sections.
156
- - **A real build pipeline.** Fonts, an SVG sprite generated from the icons you
157
- actually use, Tailwind v4 CSS, esbuild bundles with code splitting, webp
158
- variants, hashed output and brotli/gzip precompression.
169
+ `cache()`, `admin()`, plus `brand`, `images`, `security`, `logs`,
170
+ `trailingSlash` and `hooks`.
171
+ - **A real build pipeline.** Fonts, an SVG sprite from the icons you use,
172
+ Tailwind v4 CSS, esbuild bundles with code splitting, webp variants, hashed
173
+ output and brotli/gzip precompression.
159
174
  - **Developer experience.** One command, one terminal: watch build plus server,
160
175
  CSS hot-swap, automatic restart, and a devtools overlay on Alt+D showing
161
176
  requests, errors, upstream calls, a cache dump and Web Vitals.
@@ -168,8 +183,9 @@ client.
168
183
  - **No file-system routing.** Paths are written explicitly in route modules.
169
184
  - **No streaming or RSC.** A page is flushed as one document; slow sections are
170
185
  fetched from separate fragment endpoints.
171
- - **No targeted cache invalidation.** There is TTL and there is "clear
172
- everything".
186
+ - **No Next.js-style cache tags.** Invalidation is by path, pattern or RegExp
187
+ (`invalidateHtmlCache`), plus data-cache dependency tracking — not arbitrary
188
+ tag graphs.
173
189
  - **No global state management** beyond the small island store.
174
190
 
175
191
  An app-shaped interface behind a login — a dashboard, an editor, an admin panel
@@ -177,10 +193,12 @@ An app-shaped interface behind a login — a dashboard, an editor, an admin pane
177
193
  framework. It is supported rather than recommended: `route(fn, { private: true })`
178
194
  keeps per-visitor pages out of the cache, and signed cookies, CSRF, fragment
179
195
  endpoints and region swapping cover the rest
180
- ([docs/12-panel-ve-oturum.md](./docs/12-panel-ve-oturum.md)). Live data
181
- transport is deliberately left to you; pick SSE, WebSocket or polling yourself.
182
- A feature-by-feature comparison with Next.js is in
183
- [docs/11-tasima.md](./docs/11-tasima.md).
196
+ ([docs/12-panel-ve-oturum.md](./docs/12-panel-ve-oturum.md) /
197
+ [docs/en/12-dashboards-and-sessions.md](./docs/en/12-dashboards-and-sessions.md)).
198
+ Live data transport is deliberately left to you; pick SSE, WebSocket or polling
199
+ yourself. A feature-by-feature comparison with Next.js is in
200
+ [docs/11-tasima.md](./docs/11-tasima.md) /
201
+ [docs/en/11-migration.md](./docs/en/11-migration.md).
184
202
 
185
203
  ## Project layout
186
204
 
@@ -189,18 +207,20 @@ the `paths` section of the config:
189
207
 
190
208
  ```
191
209
  my-site/
192
- ├── jskelet.config.mjs # config, hooks, headers, redirects
193
- ├── routes/ # loaded in filename order (10-, 20-, …)
194
- ├── views/
195
- │ ├── pages/ # EJS pages
196
- │ ├── partials/ # header, footer, …
197
- │ └── components/ # named exports become template locals
198
- ├── client/
199
- │ ├── entries/main.js # registers islands, calls start()
200
- │ └── islands/ # one module per island
201
- ├── styles/globals.css # Tailwind entry with @source directives
202
- ├── lib/ # your data access
203
- └── public/ # build output plus static files
210
+ ├── jskelet.config.mjs # config, hooks, headers, redirects
211
+ ├── features/ # feature-first slices (optional but default in init)
212
+ │ └── home/
213
+ │ ├── index.js # register(app, api) — URLs stay explicit
214
+ │ ├── views/pages/ # .jsk pages for this feature
215
+ │ ├── views/components/
216
+ │ ├── client/ # islands; register from client/entries
217
+ │ └── server/
218
+ ├── routes/ # optional; loaded before features (10-, 20-, …)
219
+ ├── views/ # shared / app-wide pages (e.g. 404)
220
+ ├── shared/ # cross-feature server/views/client
221
+ ├── client/entries/main.js # registers islands, calls start()
222
+ ├── styles/globals.css # Tailwind entry with @source directives
223
+ └── public/ # build output plus static files
204
224
  ```
205
225
 
206
226
  Two things bite newcomers:
@@ -209,9 +229,8 @@ Two things bite newcomers:
209
229
  `styles/globals.css`, because automatic detection is turned off with
210
230
  `source(none)`. A new directory that uses classes needs an `@source` line, or
211
231
  its classes silently vanish from the stylesheet.
212
- - **`include` in EJS is async.** `await include('partials/x')` only works in a
213
- template's own body; inside a `forEach` callback it is a compile error, so use
214
- a `for` loop there.
232
+ - **`.jsk` expression language is intentionally narrow.** Formatting and object
233
+ literals belong in JS components (`views/components/**`), not in the template.
215
234
 
216
235
  ## Configuration
217
236
 
@@ -220,7 +239,7 @@ Two things bite newcomers:
220
239
  ```js
221
240
  export default {
222
241
  brand: { name: "My Site", lang: "en" },
223
- icons: { scan: ["views", "client"] },
242
+ icons: { scan: ["views", "features", "client"] },
224
243
 
225
244
  // Speculation Rules plus @view-transition, with no client runtime.
226
245
  navigation: { prefetch: "moderate", prerender: "conservative", viewTransition: true },
@@ -236,10 +255,17 @@ export default {
236
255
  async cache() {
237
256
  return {
238
257
  html: { "/": 3600, "/pricing": 3600 },
258
+ query: { "/search": ["q"] }, // only these params enter the cache key
239
259
  prewarm: { enabled: true, max: 50, concurrency: 4 },
260
+ // redis: { enabled: true, url: process.env.REDIS_URL },
240
261
  };
241
262
  },
242
263
 
264
+ // Opt-in production panel at /_jskelet/admin (password in the server log).
265
+ async admin() {
266
+ return { enabled: false };
267
+ },
268
+
243
269
  hooks: {
244
270
  metadata: () => ({ titleTemplate: "%s · My Site", siteUrl: "https://example.com" }),
245
271
  layoutContext: ({ pathname }) => ({ pathname, year: new Date().getFullYear() }),
@@ -249,16 +275,18 @@ export default {
249
275
  ```
250
276
 
251
277
  The complete reference — every field, default and failure mode — is
252
- [docs/07-yapilandirma.md](./docs/07-yapilandirma.md).
278
+ [docs/07-yapilandirma.md](./docs/07-yapilandirma.md) /
279
+ [docs/en/07-configuration.md](./docs/en/07-configuration.md).
253
280
 
254
281
  ## CLI
255
282
 
256
283
  | Command | What it does |
257
284
  | --- | --- |
258
285
  | `jskelet dev` | Watch build plus server, live reload, devtools overlay |
259
- | `jskelet build` | Production build: fonts → sprite → CSS → JS → images → manifest → precompress |
286
+ | `jskelet build` | Production build: templates → fonts → sprite → CSS → JS → images → manifest → precompress |
260
287
  | `jskelet start` | Production server; builds first if output is missing |
261
- | `jskelet init` | Scaffolds a minimal skeleton into the current directory |
288
+ | `jskelet init` | Scaffolds a feature-first `.jsk` skeleton into the current directory |
289
+ | `jskelet generate` | Scaffolds a `feature` / `page` / `island` |
262
290
 
263
291
  ## Public API
264
292
 
@@ -266,7 +294,7 @@ Only the specifiers in the `exports` map are supported:
266
294
 
267
295
  | Specifier | Contents |
268
296
  | --- | --- |
269
- | `jskelet` | `route`, `fragment`, `createApp`, `startServer`, `notFound`, `redirect`, `seeOther`, `cache`, `asset`, `getConfig`, cookie helpers, HTML cache and prewarm helpers |
297
+ | `jskelet` | `route`, `fragment`, `createApp`, `startServer`, `notFound`, `redirect`, `seeOther`, `cache`, `asset`, `getConfig`, cookie helpers, HTML/data cache and prewarm helpers, Redis/Cloudflare status and purge helpers |
270
298
  | `jskelet/client` | `register`, `registerAll`, `hydrate`, `unmount`, `start`, `swap`, `startForms`, `createStore`, DOM helpers |
271
299
  | `jskelet/html` | `attrs`, `cn`, `cx`, `esc`, `jsonScript` |
272
300
  | `jskelet/tags` | `icon`, `image`, `link`, `preloadImage`, `csrfField` |
@@ -281,28 +309,29 @@ works: a `Dockerfile` (see `examples/marketing/Dockerfile`), a systemd unit, or
281
309
  a PaaS. Run `jskelet build` at image build time, put a reverse proxy in front
282
310
  for TLS, and expose a health endpoint (the default dev gate bypass list already
283
311
  includes `/api/healthcheck`, so a route there is reachable in every mode).
284
- Details, including cache sizing
285
- behind multiple instances, are in [docs/10-dagitim.md](./docs/10-dagitim.md).
312
+ Details, including cache sizing behind multiple instances and the optional
313
+ Redis tier, are in [docs/10-dagitim.md](./docs/10-dagitim.md) /
314
+ [docs/en/10-deployment.md](./docs/en/10-deployment.md).
286
315
 
287
316
  ## Documentation
288
317
 
289
- The full reference lives under [docs/](./docs/README.md). It is currently
290
- written in Turkish; translations are a welcome contribution.
318
+ Full reference in Turkish under [docs/](./docs/README.md) and in English under
319
+ [docs/en/](./docs/en/README.md). Both editions are kept in sync.
291
320
 
292
- | Document | Topic |
321
+ | Document (TR / EN) | Topic |
293
322
  | --- | --- |
294
- | [01-baslangic](./docs/01-baslangic.md) | Installation, first route, first island, directory layout, CLI |
295
- | [02-mimari](./docs/02-mimari.md) | Decisions and their reasoning, middleware order |
296
- | [03-routing](./docs/03-routing.md) | Route modules, controller contract, load order |
297
- | [04-render-ve-sablonlar](./docs/04-render-ve-sablonlar.md) | Layout, components, helpers, metadata |
298
- | [05-islands](./docs/05-islands.md) | Island contract, hydration, store, DOM helpers |
299
- | [06-cache](./docs/06-cache.md) | TTL, stale-while-revalidate, keys, prewarm |
300
- | [07-yapilandirma](./docs/07-yapilandirma.md) | Complete `jskelet.config.mjs` reference |
301
- | [08-build](./docs/08-build.md) | Build pipeline, manifest, Tailwind `@source`, sprite |
302
- | [09-dev-araclari](./docs/09-dev-araclari.md) | Dev workflow, overlay, report page, dev gate |
303
- | [10-dagitim](./docs/10-dagitim.md) | Production, Docker, reverse proxy, health checks |
304
- | [11-tasima](./docs/11-tasima.md) | Migrating from Next.js: mapping table and plan |
305
- | [12-panel-ve-oturum](./docs/12-panel-ve-oturum.md) | Per-visitor pages: `private: true`, sessions, CSRF, fragments, swapping |
323
+ | [01-baslangic](./docs/01-baslangic.md) / [getting-started](./docs/en/01-getting-started.md) | Installation, first route, first island, directory layout, CLI |
324
+ | [02-mimari](./docs/02-mimari.md) / [architecture](./docs/en/02-architecture.md) | Decisions and their reasoning, middleware order |
325
+ | [03-routing](./docs/03-routing.md) / [routing](./docs/en/03-routing.md) | Route modules, controller contract, load order |
326
+ | [04-render](./docs/04-render-ve-sablonlar.md) / [rendering](./docs/en/04-rendering.md) | `.jsk` / EJS, layout, components, helpers, metadata |
327
+ | [05-islands](./docs/05-islands.md) / [islands](./docs/en/05-islands.md) | Island contract, hydration, store, DOM helpers |
328
+ | [06-cache](./docs/06-cache.md) / [caching](./docs/en/06-caching.md) | TTL, SWR, keys, prewarm, Redis, invalidation, admin |
329
+ | [07-yapilandirma](./docs/07-yapilandirma.md) / [configuration](./docs/en/07-configuration.md) | Complete `jskelet.config.mjs` reference |
330
+ | [08-build](./docs/08-build.md) / [build](./docs/en/08-build.md) | Build pipeline, manifest, Tailwind `@source`, sprite |
331
+ | [09-dev](./docs/09-dev-araclari.md) / [dev-tools](./docs/en/09-dev-tools.md) | Dev workflow, overlay, report page, dev gate |
332
+ | [10-dagitim](./docs/10-dagitim.md) / [deployment](./docs/en/10-deployment.md) | Production, Docker, reverse proxy, health checks |
333
+ | [11-tasima](./docs/11-tasima.md) / [migration](./docs/en/11-migration.md) | Migrating from Next.js: mapping table and plan |
334
+ | [12-panel](./docs/12-panel-ve-oturum.md) / [dashboards](./docs/en/12-dashboards-and-sessions.md) | Per-visitor pages: `private: true`, sessions, CSRF, fragments |
306
335
 
307
336
  If you work with AI agents, [AGENTS.md](./AGENTS.md) summarizes the rules that
308
337
  apply to this repository.
@@ -316,8 +345,8 @@ npm --prefix examples/marketing install && npm --prefix examples/marketing run d
316
345
  npm --prefix examples/dashboard install && npm --prefix examples/dashboard run dev
317
346
  ```
318
347
 
319
- - **`examples/minimal`** — two routes, one component, one island. The smallest
320
- thing that runs.
348
+ - **`examples/minimal`** — two routes, one component, one island, plus a
349
+ co-located `features/demo` slice. The smallest thing that runs.
321
350
  - **`examples/blog`** — dynamic routes, tag pages, every config section,
322
351
  fragment-loaded tabs, a form, prewarm, RSS and sitemap, four islands. It
323
352
  intentionally touches every surface of the framework.
package/bin/jskelet.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * JSkelet CLI.
4
4
  *
@@ -2,7 +2,7 @@
2
2
 
3
3
  Bu belge JSkelet'i sıfırdan çalıştırmayı anlatır: paket kurulumu, `jskelet init`
4
4
  ile iskeletin oluşturulması, ilk route ve ilk island'ın yazılması, oluşan dizin
5
- yapısının ne anlama geldiği ve CLI'ın dört komutu. Sonunda tarayıcıda sunucuda
5
+ yapısının ne anlama geldiği ve CLI komutları. Sonunda tarayıcıda sunucuda
6
6
  render edilmiş, önbelleğe alınmış ve island'ı görünürlükte hidre olan bir sayfa
7
7
  olacak. Kararların *nedenleri* için [02-mimari.md](./02-mimari.md)'ye, buradaki
8
8
  her config alanının tam referansı için
@@ -56,21 +56,24 @@ tamamlar, atlanan dosyaların sayısını uyarı olarak basar. Amaç, "kurulumu
56
56
  yaptım ama hiçbir şey çalışmıyor" aşamasını tamamen atlamak — `jskelet dev`
57
57
  hemen ardından çalışır.
58
58
 
59
- Oluşturulan dosyalar:
59
+ Oluşturulan dosyalar (feature-first + `.jsk`):
60
60
 
61
61
  ```
62
- jskelet.config.mjs config: brand, preconnect, cache(), hooks
63
- routes/10-pages.mjs "/" route'u
64
- views/pages/home.ejs ana sayfa şablonu
65
- views/pages/not-found.ejs 404 şablonu
66
- views/components/button.js örnek bileşen (HTML string döndüren fonksiyon)
67
- client/entries/main.js island bootstrap'ı
68
- client/islands/counter.js örnek island
69
- styles/globals.css Tailwind girişi + @source direktifleri
70
- jsconfig.json checkJs + "@/*" alias'ı
71
- .gitignore node_modules/, .jskelet/, public/assets/, .env
62
+ jskelet.config.mjs config: brand, preconnect, cache(), hooks
63
+ features/home/index.js "/" route'u
64
+ features/home/views/pages/home.jsk ana sayfa şablonu
65
+ features/home/views/components/button.js örnek bileşen (<Button />)
66
+ features/home/client/counter.js örnek island
67
+ features/home/server/.gitkeep
68
+ views/pages/not-found.jsk uygulama geneli 404
69
+ client/entries/main.js island bootstrap'ı
70
+ styles/globals.css Tailwind girişi + @source direktifleri
71
+ jsconfig.json checkJs + "@/*" alias'ı
72
+ .gitignore node_modules/, .jskelet/, public/assets/, .env
72
73
  ```
73
74
 
75
+ Büyümek için: `npx jskelet generate feature <name>` (veya `page` / `island`).
76
+
74
77
  Sonra:
75
78
 
76
79
  ```bash
@@ -88,44 +91,44 @@ ile ezilebilir. Aşağıdaki değerler varsayılanlardır (`src/config/defaults.
88
91
 
89
92
  | Dizin | Varsayılan | İçeriği |
90
93
  | --- | --- | --- |
91
- | `views` | `views` | EJS layout, sayfalar ve bileşenler |
94
+ | `views` | `views` | Uygulama geneli layout, sayfalar ve bileşenler |
95
+ | `features` | `features` | Feature dilimleri (`<name>/{server,views,client}`) |
96
+ | `shared` | `shared` | Feature'lar arası paylaşılan server/views/client |
92
97
  | `public` | `public` | Statik dosyalar; build çıktısı da buraya yazılır |
93
98
  | `client` | `client` | Island runtime kaynakları ve entry'ler |
94
- | `routes` | `routes` | Route modülleri |
99
+ | `routes` | `routes` | Route modülleri (feature'lardan önce yüklenir) |
95
100
  | `styles` | `styles/globals.css` | Tailwind/PostCSS giriş **dosyası** |
96
- | `generated` | `.jskelet` | Build ara çıktıları: `manifest.json`, `metafile.json`, `images.json` |
101
+ | `generated` | `.jskelet` | Build ara çıktıları: `manifest.json`, `metafile.json`, `images.json`, `templates/` |
97
102
 
98
103
  Bunlara ek olarak framework iki yolu her zaman türetir ve ayrı ayar kabul
99
104
  etmez: `public/assets` (hash'li build çıktısı) ve `public/fonts` (self-host
100
105
  fontlar).
101
106
 
102
- Tipik bir proje:
107
+ Tipik bir proje (`jskelet init` çıktısına yakın):
103
108
 
104
109
  ```
105
110
  benim-sitem/
106
111
  ├── jskelet.config.mjs
107
112
  ├── jsconfig.json
108
- ├── routes/
109
- │ ├── 10-pages.mjs
110
- │ └── 90-catch-all.mjs
113
+ ├── features/
114
+ │ └── home/
115
+ │ ├── index.js
116
+ │ ├── server/
117
+ │ ├── views/
118
+ │ │ ├── pages/home.jsk
119
+ │ │ └── components/button.js
120
+ │ └── client/counter.js
111
121
  ├── views/
112
- │ ├── layout.ejs
113
- │ ├── pages/
114
- │ │ ├── home.ejs
115
- │ │ └── not-found.ejs
116
- │ └── components/
117
- │ └── card.js
122
+ │ └── pages/not-found.jsk
118
123
  ├── client/
119
- │ ├── entries/
120
- │ │ └── main.js
121
- │ └── islands/
122
- │ └── counter.js
124
+ │ └── entries/main.js
123
125
  ├── styles/
124
126
  │ └── globals.css
125
127
  ├── public/
126
128
  │ └── (statik dosyalar; build → public/assets)
127
129
  └── .jskelet/
128
- └── manifest.json
130
+ ├── manifest.json
131
+ └── templates/
129
132
  ```
130
133
 
131
134
  ## İlk route
@@ -135,7 +138,7 @@ kendi yollarını `app.get(...)` ile açıkça yazar. Modül sözleşmesi: defau
135
138
  export ya da `register` adlı named export, `(app, api)` imzasıyla.
136
139
 
137
140
  ```js
138
- // routes/10-pages.mjs
141
+ // features/home/index.js
139
142
  export default function register(app, { route }) {
140
143
  app.get(
141
144
  "/",
@@ -143,7 +146,7 @@ export default function register(app, { route }) {
143
146
  async () => ({
144
147
  view: "pages/home",
145
148
  metadata: { title: "Ana sayfa" },
146
- data: { heading: "JSkelet çalışıyor", items: ["Bir", "İki"] },
149
+ data: { message: "JSkelet çalışıyor" },
147
150
  }),
148
151
  { revalidate: 60 },
149
152
  ),
@@ -157,25 +160,26 @@ yapmak zorunda kalmaz. `route()` controller'ı sarar: HTML cache'i,
157
160
  notFound/redirect kontrol akışı, sıkıştırma ve `X-JSkelet-Cache` başlığı ondan
158
161
  gelir. Controller'ın tek işi bir sayfa tanımı döndürmektir.
159
162
 
160
- Dosya adındaki `10-` öneki yükleme sırasını belirler. `routes/` alfabetik
161
- tarandığı için `/:slug` gibi yakalayıcı route'ları daha yüksek numaralı bir
162
- dosyaya koymalısınız; aksi hâlde `/hakkinda` bir slug sanılır. Ayrıntı:
163
- [03-routing.md](./03-routing.md).
163
+ `routes/` kullanıyorsanız dosya adındaki `10-` öneki yükleme sırasını belirler;
164
+ `/:slug` gibi yakalayıcı route'ları daha yüksek numaralı bir dosyaya koyun.
165
+ Feature `index.js` dosyaları `routes/` tarandıktan sonra alfabetik eklenir.
166
+ Ayrıntı: [03-routing.md](./03-routing.md).
164
167
 
165
- Şablon tarafı düz EJS:
168
+ Şablon tarafı `.jsk` (build-time derlenir):
166
169
 
167
- ```ejs
168
- <%# views/pages/home.ejs %>
170
+ ```html
171
+ {# features/home/views/pages/home.jsk #}
169
172
  <section class="wrapper">
170
- <h1 class="text-3xl font-bold"><%= heading %></h1>
171
- <%- list({ items }) %>
172
- <div data-island="counter" data-island-props='{"start":5}'></div>
173
+ <h1>{{ metadata.title }}</h1>
174
+ <p>{{ message }}</p>
175
+ <Button text="Örnek bileşen" />
176
+ <div data-island="counter" data-island-props='{"start":0}'></div>
173
177
  </section>
174
178
  ```
175
179
 
176
- `list` burada `views/components/list.js` içinde tanımlı bir fonksiyondur ve
177
- import edilmemiştir: `views/components/**` altındaki her named export otomatik
178
- olarak şablon local'i olur ([04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)).
180
+ `Button`, `features/home/views/components/button.js` içindeki `button` named
181
+ export'undan gelir — PascalCase etiket; import gerekmez
182
+ ([04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)).
179
183
 
180
184
  ## İlk island
181
185
 
@@ -193,7 +197,7 @@ iki parçadan oluşur.
193
197
  verir.
194
198
 
195
199
  ```js
196
- // client/islands/counter.js
200
+ // features/home/client/counter.js
197
201
  /**
198
202
  * @param {HTMLElement} element
199
203
  * @param {{ start?: number }} props
@@ -225,7 +229,7 @@ runtime'ı başlatır.
225
229
  import { registerAll, start } from "jskelet/client";
226
230
 
227
231
  registerAll({
228
- counter: () => import("../islands/counter.js"),
232
+ counter: () => import("../../features/home/client/counter.js"),
229
233
  });
230
234
 
231
235
  start();
@@ -239,7 +243,7 @@ haritayı büyütmek ilk yükü büyütmez. Hidrasyon stratejileri
239
243
 
240
244
  ## CLI komutları
241
245
 
242
- `bin/jskelet.mjs` dört alt komut sunar. Her biri ayrı bir Node sürecinde
246
+ `bin/jskelet.mjs` şu alt komutları sunar. Her biri ayrı bir Node sürecinde
243
247
  çalışır; sebebi `dev`in iki uzun ömürlü süreci yönetmesi ve sunucunun ESM
244
248
  resolve hook'larına (`--import`) süreç başlangıcında ihtiyaç duyması.
245
249
 
@@ -248,7 +252,8 @@ resolve hook'larına (`--import`) süreç başlangıcında ihtiyaç duyması.
248
252
  | `jskelet dev` | Build watch + sunucu, tek terminalde. Canlı yenileme, CSS hot-swap, dev overlay. `NODE_ENV=development`. |
249
253
  | `jskelet build` | Tek seferlik prod build: fontlar → ikon sprite → CSS → client JS → görseller → manifest → precompress. `NODE_ENV` verilmemişse `production`. |
250
254
  | `jskelet start` | Prod sunucu. Build çıktısı yoksa önce üretir. `NODE_ENV` verilmemişse `production`. |
251
- | `jskelet init` | Bulunduğun dizine minimal iskelet kurar; var olan dosyalara dokunmaz. |
255
+ | `jskelet init` | Bulunduğun dizine feature-first `.jsk` iskeleti kurar; var olan dosyalara dokunmaz. |
256
+ | `jskelet generate` | `feature` / `page` / `island` iskeleti üretir. |
252
257
 
253
258
  Bilinmeyen bir komut ya da argümansız çağrı kullanım metnini basar.
254
259