jskelet 0.7.4 → 0.7.5

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
@@ -10,6 +10,10 @@ one is listed under a **Breaking** heading.
10
10
 
11
11
  ### Changed
12
12
 
13
+ - Page stylesheets are built as one asset containing global CSS followed by
14
+ page CSS. Routes that declare `styles` load the combined asset instead of
15
+ applying `app.css` and page utilities in separate cascade steps, preventing
16
+ the intermediate layout shifts that occur when those sheets arrive apart.
13
17
  - Redis HTML is stored as a small JSON meta key, a raw brotli `:body` key, and
14
18
  optional `:enc:br` / `:enc:gzip` keys on a second connection. HTML command
15
19
  timeouts use their own circuit breaker and no longer bypass the data cache.
@@ -219,7 +219,7 @@ Controller `async (ctx) => sayfa` biçimindedir ve şu alanları döndürebilir:
219
219
  | `head` | `string` | `""` | `<head>`e olduğu gibi basılacak ham HTML (ör. LCP preload'ı). |
220
220
  | `bodyClass` | `string` | `hooks.layoutContext().bodyClass ?? ""` | `<body class="…">`. |
221
221
  | `entries` | `string[]` | `[]` | Bu sayfada ek olarak yüklenecek client entry adları: `["chart.js"]`. |
222
- | `styles` | `string[]` | `[]` | Bu sayfada ek olarak yüklenecek stylesheet adları: `["home.css"]` → `styles/pages/home.css`. |
222
+ | `styles` | `string[]` | `[]` | Global CSS ile birleştirilip `app.css` yerine yüklenecek sayfa stylesheet adları: `["home.css"]` → `styles/pages/home.css`. |
223
223
 
224
224
  `revalidate` **`route()`'un ikinci argümanıdır**, controller'ın döndürdüğü
225
225
  nesnenin alanı değil.
@@ -182,12 +182,12 @@ Dikkat edilecek noktalar:
182
182
  - **`extraHead` en başta.** Kaynak ipuçlarını (`preconnect`, LCP `preload`)
183
183
  geciktirmek doğrudan LCP'ye yazılır.
184
184
  - **Global `app.css` varsayılan olarak render-blocking** ve gerekçesi
185
- [02-mimari.md](./02-mimari.md)'de. Controller `styles: [...]` ile ek sayfa
186
- sheet'leri de aynı şekilde basılır. `<Stylesheets :blocking="false" />`
187
- (`stylesheets({ blocking: false })`) linkleri `media="print"` + `onload`
188
- ile basar; JS kapalıyken `<noscript>` yedeği uygulanır. Boyama beklenmez,
189
- sheet gelince düzen kayabilir. Build çalışmadıysa `hasAsset` false olur
190
- ve etiket hiç basılmaz.
185
+ [02-mimari.md](./02-mimari.md)'de. Controller `styles: [...]` verirse sayfa
186
+ sheet'i global CSS'i de içeren birleşik varlıktır ve `app.css` yerine basılır.
187
+ `<Stylesheets :blocking="false" />` (`stylesheets({ blocking: false })`)
188
+ linki `media="print"` + `onload` ile basar; JS kapalıyken `<noscript>` yedeği
189
+ uygulanır. Boyama beklenmez, sheet gelince düzen kayabilir. Build çalışmadıysa
190
+ `hasAsset` false olur ve etiket hiç basılmaz.
191
191
  - **`hasAsset` kontrolleri** build eksikken sayfanın 404 veren dosyaları
192
192
  istememesini sağlar (`Stylesheets` / `BodyScripts` içinde).
193
193
  - **Devtools script'i** yalnızca `NODE_ENV=development` iken basılır; prod
@@ -437,7 +437,8 @@ icon({ name: "ArrowRight", weight: "bold", size: 20, class: "text-slate-500" });
437
437
 
438
438
  ### `stylesheets(props)`
439
439
 
440
- Layout `<head>` içindeki global `app.css` ve controller `styles` dizisi.
440
+ Layout `<head>` içindeki global `app.css` veya controller `styles` dizisindeki
441
+ birleşik sayfa sheet'leri.
441
442
  Doğrudan çağırmak yerine yerleşik etiket kullanılır:
442
443
 
443
444
  ```jsk
@@ -451,7 +452,9 @@ stylesheets({ styles: ["home.css"], blocking: false });
451
452
  ```
452
453
 
453
454
  - `styles` manifest anahtarlarıdır (`"home.css"` → `styles/pages/home.css`).
454
- `hasAsset` false olanlar basılmaz.
455
+ Geçerli en az bir sayfa sheet'i varsa `app.css` ayrıca basılmaz; her sayfa
456
+ çıktısı global CSS'i zaten içerir. Hiçbiri manifest'te yoksa `app.css`e
457
+ dönülür.
455
458
  - `blocking` varsayılan `true`: `<link rel="stylesheet" data-jskelet-css="…">`.
456
459
  Yalnızca boolean `false` beklemeyi kapatır. Çıktı `media="print"` ve
457
460
  `onload="this.media='all'"` olur; eşleşmeyen media boyamayı bekletmez.
package/docs/08-build.md CHANGED
@@ -163,12 +163,18 @@ return {
163
163
  Dizin, `paths.styles` dosyasının yanındaki `pages/` klasörüdür (`styles` taşınırsa
164
164
  pages de yanında kalır). Dizin yoksa veya boşsa adım yalnızca global sheet üretir.
165
165
 
166
- Sayfa CSS'i sayfaya özel kurallar içindir. Tailwind utility'leri global sheet'te
167
- kalmalı — dosyada tam `@import "tailwindcss"` utility çıktısını tekrarlar.
168
-
169
- Layout `app.css`ten sonra `styles` dizisindeki her sheet için
170
- `<link data-jskelet-css="…">` basar; `hasAsset` false ise etiket yok.
171
- `blocking: false` aynı linklere `media="print"` ve `onload` ekler.
166
+ Her sayfa çıktısı **global derlenmiş CSS + sayfa derlenmiş CSS** içeren tek bir
167
+ dosyadır. Controller bir sayfa sheet'i bildirdiğinde layout `app.css`i ayrıca
168
+ basmaz; yalnızca birleşik sheet'i yükler. Böylece inline kritik CSS, `app.css`
169
+ ve page sheet içindeki aynı Tailwind utility katmanı üç ayrı anda uygulanmaz.
170
+ Örneğin global `.px-0` ile sayfadaki `.lg:px-4` çatışsa bile tarayıcı yalnızca
171
+ dosyanın son cascade sonucunu boyar ve ara düzen CLS üretmez.
172
+
173
+ Sayfa CSS'i sayfaya özel kurallar içindir. Bir route için tek sayfa dosyası
174
+ tercih edin; `styles` dizisinde birden fazla ad hâlâ desteklenir ama her biri
175
+ global CSS'i taşıdığı için gereksiz tekrar indirir. Sayfa adı manifest'te yoksa
176
+ layout güvenli biçimde `app.css`e döner. `blocking: false` birleşik linke
177
+ `media="print"` ve `onload` ekler.
172
178
 
173
179
  ### `@source` direktifleri zorunludur
174
180
 
@@ -222,7 +222,7 @@ fields:
222
222
  | `head` | `string` | `""` | Raw HTML to be printed into `<head>` as-is (e.g. the LCP preload). |
223
223
  | `bodyClass` | `string` | `hooks.layoutContext().bodyClass ?? ""` | `<body class="…">`. |
224
224
  | `entries` | `string[]` | `[]` | The names of client entries to be loaded additionally on this page: `["chart.js"]`. |
225
- | `styles` | `string[]` | `[]` | Extra stylesheets for this page: `["home.css"]` → `styles/pages/home.css`. |
225
+ | `styles` | `string[]` | `[]` | Page stylesheets combined with global CSS and loaded instead of `app.css`: `["home.css"]` → `styles/pages/home.css`. |
226
226
 
227
227
  `revalidate` is **the second argument of `route()`**, not a field of the object
228
228
  the controller returns.
@@ -181,8 +181,9 @@ Points to watch:
181
181
  - **`extraHead` comes first.** Delaying resource hints (`preconnect`, LCP
182
182
  `preload`) writes straight into LCP.
183
183
  - **`<Stylesheets />` emits global `app.css` render-blocking by default**,
184
- plus controller `styles: [...]`, with the reasoning in
185
- [02-architecture.md](./02-architecture.md).
184
+ with the reasoning in [02-architecture.md](./02-architecture.md). When a
185
+ controller provides `styles: [...]`, a page sheet is a combined asset that
186
+ already contains global CSS and is emitted instead of `app.css`.
186
187
  `<Stylesheets :blocking="false" />` (`stylesheets({ blocking: false })`)
187
188
  emits the links with `media="print"` plus `onload`, and a `<noscript>`
188
189
  fallback when JS is off. Painting is not held, and the layout can shift
@@ -442,7 +443,8 @@ icon({ name: "ArrowRight", weight: "bold", size: 20, class: "text-slate-500" });
442
443
 
443
444
  ### `stylesheets(props)`
444
445
 
445
- Global `app.css` and the controller `styles` list for the layout `<head>`.
446
+ Global `app.css`, or the combined page sheets in the controller `styles` list,
447
+ for the layout `<head>`.
446
448
  The built-in tag is the usual call site:
447
449
 
448
450
  ```jsk
@@ -456,7 +458,9 @@ stylesheets({ styles: ["home.css"], blocking: false });
456
458
  ```
457
459
 
458
460
  - `styles` are manifest keys (`"home.css"` → `styles/pages/home.css`). Names
459
- for which `hasAsset` is false are omitted.
461
+ for which `hasAsset` is false are omitted. If at least one page sheet exists,
462
+ `app.css` is not emitted separately because every page output already
463
+ includes it. If none exists, the helper falls back to `app.css`.
460
464
  - `blocking` defaults to `true`:
461
465
  `<link rel="stylesheet" data-jskelet-css="…">`. Only the boolean `false`
462
466
  turns waiting off. The output is `media="print"` and
@@ -168,12 +168,19 @@ The directory is `pages/` next to the `paths.styles` file (if `styles` moves,
168
168
  pages stays beside it). If the directory is missing or empty, the step only
169
169
  produces the global sheet.
170
170
 
171
- Page CSS is for page-specific rules. Keep Tailwind utilities in the global
172
- sheet — a full `@import "tailwindcss"` in a page file duplicates utility output.
173
-
174
- After `app.css`, the layout emits a `<link data-jskelet-css="…">` for each name
175
- in `styles`; if `hasAsset` is false the tag is omitted. `blocking: false`
176
- adds `media="print"` and `onload` to those same links.
171
+ Each page output is one file containing **compiled global CSS + compiled page
172
+ CSS**. When a controller declares a page sheet, the layout does not emit
173
+ `app.css` separately; it loads only the combined sheet. This prevents the same
174
+ Tailwind utility layer in inline critical CSS, `app.css`, and the page sheet
175
+ from taking effect at three different times. Even if global `.px-0` conflicts
176
+ with page `.lg:px-4`, the browser paints only the file's final cascade result
177
+ instead of an intermediate layout that causes CLS.
178
+
179
+ Page CSS is for page-specific rules. Prefer one page file per route; multiple
180
+ names in `styles` are still supported, but each carries the global CSS and
181
+ therefore downloads redundant bytes. If no requested page name exists in the
182
+ manifest, the layout safely falls back to `app.css`. `blocking: false` adds
183
+ `media="print"` and `onload` to the combined link.
177
184
 
178
185
  ### `@source` directives are mandatory
179
186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "A framework that feels like no framework: Express 5 + build-time .jsk SSR (optional EJS peer), vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Tailwind v4 → global `app.css` + isteğe bağlı sayfa stylesheet'leri.
3
3
  *
4
- * Global sheet her sayfada yüklenir (`paths.styles`). Sayfa sheet'leri
5
- * `styles/pages/*.css` altındadır ve yalnızca controller `styles: ["home.css"]`
6
- * bildiren sayfalarda basılır — island `entries` ile aynı sözleşme.
4
+ * Global sheet `paths.styles` girdisinden üretilir. Sayfa sheet'leri
5
+ * `styles/pages/*.css` altındadır; her çıktı global CSS + sayfa CSS'ini tek
6
+ * dosyada taşır ve yalnızca controller `styles: ["home.css"]` bildiren
7
+ * sayfalarda basılır — island `entries` ile aynı sözleşme.
7
8
  *
8
9
  * Ayrı bir "critical CSS" üretilmez. Ölçümde inline kritik CSS ilk ekranı tam
9
10
  * kapsamadığı için sheet gelince sayfa yeniden akıyordu (bir liste sayfasında
@@ -13,9 +14,11 @@
13
14
  * varsayılan olarak render-blocking; yalnızca ilgili sayfada ek maliyet.
14
15
  * Layout `stylesheets({ blocking: false })` ile bu beklemeyi kapatabilir.
15
16
  *
16
- * Tailwind utility'leri global sheet'te kalmalı. Sayfa CSS'inde tam
17
- * `@import "tailwindcss"` utility çıktısını tekrarlar; sayfaya özel kurallar
18
- * yeter.
17
+ * Birleşik çıktı önemli: inline kritik CSS, global utility'ler ve sayfa
18
+ * utility'leri ayrı ağ yanıtlarında sırayla uygulanırsa ara cascade düzeni
19
+ * boyanıp CLS üretebilir. Tarayıcı tek dosyayı ancak tamamı geldikten sonra
20
+ * uygular; sayfa dosyasındaki son kurallar yine global kuralları ezebilir ama
21
+ * kullanıcı ara durumu görmez.
19
22
  *
20
23
  * Tailwind'in sınıf taraması `globals.css` içindeki `@source` direktiflerine
21
24
  * bağlıdır. Otomatik tespit yalnızca stylesheet'in bulunduğu dizini tarar,
@@ -130,7 +133,8 @@ export async function buildCss(config, { watch = false } = {}) {
130
133
 
131
134
  for (const file of listPageStyles(pagesDir)) {
132
135
  const name = path.basename(file);
133
- const css = applyCdnStaticUrls(await compile(file), config);
136
+ const pageCss = applyCdnStaticUrls(await compile(file), config);
137
+ const css = `${appCss}\n${pageCss}`;
134
138
  const url = writeAsset(name, css);
135
139
  urls[name] = url;
136
140
  bytes += Buffer.byteLength(css);
@@ -320,9 +320,11 @@ export function toKebab(name) {
320
320
  }
321
321
 
322
322
  /**
323
- * Layout `<head>` stylesheet'leri. `hasAsset` false olanları basmaz —
324
- * build yokken 404 link'i istememek için. `.jsk` ifade dilinde `asset()` /
325
- * `hasAsset()` çağrılamadığı için layout bu etiketi kullanır.
323
+ * Layout `<head>` stylesheet'leri. Sayfa sheet'i varsa global CSS'i zaten
324
+ * taşıyan bu birleşik varlık basılır; yoksa `app.css` kullanılır. `hasAsset`
325
+ * false olanları basmaz — build yokken 404 link'i istememek için. `.jsk`
326
+ * ifade dilinde `asset()` / `hasAsset()` çağrılamadığı için layout bu etiketi
327
+ * kullanır.
326
328
  *
327
329
  * `blocking` varsayılan `true`: `<link rel="stylesheet">` boyamayı bekletir.
328
330
  * Ölçümde bu, sheet geç gelince oluşan CLS'ten daha ucuz. `false` yalnızca
@@ -337,11 +339,14 @@ export function stylesheets(props = {}) {
337
339
  const blocking = props.blocking !== false;
338
340
  /** @type {string[]} */
339
341
  const parts = [];
342
+ const available = [...new Set(styles.filter((sheet) => sheet && hasAsset(sheet)))];
340
343
 
341
- pushStylesheet(parts, "app.css", blocking);
342
-
343
- for (const sheet of styles) {
344
- if (!sheet) continue;
344
+ // Sayfa çıktıları app.css'i içerir. İki linki birden basmak utility
345
+ // katmanını iki ağ yanıtında uygulayıp aradaki cascade durumunu boyatır.
346
+ if (available.length === 0) {
347
+ pushStylesheet(parts, "app.css", blocking);
348
+ }
349
+ for (const sheet of available) {
345
350
  pushStylesheet(parts, sheet, blocking);
346
351
  }
347
352
 
@@ -98,9 +98,11 @@ export declare function csrfField(): string;
98
98
  */
99
99
  export declare function toKebab(name: string): string;
100
100
  /**
101
- * Layout `<head>` stylesheet'leri. `hasAsset` false olanları basmaz —
102
- * build yokken 404 link'i istememek için. `.jsk` ifade dilinde `asset()` /
103
- * `hasAsset()` çağrılamadığı için layout bu etiketi kullanır.
101
+ * Layout `<head>` stylesheet'leri. Sayfa sheet'i varsa global CSS'i zaten
102
+ * taşıyan bu birleşik varlık basılır; yoksa `app.css` kullanılır. `hasAsset`
103
+ * false olanları basmaz — build yokken 404 link'i istememek için. `.jsk`
104
+ * ifade dilinde `asset()` / `hasAsset()` çağrılamadığı için layout bu etiketi
105
+ * kullanır.
104
106
  *
105
107
  * `blocking` varsayılan `true`: `<link rel="stylesheet">` boyamayı bekletir.
106
108
  * Ölçümde bu, sheet geç gelince oluşan CLS'ten daha ucuz. `false` yalnızca