jskelet 0.3.5 → 0.4.1
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 +34 -0
- package/bin/jskelet.mjs +20 -7
- package/docs/02-mimari.md +3 -0
- package/docs/03-routing.md +19 -3
- package/docs/04-render-ve-sablonlar.md +94 -9
- package/docs/06-cache.md +62 -0
- package/docs/07-yapilandirma.md +7 -5
- package/docs/08-build.md +5 -0
- package/docs/en/02-architecture.md +3 -0
- package/docs/en/03-routing.md +19 -4
- package/docs/en/04-rendering.md +96 -12
- package/docs/en/06-caching.md +133 -71
- package/docs/en/07-configuration.md +5 -3
- package/docs/en/08-build.md +5 -0
- package/package.json +2 -2
- package/src/build/build.mjs +6 -0
- package/src/build/ensure-build.mjs +5 -1
- package/src/build/tasks/icons.mjs +2 -2
- package/src/build/tasks/templates.mjs +20 -0
- package/src/compile/codegen.js +332 -0
- package/src/compile/compile-all.js +158 -0
- package/src/compile/errors.js +66 -0
- package/src/compile/expr.js +404 -0
- package/src/compile/index.js +17 -0
- package/src/compile/parse.js +485 -0
- package/src/compile/resolve.js +208 -0
- package/src/compile/scan-exports.js +51 -0
- package/src/config/defaults.js +6 -2
- package/src/config/index.js +7 -0
- package/src/dev-server.mjs +4 -2
- package/src/generate.mjs +163 -0
- package/src/init.mjs +8 -6
- package/src/server/render.js +153 -40
- package/src/server/router.js +26 -3
- package/src/views/components/loader.js +34 -18
package/CHANGELOG.md
CHANGED
|
@@ -27,6 +27,29 @@ 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.
|
|
41
|
+
- Build-time `.jsk` templates: declarative HTML-like syntax compiled to ESM
|
|
42
|
+
render modules under `.jskelet/templates/` (no request-time parse, `eval`, or
|
|
43
|
+
`new Function`). Coexists with EJS; compiled `.jsk` wins when both exist.
|
|
44
|
+
Syntax: `{{ }}` / `{{{ }}}`, `{#if}` / `{#each}` / `{#include}`, PascalCase
|
|
45
|
+
components (`:prop` bindings), built-ins `Link` / `Image` / `Icon` /
|
|
46
|
+
`CsrfField` / `PreloadImage`.
|
|
47
|
+
- Feature-first conventions: `paths.features` / `paths.shared`, multi-root
|
|
48
|
+
views and components, `features/<name>/index.js` route registration after
|
|
49
|
+
`routes/`. CLI: `jskelet generate feature|page|island`. `jskelet init`
|
|
50
|
+
scaffolds `.jsk` pages.
|
|
51
|
+
- Template compile step in `jskelet build`; icon scan and Tailwind docs cover
|
|
52
|
+
`.jsk` / `features` / `shared`. Bench: `node scripts/bench-templates.mjs`.
|
|
30
53
|
- Top-level `logs` config for persistent sinks: daily NDJSON files
|
|
31
54
|
(`logs.file`) and batched S3 PutObject (`logs.s3`) with embedded SigV4 — no
|
|
32
55
|
`@aws-sdk` dependency. `kinds` selects `http` / `event` / `error`; `console`
|
|
@@ -113,6 +136,17 @@ one is listed under a **Breaking** heading.
|
|
|
113
136
|
variant of a path, which is the right default for a webhook but wrong when you
|
|
114
137
|
want `/list?page=2` gone and `/list?page=3` left hot.
|
|
115
138
|
|
|
139
|
+
### Changed
|
|
140
|
+
|
|
141
|
+
- Duplicate component named exports (or the same PascalCase tag in two files)
|
|
142
|
+
now **fail** at build and at server startup instead of warning and letting
|
|
143
|
+
the second definition win. Overwriting `components/index.js` barrel exports
|
|
144
|
+
remains allowed.
|
|
145
|
+
- `examples/minimal` pages moved to `.jsk`; adds `features/demo` as a
|
|
146
|
+
co-located route + view sample.
|
|
147
|
+
- Marketing compare/FAQ copy no longer claims targeted invalidation is missing;
|
|
148
|
+
it points at `invalidateHtmlCache()` (and Redis pub/sub for multi-instance).
|
|
149
|
+
|
|
116
150
|
- An adaptive per-host rate limit for upstream calls, `cache().upstream`. It sits
|
|
117
151
|
in the `fetch` wrapper rather than in the prewarm pass, because what spends the
|
|
118
152
|
quota is the API call, not the page: one render may make one call or twenty, so
|
package/bin/jskelet.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* JSkelet CLI.
|
|
4
4
|
*
|
|
5
5
|
* jskelet dev build watch + sunucu, canlı yenileme, dev overlay
|
|
6
6
|
* jskelet build tek seferlik prod build (fontlar, sprite, CSS, JS, görseller)
|
|
7
7
|
* jskelet start prod sunucu (build eksikse önce üretir)
|
|
8
|
-
* jskelet init
|
|
8
|
+
* jskelet init bulunduğun dizine minimal iskelet kurar
|
|
9
|
+
* jskelet generate feature / page / island iskeleti
|
|
9
10
|
*
|
|
10
11
|
* Alt komutlar ayrı süreçlerde çalışır. Sebep: `dev` iki uzun ömürlü süreci
|
|
11
12
|
* (build watch + sunucu) yönetiyor ve sunucunun ESM resolve hook'larına
|
|
@@ -90,14 +91,26 @@ switch (command) {
|
|
|
90
91
|
break;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
case "generate": {
|
|
95
|
+
const { generate } = await import("../src/generate.mjs");
|
|
96
|
+
try {
|
|
97
|
+
await generate(process.cwd(), rest);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
process.stderr.write(`${error instanceof Error ? error.message : error}\n`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
default: {
|
|
94
106
|
const known = command ? `unknown command: ${command}\n\n` : "";
|
|
95
107
|
process.stderr.write(
|
|
96
|
-
`${known}usage: jskelet <dev|build|start|init>\n\n` +
|
|
97
|
-
" dev
|
|
98
|
-
" build
|
|
99
|
-
" start
|
|
100
|
-
" init
|
|
108
|
+
`${known}usage: jskelet <dev|build|start|init|generate>\n\n` +
|
|
109
|
+
" dev build watch + server (live reload, dev overlay)\n" +
|
|
110
|
+
" build production build\n" +
|
|
111
|
+
" start production server\n" +
|
|
112
|
+
" init scaffold a minimal skeleton in the current directory\n" +
|
|
113
|
+
" generate scaffold feature | page | island\n",
|
|
101
114
|
);
|
|
102
115
|
process.exit(command ? 1 : 0);
|
|
103
116
|
}
|
package/docs/02-mimari.md
CHANGED
|
@@ -25,6 +25,9 @@ JSkelet bu gözlemi mimarinin merkezine alır:
|
|
|
25
25
|
olarak, kendi modülüyle, kendi zamanında bağlanır.
|
|
26
26
|
3. **Sayfa üretimi önbelleklenir.** Aynı HTML'i her istekte yeniden üretmenin
|
|
27
27
|
anlamı yok; TTL'li bir bellek önbelleği ISR'nin yerini tutar.
|
|
28
|
+
4. **Şablonlar build-time derlenir (`.jsk`).** İstek anında parse yok; EJS
|
|
29
|
+
legacy olarak yan yana kalır. Feature'lar `features/<name>/{server,views,client}`
|
|
30
|
+
altında toplanabilir — URL kaydı yine açıktır.
|
|
28
31
|
|
|
29
32
|
## Bir isteğin yolu
|
|
30
33
|
|
package/docs/03-routing.md
CHANGED
|
@@ -63,9 +63,25 @@ export default {
|
|
|
63
63
|
};
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
**2. Liste yoksa `routes/` dizini alfabetik taranır
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
**2. Liste yoksa `routes/` dizini alfabetik taranır**, ardından her
|
|
67
|
+
`features/<name>/index.js` (veya `.mjs`) alfabetik eklenir. Tarama
|
|
68
|
+
özyinelemelidir (alt dizinler de dâhil), yalnızca `.js` ve `.mjs` dosyaları
|
|
69
|
+
alınır ve adı `_` ile başlayan dosyalar atlanır (`_helpers.js` gibi paylaşılan
|
|
70
|
+
modüller için).
|
|
71
|
+
|
|
72
|
+
Feature-first düzen zorunlu değildir; bir feature örneği:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
features/piyasalar/
|
|
76
|
+
index.js # register(app, api) — URL'ler açıkça yazılır
|
|
77
|
+
server/
|
|
78
|
+
views/pages/… # .jsk veya .ejs
|
|
79
|
+
views/components/
|
|
80
|
+
client/ # island'lar; client/entries'ten registerAll
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`jskelet generate feature|page|island` iskelet üretir. Filesystem URL routing
|
|
84
|
+
yoktur.
|
|
69
85
|
|
|
70
86
|
Bu durumda dosya adlarına sayısal önek verin:
|
|
71
87
|
|
|
@@ -20,17 +20,96 @@ route(controller)
|
|
|
20
20
|
│ renderView(page.view, { …data, metadata }), → body
|
|
21
21
|
│ hooks.layoutContext({ pathname, metadata }), → context
|
|
22
22
|
│ ])
|
|
23
|
-
└─ layout.ejs
|
|
23
|
+
└─ layout (.jsk derlenmiş veya .ejs) → tam HTML
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Layout bağlamı ve gövde **paralel** üretilir. Sebebi ölçümden geliyor:
|
|
27
27
|
navigasyon çoğu projede upstream'den geliyor ve gövde render'ıyla sırayla
|
|
28
28
|
beklemek her sayfaya gereksiz gecikme ekliyor.
|
|
29
29
|
|
|
30
|
-
##
|
|
30
|
+
## `.jsk` — build-time derlenmiş şablonlar
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
Yeni uygulamalarda varsayılan şablon biçimi `.jsk`'dir. Build sırasında
|
|
33
|
+
(`.jskelet/templates/*.mjs`) normal ESM modüllerine çevrilir; **istek anında
|
|
34
|
+
parse / `eval` / `new Function` yoktur**. Production yolu:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
controller data → import edilmiş render(data, helpers) → HTML
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Sözdizimi özeti
|
|
41
|
+
|
|
42
|
+
```html
|
|
43
|
+
<section class="wrapper">
|
|
44
|
+
<h1>{{ title }}</h1>
|
|
45
|
+
<div>{{{ trustedHtml }}}</div>
|
|
46
|
+
|
|
47
|
+
{#if items.length}
|
|
48
|
+
<List :items="items" />
|
|
49
|
+
{#else}
|
|
50
|
+
<p>Boş</p>
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
{#each items as item, i}
|
|
54
|
+
<li data-i="{{ i }}">{{ item }}</li>
|
|
55
|
+
{/each}
|
|
56
|
+
|
|
57
|
+
<Link href="/" text="Home" />
|
|
58
|
+
<div data-island="counter" data-island-props='{"start":0}'></div>
|
|
59
|
+
</section>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Özellik | Yazım |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| Kaçışlı metin | `{{ expr }}` |
|
|
65
|
+
| Ham HTML | `{{{ expr }}}` |
|
|
66
|
+
| Koşul | `{#if expr}` … `{#else}` … `{/if}` |
|
|
67
|
+
| Döngü | `{#each list as item}` veya `as item, i` |
|
|
68
|
+
| Include | `{#include "partials/header"}` (derlenmiş `.jsk`) |
|
|
69
|
+
| Bileşen | PascalCase etiket; `:prop="expr"`, `prop="literal"`, boolean `disabled` |
|
|
70
|
+
| Yerleşikler | `Link`, `Image`, `Icon`, `CsrfField`, `PreloadImage` |
|
|
71
|
+
|
|
72
|
+
İfade dili kasıtlı olarak dardır (erişim, karşılaştırma, ternary, `.length`).
|
|
73
|
+
Atama, object literal ve rastgele fonksiyon çağrısı yok — mantık controller
|
|
74
|
+
veya JS bileşende kalır.
|
|
75
|
+
|
|
76
|
+
#### Şablon mu, bileşen mi?
|
|
77
|
+
|
|
78
|
+
EJS’den geçerken sınırı erken çizmek işe yarar:
|
|
79
|
+
|
|
80
|
+
| Burada kalsın (`.jsk`) | JS bileşene taşı |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| Metin, koşul, liste, prop bağlama | Fonksiyon çağrısı, nesne üretimi, biçimlendirme |
|
|
83
|
+
| Yerleşik etiketler (`Link`, `Image`, …) | Birden fazla yardımcıdan HTML birleştirme |
|
|
84
|
+
| Controller’dan gelen hazır veri | Upstream / hata ayırt eden UI (`LoadErrorState`) |
|
|
85
|
+
|
|
86
|
+
Şablonda `format(x)` veya `{ a: 1 }` yazılamıyorsa bu bir eksik değil: o iş
|
|
87
|
+
`views/components/*.js` veya controller’ındır. Karmaşık sayfalar bileşene
|
|
88
|
+
kaçıyorsa ifade dilini genişletmek yerine bileşen sınırını net tutmak tercih
|
|
89
|
+
edilir.
|
|
90
|
+
|
|
91
|
+
### Editör desteği
|
|
92
|
+
|
|
93
|
+
Repo içinde `extensions/vscode-jsk` VS Code / Cursor uzantısı vardır: sözdizimi
|
|
94
|
+
renklendirme, dil yapılandırması ve snippet'ler. Yerel kurulum:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
code --install-extension extensions/vscode-jsk
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Ayrıntılar uzantı README'sinde.
|
|
101
|
+
|
|
102
|
+
### EJS ile birlikte yaşam
|
|
103
|
+
|
|
104
|
+
Aynı `view` id için derlenmiş `.jsk` varsa o kullanılır; yoksa `.ejs` dosyası
|
|
105
|
+
EJS ile render edilir. Mevcut uygulamalar değişmeden çalışır. `jskelet init`
|
|
106
|
+
yeni iskeleti `.jsk` ile kurar.
|
|
107
|
+
|
|
108
|
+
## EJS motoru (legacy)
|
|
109
|
+
|
|
110
|
+
EJS hâlâ desteklenir. Motor ilk render'da bir kez kurulur; bileşen taraması
|
|
111
|
+
dosya sistemine dokunduğu için her istekte yapılamaz ve config yüklenmeden
|
|
112
|
+
hesaplanamaz.
|
|
34
113
|
|
|
35
114
|
Ayarlar:
|
|
36
115
|
|
|
@@ -52,8 +131,9 @@ normal akışta gerekmez.
|
|
|
52
131
|
1. `jskelet.config.mjs` → `layout` verilmişse o kullanılır. Yol, **views
|
|
53
132
|
dizininin üst dizinine** göre çözülür: `views` varsayılansa
|
|
54
133
|
`layout: "views/ozel.ejs"` → `<root>/views/ozel.ejs`.
|
|
55
|
-
2. Verilmemişse `views/layout.
|
|
56
|
-
3.
|
|
134
|
+
2. Verilmemişse `views/layout.jsk` (derlenmiş) varsa o kullanılır.
|
|
135
|
+
3. Yoksa `views/layout.ejs` varsa o kullanılır.
|
|
136
|
+
4. O da yoksa framework'ün kendi minimal layout'u kullanılır
|
|
57
137
|
(`node_modules/jskelet/src/templates/layout.ejs`, ayrıca
|
|
58
138
|
`jskelet/layout` belirteciyle de erişilebilir).
|
|
59
139
|
|
|
@@ -189,13 +269,18 @@ Kurallar:
|
|
|
189
269
|
|
|
190
270
|
- Tarama özyinelemelidir; alt dizinler de kapsanır.
|
|
191
271
|
- `default` export'lar yok sayılır — yalnızca named export'lar kaydedilir.
|
|
272
|
+
- Compile-time bilinen bileşen listesi **dosya adından değil**, kaynak
|
|
273
|
+
metindeki named export'lardan okunur. `ui.js` içindeki `sectionHead` →
|
|
274
|
+
şablonda `<SectionHead />` (runtime zaten camelCase export'a PascalCase
|
|
275
|
+
alias ekler). Dosya adına göre stub re-export eklemeye gerek yoktur.
|
|
192
276
|
- `loader.js` ve `index.js` bileşen dosyası sayılmaz.
|
|
193
277
|
- `views/components/index.js` varsa **barrel** olarak, en düşük öncelikle en
|
|
194
278
|
önce yüklenir. Tek amacı `lib/` yeniden ihraçlarını şablon local'i yapmak;
|
|
195
279
|
bileşenlerin kendi dosyaları sonradan gelip sessizce üzerine yazar.
|
|
196
|
-
- Aynı ad iki farklı bileşen dosyasında
|
|
197
|
-
|
|
198
|
-
|
|
280
|
+
- Aynı ad (veya aynı PascalCase etiket) iki farklı bileşen dosyasında
|
|
281
|
+
tanımlıysa **uyarı değil hata**: build ve sunucu açılışı
|
|
282
|
+
`Component 'card' is defined twice: …` ile durur. Barrel üzerine yazmak
|
|
283
|
+
bilinçli istisnadır.
|
|
199
284
|
- `views/components` dizini yoksa bileşen kaydı boş kalır; bileşen kullanmayan
|
|
200
285
|
bir proje de çalışır.
|
|
201
286
|
|
package/docs/06-cache.md
CHANGED
|
@@ -380,6 +380,68 @@ export async function apiGet(path) {
|
|
|
380
380
|
}
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
+
### Loader sözleşmesi: boş liste ≠ hata
|
|
384
|
+
|
|
385
|
+
`catch → []` (veya `null`) ile yutulan bir upstream hatası, yanlış mapping ile
|
|
386
|
+
aynı görünür: boş UI. Rate limit ve geçici hatalar logda doğru yönde
|
|
387
|
+
işaretlense bile ziyaretçi “veri yok” sanır. Widget loader’ları sessiz
|
|
388
|
+
`[]`’ye gömülmek yerine sonucu ayırsın:
|
|
389
|
+
|
|
390
|
+
```js
|
|
391
|
+
/**
|
|
392
|
+
* @returns {Promise<{ items: object[], error: Error | null }>}
|
|
393
|
+
*/
|
|
394
|
+
export async function loadTickerItems() {
|
|
395
|
+
try {
|
|
396
|
+
const items = await apiGet("/ticker");
|
|
397
|
+
if (!items) {
|
|
398
|
+
return { items: [], error: new Error("Upstream returned no data") };
|
|
399
|
+
}
|
|
400
|
+
return { items, error: null };
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return {
|
|
403
|
+
items: [],
|
|
404
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Uygulama tarafında ortak bir `LoadErrorState` bileşeni (veya eşdeğeri) bu
|
|
411
|
+
`error` alanını göstersin; her widget kendi boş hâline düşmesin:
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
// views/components/load-error-state.js
|
|
415
|
+
import { esc } from "jskelet/html";
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @param {{ message?: string, title?: string }} props
|
|
419
|
+
* @returns {string}
|
|
420
|
+
*/
|
|
421
|
+
export function LoadErrorState({ message, title = "Veri yüklenemedi" }) {
|
|
422
|
+
return `<div role="alert" data-load-error class="…">
|
|
423
|
+
<p>${esc(title)}</p>
|
|
424
|
+
${message ? `<p>${esc(message)}</p>` : ""}
|
|
425
|
+
</div>`;
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```html
|
|
430
|
+
{#if error}
|
|
431
|
+
<LoadErrorState :message="error.message" />
|
|
432
|
+
{#else if items.length}
|
|
433
|
+
{#each items as item}
|
|
434
|
+
…
|
|
435
|
+
{/each}
|
|
436
|
+
{#else}
|
|
437
|
+
<p>Kayıt yok</p>
|
|
438
|
+
{/if}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Framework markaya özel UI taşımaz; `LoadErrorState` uygulama bileşenidir.
|
|
442
|
+
Önemli olan sözleşme: `{ items, error }` (veya eşdeğeri) ve hata ile “gerçekten
|
|
443
|
+
boş”un şablonda ayrı kolları.
|
|
444
|
+
|
|
383
445
|
### Geçici ve kalıcı hata ayrımı
|
|
384
446
|
|
|
385
447
|
| Durum | Sayılır | Sonuç |
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -150,12 +150,14 @@ göre çözülür ve içeride mutlak yola çevrilir.
|
|
|
150
150
|
|
|
151
151
|
| Anahtar | Varsayılan | İçeriği |
|
|
152
152
|
| --- | --- | --- |
|
|
153
|
-
| `views` | `"views"` |
|
|
153
|
+
| `views` | `"views"` | Layout, sayfalar, bileşenler (klasik kök; `.jsk` / `.ejs`) |
|
|
154
|
+
| `features` | `"features"` | Feature-first dilimler (`<name>/{server,views,client}`) |
|
|
155
|
+
| `shared` | `"shared"` | Özellikler arası paylaşılan server/views/client |
|
|
154
156
|
| `public` | `"public"` | Statik dosyalar; build çıktısı da buraya yazılır |
|
|
155
157
|
| `client` | `"client"` | Island runtime kaynakları ve entry'ler |
|
|
156
158
|
| `routes` | `"routes"` | Route modülleri |
|
|
157
159
|
| `styles` | `"styles/globals.css"` | Tailwind/PostCSS giriş **dosyası** |
|
|
158
|
-
| `generated` | `".jskelet"` | `manifest.json`, `metafile.json`, `images.json` |
|
|
160
|
+
| `generated` | `".jskelet"` | `manifest.json`, `templates/`, `metafile.json`, `images.json` |
|
|
159
161
|
|
|
160
162
|
`styles` bir dosya yolu olduğu hâlde aynı çözümlemeden geçer; ayrı bir alan
|
|
161
163
|
tutmaya değmiyor.
|
|
@@ -200,8 +202,8 @@ Layout `.ejs` dosyasının yolu. Verilen değer **views dizininin üst dizinine*
|
|
|
200
202
|
göre çözülür, yani varsayılan `views` ile `"views/ozel.ejs"` →
|
|
201
203
|
`<root>/views/ozel.ejs`.
|
|
202
204
|
|
|
203
|
-
Verilmezse sırayla: `views/layout.ejs
|
|
204
|
-
layout'u. Ayrıntı: [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md).
|
|
205
|
+
Verilmezse sırayla: `views/layout.jsk`, `views/layout.ejs`, yoksa framework'ün
|
|
206
|
+
minimal layout'u. Ayrıntı: [04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md).
|
|
205
207
|
|
|
206
208
|
## `routes`
|
|
207
209
|
|
|
@@ -467,7 +469,7 @@ Phosphor SVG sprite üretimi.
|
|
|
467
469
|
|
|
468
470
|
| Değer | Sonuç |
|
|
469
471
|
| --- | --- |
|
|
470
|
-
| `{}` (varsayılan) | Sprite üretilir; taranan dizinler `["views", "client", "routes", "lib"]` |
|
|
472
|
+
| `{}` (varsayılan) | Sprite üretilir; taranan dizinler `["views", "client", "routes", "lib", "features", "shared"]` |
|
|
471
473
|
| `{ scan: [...] }` | Taranan dizinler değiştirilir |
|
|
472
474
|
| `false` | Sprite adımı tamamen atlanır |
|
|
473
475
|
|
package/docs/08-build.md
CHANGED
|
@@ -12,6 +12,7 @@ davranışı burada. Çıktının çalışma anında nasıl servis edildiği
|
|
|
12
12
|
## Hat ve sırası
|
|
13
13
|
|
|
14
14
|
```
|
|
15
|
+
0. Templates .jsk → .jskelet/templates/*.mjs (her zaman; dosya yoksa no-op)
|
|
15
16
|
1. Fonts config.fonts varsa
|
|
16
17
|
2. Icon sprite config.icons !== false ise
|
|
17
18
|
3. CSS styles giriş dosyası varsa
|
|
@@ -21,6 +22,10 @@ davranışı burada. Çıktının çalışma anında nasıl servis edildiği
|
|
|
21
22
|
7. Precompress watch değilse
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
Şablon derlemesi asset taramasından **önce** biter; istek yolunda parse yoktur.
|
|
26
|
+
Tailwind `@source` ve ikon taraması kaynak `.jsk` dosyalarını okur (üretilmiş
|
|
27
|
+
`.mjs` değil).
|
|
28
|
+
|
|
24
29
|
Sıra rastgele değil:
|
|
25
30
|
|
|
26
31
|
- **CSS ikon sprite'ından sonra gelir.** Sprite bir varlıktır ve sınıf üretmez,
|
|
@@ -25,6 +25,9 @@ JSkelet puts this observation at the centre of the architecture:
|
|
|
25
25
|
independent "island", with its own module, at its own time.
|
|
26
26
|
3. **Page production is cached.** There is no point in producing the same HTML
|
|
27
27
|
again on every request; a memory cache with a TTL takes the place of ISR.
|
|
28
|
+
4. **Templates are compiled at build time (`.jsk`).** No request-time parsing;
|
|
29
|
+
EJS remains as a legacy path. Features may co-locate under
|
|
30
|
+
`features/<name>/{server,views,client}` — route URLs stay explicit.
|
|
28
31
|
|
|
29
32
|
## The path of a request
|
|
30
33
|
|
package/docs/en/03-routing.md
CHANGED
|
@@ -65,10 +65,25 @@ export default {
|
|
|
65
65
|
};
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
**2. If there is no list, the `routes/` directory is scanned alphabetically
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
**2. If there is no list, the `routes/` directory is scanned alphabetically**,
|
|
69
|
+
then each `features/<name>/index.js` (or `.mjs`) is appended alphabetically.
|
|
70
|
+
The scan under `routes/` is recursive (subdirectories included), only `.js` and
|
|
71
|
+
`.mjs` files are picked up, and files whose name begins with `_` are skipped
|
|
72
|
+
(for shared modules like `_helpers.js`).
|
|
73
|
+
|
|
74
|
+
Feature-first layout is optional:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
features/markets/
|
|
78
|
+
index.js # register(app, api) — URLs are still explicit
|
|
79
|
+
server/
|
|
80
|
+
views/pages/… # .jsk or .ejs
|
|
81
|
+
views/components/
|
|
82
|
+
client/ # islands; register from client/entries
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`jskelet generate feature|page|island` scaffolds this. There is no filesystem
|
|
86
|
+
URL routing.
|
|
72
87
|
|
|
73
88
|
In that case, give the file names a numeric prefix:
|
|
74
89
|
|
package/docs/en/04-rendering.md
CHANGED
|
@@ -21,18 +21,95 @@ route(controller)
|
|
|
21
21
|
│ renderView(page.view, { …data, metadata }), → body
|
|
22
22
|
│ hooks.layoutContext({ pathname, metadata }), → context
|
|
23
23
|
│ ])
|
|
24
|
-
└─ layout.ejs
|
|
24
|
+
└─ layout (.jsk compiled or .ejs) → full HTML
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
The layout context and the body are produced **in parallel**. The reason comes
|
|
28
28
|
from measurement: in most projects navigation comes from upstream, and waiting
|
|
29
29
|
for it in sequence with the body render adds needless latency to every page.
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## `.jsk` — build-time compiled templates
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
New apps default to `.jsk`. At build time they become normal ESM modules under
|
|
34
|
+
`.jskelet/templates/*.mjs`. There is **no request-time parsing, `eval`, or
|
|
35
|
+
`new Function`**. Production path:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
controller data → imported render(data, helpers) → HTML
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Syntax summary
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<section class="wrapper">
|
|
45
|
+
<h1>{{ title }}</h1>
|
|
46
|
+
<div>{{{ trustedHtml }}}</div>
|
|
47
|
+
|
|
48
|
+
{#if items.length}
|
|
49
|
+
<List :items="items" />
|
|
50
|
+
{#else}
|
|
51
|
+
<p>Empty</p>
|
|
52
|
+
{/if}
|
|
53
|
+
|
|
54
|
+
{#each items as item, i}
|
|
55
|
+
<li data-i="{{ i }}">{{ item }}</li>
|
|
56
|
+
{/each}
|
|
57
|
+
|
|
58
|
+
<Link href="/" text="Home" />
|
|
59
|
+
<div data-island="counter" data-island-props='{"start":0}'></div>
|
|
60
|
+
</section>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Feature | Form |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| Escaped text | `{{ expr }}` |
|
|
66
|
+
| Raw HTML | `{{{ expr }}}` |
|
|
67
|
+
| Conditional | `{#if expr}` … `{#else}` … `{/if}` |
|
|
68
|
+
| Loop | `{#each list as item}` or `as item, i` |
|
|
69
|
+
| Include | `{#include "partials/header"}` (compiled `.jsk`) |
|
|
70
|
+
| Component | PascalCase tag; `:prop="expr"`, `prop="literal"`, boolean `disabled` |
|
|
71
|
+
| Built-ins | `Link`, `Image`, `Icon`, `CsrfField`, `PreloadImage` |
|
|
72
|
+
|
|
73
|
+
The expression language is intentionally small (access, compare, ternary,
|
|
74
|
+
`.length`). No assignments, object literals, or arbitrary calls — keep logic in
|
|
75
|
+
controllers or JS components.
|
|
76
|
+
|
|
77
|
+
#### Template or component?
|
|
78
|
+
|
|
79
|
+
When moving off EJS, draw the line early:
|
|
80
|
+
|
|
81
|
+
| Stay in `.jsk` | Move to a JS component |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| Text, conditionals, lists, prop binding | Function calls, object construction, formatting |
|
|
84
|
+
| Built-in tags (`Link`, `Image`, …) | Composing HTML from several helpers |
|
|
85
|
+
| Ready-made data from the controller | Upstream / error-aware UI (`LoadErrorState`) |
|
|
86
|
+
|
|
87
|
+
If the template cannot write `format(x)` or `{ a: 1 }`, that is intentional: the
|
|
88
|
+
work belongs in `views/components/*.js` or the controller. Prefer a clear
|
|
89
|
+
component boundary over widening the expression language when complex pages
|
|
90
|
+
“escape” into JS.
|
|
91
|
+
|
|
92
|
+
### Editor support
|
|
93
|
+
|
|
94
|
+
`extensions/vscode-jsk` is a VS Code / Cursor extension in this repo: syntax
|
|
95
|
+
highlighting, language configuration, and snippets. Local install:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
code --install-extension extensions/vscode-jsk
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
See the extension README for details.
|
|
102
|
+
|
|
103
|
+
### Coexistence with EJS
|
|
104
|
+
|
|
105
|
+
If a compiled `.jsk` exists for a view id it wins; otherwise `.ejs` is rendered
|
|
106
|
+
with EJS. Existing apps keep working unchanged. `jskelet init` scaffolds `.jsk`.
|
|
107
|
+
|
|
108
|
+
## The EJS engine (legacy)
|
|
109
|
+
|
|
110
|
+
EJS remains supported. The engine is set up once on the first render; the
|
|
111
|
+
component scan touches the file system, so it cannot be done on every request
|
|
112
|
+
and cannot be computed before the config is loaded.
|
|
36
113
|
|
|
37
114
|
Settings:
|
|
38
115
|
|
|
@@ -54,14 +131,15 @@ normal flow because the dev server restarts the process.
|
|
|
54
131
|
1. `jskelet.config.mjs` → if `layout` is given, it is used. The path is
|
|
55
132
|
resolved relative to the **parent directory of the views directory**: if
|
|
56
133
|
`views` is the default, `layout: "views/custom.ejs"` → `<root>/views/custom.ejs`.
|
|
57
|
-
2. If it is not given and `views/layout.
|
|
58
|
-
3.
|
|
134
|
+
2. If it is not given and `views/layout.jsk` exists (compiled), that is used.
|
|
135
|
+
3. Else if `views/layout.ejs` exists, that is used.
|
|
136
|
+
4. If that does not exist either, the framework's own minimal layout is used
|
|
59
137
|
(`node_modules/jskelet/src/templates/layout.ejs`, also reachable through the
|
|
60
138
|
`jskelet/layout` specifier).
|
|
61
139
|
|
|
62
|
-
|
|
140
|
+
These fallbacks exist so that a new project can work with a single route. The
|
|
63
141
|
most practical way to move to your own layout is to copy that file to
|
|
64
|
-
`views/layout.ejs`.
|
|
142
|
+
`views/layout.ejs` or author `views/layout.jsk`.
|
|
65
143
|
|
|
66
144
|
### The framework's default layout
|
|
67
145
|
|
|
@@ -192,14 +270,20 @@ Rules:
|
|
|
192
270
|
|
|
193
271
|
- The scan is recursive; subdirectories are covered too.
|
|
194
272
|
- `default` exports are ignored — only named exports are registered.
|
|
273
|
+
- The compile-time known-component set is read from **named exports in the
|
|
274
|
+
source**, not from the file basename. `sectionHead` in `ui.js` →
|
|
275
|
+
`<SectionHead />` in the template (runtime already adds a PascalCase alias
|
|
276
|
+
for camelCase exports). You do not need a stub re-export named after the
|
|
277
|
+
file.
|
|
195
278
|
- `loader.js` and `index.js` do not count as component files.
|
|
196
279
|
- If `views/components/index.js` exists it is loaded first as a **barrel**,
|
|
197
280
|
with the lowest priority. Its only purpose is to turn `lib/` re-exports into
|
|
198
281
|
template locals; the components' own files come later and silently overwrite
|
|
199
282
|
it.
|
|
200
|
-
- If the same name is defined in two different
|
|
201
|
-
|
|
202
|
-
|
|
283
|
+
- If the same name (or the same PascalCase tag) is defined in two different
|
|
284
|
+
component files, that is an **error, not a warning**: build and server
|
|
285
|
+
startup stop with `Component 'card' is defined twice: …`. Overwriting the
|
|
286
|
+
barrel is the deliberate exception.
|
|
203
287
|
- If the `views/components` directory does not exist the component registry
|
|
204
288
|
stays empty; a project that uses no components works fine too.
|
|
205
289
|
|