jskelet 0.7.2 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -73,6 +73,10 @@ one is listed under a **Breaking** heading.
73
73
 
74
74
  ### Added
75
75
 
76
+ - `stylesheets({ blocking: false })` and `<Stylesheets :blocking="false" />`
77
+ emit each stylesheet with `media="print"` and an `onload` that switches it
78
+ to `all`, plus a `<noscript>` fallback, so the links do not block rendering.
79
+ The default stays render-blocking.
76
80
  - Dev tools **Server** tab can clear the HTML cache for the open page, every
77
81
  HTML entry, or the data cache. Live stats include HTML/data entry counts;
78
82
  new endpoints under `devBasePath`: `POST /cache/clear-html`,
package/docs/02-mimari.md CHANGED
@@ -155,9 +155,17 @@ kapsamadığı için sheet gelince sayfa yeniden akıyordu (bir liste sayfasınd
155
155
  ziyarette zaten `immutable` önbellekten geliyor.
156
156
 
157
157
  Sayfaya özel kurallar için `styles/pages/*.css` + controller `styles: [...]`
158
- eklenebilir ([08-build.md](./08-build.md)); bunlar da render-blocking basılır
159
- ama yalnızca ilgili sayfada. Tailwind utility'leri global sheet'te kalır —
160
- sayfa sheet'inde tam `@import "tailwindcss"` utility çıktısını tekrarlar.
158
+ eklenebilir ([08-build.md](./08-build.md)); bunlar da varsayılan olarak
159
+ render-blocking basılır ama yalnızca ilgili sayfada. Tailwind utility'leri
160
+ global sheet'te kalır — sayfa sheet'inde tam `@import "tailwindcss"` utility
161
+ çıktısını tekrarlar.
162
+
163
+ Erken boyama isteyen bir layout bu varsayılandan çıkabilir:
164
+ `stylesheets({ blocking: false })` veya şablonda
165
+ `<Stylesheets :blocking="false" />`. Link `media="print"` ile iner, `onload`
166
+ onu `all` yapar; JS kapalıyken `<noscript>` içindeki normal link uygulanır.
167
+ Bu, yukarıdaki CLS takasının tersidir: ilk boyama erken gelir, sheet gelince
168
+ düzen kayabilir.
161
169
 
162
170
  Aynı mantık ikonlarda da var: her ikon için ayrı istek yerine, build zamanında
163
171
  yalnızca kaynakta kullanılan sembollerden bir SVG sprite üretilir. Tüm Phosphor
@@ -113,7 +113,7 @@ script ve JSON-LD döngüleri için yerleşikler:
113
113
 
114
114
  | Etiket | Props | Çıktı |
115
115
  | --- | --- | --- |
116
- | `Stylesheets` | `styles` | `app.css` + sayfa sheet’leri (`data-jskelet-css`) |
116
+ | `Stylesheets` | `styles`, `blocking` | `app.css` + sayfa sheet’leri (`data-jskelet-css`). `blocking` varsayılan `true` |
117
117
  | `BodyScripts` | `entries`, `devtools`, `devBasePath` | `main.js`, entry’ler, isteğe bağlı overlay |
118
118
  | `JsonLd` | `items` (`structuredData`) | `application/ld+json` script’leri |
119
119
 
@@ -181,9 +181,12 @@ Dikkat edilecek noktalar:
181
181
 
182
182
  - **`extraHead` en başta.** Kaynak ipuçlarını (`preconnect`, LCP `preload`)
183
183
  geciktirmek doğrudan LCP'ye yazılır.
184
- - **Global `app.css` render-blocking** ve gerekçesi
184
+ - **Global `app.css` varsayılan olarak render-blocking** ve gerekçesi
185
185
  [02-mimari.md](./02-mimari.md)'de. Controller `styles: [...]` ile ek sayfa
186
- sheet'leri de aynı şekilde basılır. Build çalışmadıysa `hasAsset` false olur
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
187
190
  ve etiket hiç basılmaz.
188
191
  - **`hasAsset` kontrolleri** build eksikken sayfanın 404 veren dosyaları
189
192
  istememesini sağlar (`Stylesheets` / `BodyScripts` içinde).
@@ -432,6 +435,32 @@ icon({ name: "ArrowRight", weight: "bold", size: 20, class: "text-slate-500" });
432
435
  çalışma anında hesaplanan bir çağrı eksik sembole işaret ederse ekranda
433
436
  sessizce boşluk kalır ([08-build.md](./08-build.md)).
434
437
 
438
+ ### `stylesheets(props)`
439
+
440
+ Layout `<head>` içindeki global `app.css` ve controller `styles` dizisi.
441
+ Doğrudan çağırmak yerine yerleşik etiket kullanılır:
442
+
443
+ ```jsk
444
+ <Stylesheets :styles="styles" />
445
+ <Stylesheets :styles="styles" :blocking="false" />
446
+ ```
447
+
448
+ ```js
449
+ stylesheets({ styles: ["home.css"] });
450
+ stylesheets({ styles: ["home.css"], blocking: false });
451
+ ```
452
+
453
+ - `styles` manifest anahtarlarıdır (`"home.css"` → `styles/pages/home.css`).
454
+ `hasAsset` false olanlar basılmaz.
455
+ - `blocking` varsayılan `true`: `<link rel="stylesheet" data-jskelet-css="…">`.
456
+ Yalnızca boolean `false` beklemeyi kapatır. Çıktı `media="print"` ve
457
+ `onload="this.media='all'"` olur; eşleşmeyen media boyamayı bekletmez.
458
+ `<noscript>` içindeki ikinci link, JS kapalıyken stillerin print'te
459
+ kalmamasını sağlar. Inline `onload` çalışmayan bir CSP'de (`script-src`
460
+ içinde `'unsafe-inline'` veya `script-src-attr` yok) sheet `print`'te kalır.
461
+ - `data-jskelet-css` dev hot-swap'ın doğru linki seçmesi içindir; her iki
462
+ modda da yazılır.
463
+
435
464
  ### `preloadImage(props)`
436
465
 
437
466
  ```js
package/docs/08-build.md CHANGED
@@ -141,8 +141,10 @@ minifikasyon → `writeAsset("app.css", …)`.
141
141
  şekilde yavaşlatıyor.
142
142
  - **lightningcss opsiyoneldir:** yoksa Tailwind'in kendi çıktısı kullanılır,
143
143
  yalnızca birkaç kB daha büyük olur.
144
- - Global çıktı `app.css`'tir ve layout onu her sayfada render-blocking olarak
145
- yükler. Ayrı bir "critical CSS" üretilmemesinin ölçüm gerekçesi
144
+ - Global çıktı `app.css`'tir ve layout onu her sayfada varsayılan olarak
145
+ render-blocking yükler. `<Stylesheets :blocking="false" />` bu beklemeyi
146
+ kapatır ([04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)). Ayrı bir
147
+ "critical CSS" üretilmemesinin ölçüm gerekçesi
146
148
  [02-mimari.md](./02-mimari.md)'de.
147
149
 
148
150
  ### Sayfa stylesheet'leri (`styles/pages/`)
@@ -166,6 +168,7 @@ kalmalı — dosyada tam `@import "tailwindcss"` utility çıktısını tekrarla
166
168
 
167
169
  Layout `app.css`ten sonra `styles` dizisindeki her sheet için
168
170
  `<link data-jskelet-css="…">` basar; `hasAsset` false ise etiket yok.
171
+ `blocking: false` aynı linklere `media="print"` ve `onload` ekler.
169
172
 
170
173
  ### `@source` direktifleri zorunludur
171
174
 
@@ -160,9 +160,16 @@ both faster and free of CLS; on the second visit it already comes from the
160
160
 
161
161
  Page-specific rules can use `styles/pages/*.css` plus controller
162
162
  `styles: [...]` ([08-build.md](./08-build.md)); those are also render-blocking
163
- but only on the pages that ask for them. Keep Tailwind utilities in the global
164
- sheet — a full `@import "tailwindcss"` in a page sheet duplicates utility
165
- output.
163
+ by default, but only on the pages that ask for them. Keep Tailwind utilities
164
+ in the global sheet — a full `@import "tailwindcss"` in a page sheet
165
+ duplicates utility output.
166
+
167
+ A layout that wants an earlier first paint can leave this default:
168
+ `stylesheets({ blocking: false })`, or `<Stylesheets :blocking="false" />`
169
+ in the template. The link downloads with `media="print"`, `onload` switches
170
+ it to `all`, and a plain link inside `<noscript>` applies when JS is off.
171
+ That is the reverse of the CLS tradeoff above: first paint arrives sooner,
172
+ and the layout can shift when the sheet arrives.
166
173
 
167
174
  The same logic applies to icons: instead of a separate request per icon, an SVG
168
175
  sprite is produced at build time from only the symbols actually used in the
@@ -180,10 +180,14 @@ Points to watch:
180
180
 
181
181
  - **`extraHead` comes first.** Delaying resource hints (`preconnect`, LCP
182
182
  `preload`) writes straight into LCP.
183
- - **`<Stylesheets />` emits global `app.css` (render-blocking)** plus controller
184
- `styles: [...]`, with the reasoning in
185
- [02-architecture.md](./02-architecture.md). If the build has not run,
186
- `hasAsset` is false inside the tag and nothing is emitted.
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).
186
+ `<Stylesheets :blocking="false" />` (`stylesheets({ blocking: false })`)
187
+ emits the links with `media="print"` plus `onload`, and a `<noscript>`
188
+ fallback when JS is off. Painting is not held, and the layout can shift
189
+ when the sheet arrives. If the build has not run, `hasAsset` is false
190
+ inside the tag and nothing is emitted.
187
191
  - **`<BodyScripts />` emits `main.js`, page `entries`, and the
188
192
  development-only overlay.** The overlay script exists only when
189
193
  `NODE_ENV=development`; it is absent from production output.
@@ -436,6 +440,34 @@ icon({ name: "ArrowRight", weight: "bold", size: 20, class: "text-slate-500" });
436
440
  points at a missing symbol, the screen is silently left blank
437
441
  ([08-build.md](./08-build.md)).
438
442
 
443
+ ### `stylesheets(props)`
444
+
445
+ Global `app.css` and the controller `styles` list for the layout `<head>`.
446
+ The built-in tag is the usual call site:
447
+
448
+ ```jsk
449
+ <Stylesheets :styles="styles" />
450
+ <Stylesheets :styles="styles" :blocking="false" />
451
+ ```
452
+
453
+ ```js
454
+ stylesheets({ styles: ["home.css"] });
455
+ stylesheets({ styles: ["home.css"], blocking: false });
456
+ ```
457
+
458
+ - `styles` are manifest keys (`"home.css"` → `styles/pages/home.css`). Names
459
+ for which `hasAsset` is false are omitted.
460
+ - `blocking` defaults to `true`:
461
+ `<link rel="stylesheet" data-jskelet-css="…">`. Only the boolean `false`
462
+ turns waiting off. The output is `media="print"` and
463
+ `onload="this.media='all'"`; a non-matching media does not hold painting.
464
+ A second link inside `<noscript>` keeps styles from staying on `print`
465
+ when JS is off. Under a CSP that blocks inline handlers (no
466
+ `'unsafe-inline'` in `script-src`, and no `script-src-attr`) the sheet
467
+ stays on `print`.
468
+ - `data-jskelet-css` is how dev hot-swap picks the right link; it is written
469
+ in both modes.
470
+
439
471
  ### `preloadImage(props)`
440
472
 
441
473
  ```js
@@ -145,9 +145,11 @@ The pipeline: PostCSS + `@tailwindcss/postcss` → minification with lightningcs
145
145
  noticeably.
146
146
  - **lightningcss is optional:** without it, Tailwind's own output is used, and it
147
147
  is only a few kB bigger.
148
- - The global output is `app.css` and the layout loads it render-blocking on every
149
- page. The measurement-based reasoning for not producing a separate "critical
150
- CSS" is in [02-architecture.md](./02-architecture.md).
148
+ - The global output is `app.css` and the layout loads it render-blocking on
149
+ every page by default. `<Stylesheets :blocking="false" />` turns that wait
150
+ off ([04-rendering.md](./04-rendering.md)). The measurement-based reasoning
151
+ for not producing a separate "critical CSS" is in
152
+ [02-architecture.md](./02-architecture.md).
151
153
 
152
154
  ### Page stylesheets (`styles/pages/`)
153
155
 
@@ -170,7 +172,8 @@ Page CSS is for page-specific rules. Keep Tailwind utilities in the global
170
172
  sheet — a full `@import "tailwindcss"` in a page file duplicates utility output.
171
173
 
172
174
  After `app.css`, the layout emits a `<link data-jskelet-css="…">` for each name
173
- in `styles`; if `hasAsset` is false the tag is omitted.
175
+ in `styles`; if `hasAsset` is false the tag is omitted. `blocking: false`
176
+ adds `media="print"` and `onload` to those same links.
174
177
 
175
178
  ### `@source` directives are mandatory
176
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jskelet",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
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",
@@ -10,7 +10,8 @@
10
10
  * CLS 0.307) ve aynı ~27 KB her HTML yanıtında tekrar ediyordu. Sıkıştırılmış
11
11
  * global sheet'i render-blocking bırakmak hem daha hızlı hem CLS'siz; ikinci
12
12
  * ziyarette zaten immutable önbellekten geliyor. Sayfa sheet'leri de
13
- * render-blocking; yalnızca ilgili sayfada ek maliyet.
13
+ * varsayılan olarak render-blocking; yalnızca ilgili sayfada ek maliyet.
14
+ * Layout `stylesheets({ blocking: false })` ile bu beklemeyi kapatabilir.
14
15
  *
15
16
  * Tailwind utility'leri global sheet'te kalmalı. Sayfa CSS'inde tam
16
17
  * `@import "tailwindcss"` utility çıktısını tekrarlar; sayfaya özel kurallar
@@ -324,28 +324,57 @@ export function toKebab(name) {
324
324
  * build yokken 404 link'i istememek için. `.jsk` ifade dilinde `asset()` /
325
325
  * `hasAsset()` çağrılamadığı için layout bu etiketi kullanır.
326
326
  *
327
- * @param {{ styles?: string[] }} [props]
327
+ * `blocking` varsayılan `true`: `<link rel="stylesheet">` boyamayı bekletir.
328
+ * Ölçümde bu, sheet geç gelince oluşan CLS'ten daha ucuz. `false` yalnızca
329
+ * açık boolean'dır (`:blocking="false"`); atlamak veya `true` aynı çıktıyı
330
+ * verir.
331
+ *
332
+ * @param {{ styles?: string[], blocking?: boolean }} [props]
328
333
  * @returns {string}
329
334
  */
330
335
  export function stylesheets(props = {}) {
331
336
  const styles = Array.isArray(props.styles) ? props.styles : [];
337
+ const blocking = props.blocking !== false;
332
338
  /** @type {string[]} */
333
339
  const parts = [];
334
340
 
335
- if (hasAsset("app.css")) {
336
- parts.push(
337
- `<link rel="stylesheet" href="${esc(asset("app.css"))}" data-jskelet-css="app.css">`,
338
- );
339
- }
341
+ pushStylesheet(parts, "app.css", blocking);
340
342
 
341
343
  for (const sheet of styles) {
342
- if (!sheet || !hasAsset(sheet)) continue;
344
+ if (!sheet) continue;
345
+ pushStylesheet(parts, sheet, blocking);
346
+ }
347
+
348
+ return parts.join("\n ");
349
+ }
350
+
351
+ /**
352
+ * Tek bir manifest anahtarı için stylesheet linki.
353
+ *
354
+ * `blocking: false` iken `media="print"`: eşleşmeyen media tarayıcıda
355
+ * render-blocking sayılmaz, indirme boyamayı bekletmez. `onload` sheet
356
+ * inince `media`'yı `all` yapar. JS kapalıyken `onload` çalışmaz ve link
357
+ * print'te kalır; `<noscript>` içindeki normal link o yolu kapatır.
358
+ *
359
+ * @param {string[]} parts
360
+ * @param {string} name Manifest anahtarı (`app.css`, `home.css`, …)
361
+ * @param {boolean} blocking
362
+ */
363
+ function pushStylesheet(parts, name, blocking) {
364
+ if (!hasAsset(name)) return;
365
+
366
+ const href = esc(asset(name));
367
+ const id = esc(name);
368
+ if (blocking) {
343
369
  parts.push(
344
- `<link rel="stylesheet" href="${esc(asset(sheet))}" data-jskelet-css="${esc(sheet)}">`,
370
+ `<link rel="stylesheet" href="${href}" data-jskelet-css="${id}">`,
345
371
  );
372
+ return;
346
373
  }
347
374
 
348
- return parts.join("\n ");
375
+ parts.push(
376
+ `<link rel="stylesheet" href="${href}" media="print" onload="this.media='all'" data-jskelet-css="${id}">\n <noscript><link rel="stylesheet" href="${href}" data-jskelet-css="${id}"></noscript>`,
377
+ );
349
378
  }
350
379
 
351
380
  /**
@@ -102,11 +102,17 @@ export declare function toKebab(name: string): string;
102
102
  * build yokken 404 link'i istememek için. `.jsk` ifade dilinde `asset()` /
103
103
  * `hasAsset()` çağrılamadığı için layout bu etiketi kullanır.
104
104
  *
105
- * @param {{ styles?: string[] }} [props]
105
+ * `blocking` varsayılan `true`: `<link rel="stylesheet">` boyamayı bekletir.
106
+ * Ölçümde bu, sheet geç gelince oluşan CLS'ten daha ucuz. `false` yalnızca
107
+ * açık boolean'dır (`:blocking="false"`); atlamak veya `true` aynı çıktıyı
108
+ * verir.
109
+ *
110
+ * @param {{ styles?: string[], blocking?: boolean }} [props]
106
111
  * @returns {string}
107
112
  */
108
113
  export declare function stylesheets(props?: {
109
114
  styles?: string[];
115
+ blocking?: boolean;
110
116
  }): string;
111
117
  /**
112
118
  * Layout gövde sonu script'leri: `main.js`, controller `entries`, isteğe