jskelet 0.2.3 → 0.2.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.
Files changed (91) hide show
  1. package/AGENTS.md +132 -132
  2. package/CHANGELOG.md +15 -0
  3. package/LICENSE +21 -21
  4. package/bin/jskelet.mjs +103 -103
  5. package/docs/01-baslangic.md +285 -285
  6. package/docs/02-mimari.md +287 -287
  7. package/docs/03-routing.md +480 -480
  8. package/docs/04-render-ve-sablonlar.md +490 -490
  9. package/docs/05-islands.md +482 -482
  10. package/docs/06-cache.md +1209 -1202
  11. package/docs/08-build.md +366 -366
  12. package/docs/09-dev-araclari.md +335 -335
  13. package/docs/10-dagitim.md +329 -329
  14. package/docs/12-panel-ve-oturum.md +384 -384
  15. package/docs/README.md +105 -105
  16. package/docs/en/01-getting-started.md +292 -292
  17. package/docs/en/02-architecture.md +305 -305
  18. package/docs/en/03-routing.md +497 -497
  19. package/docs/en/04-rendering.md +504 -504
  20. package/docs/en/05-islands.md +492 -492
  21. package/docs/en/06-caching.md +1239 -1232
  22. package/docs/en/07-configuration.md +986 -986
  23. package/docs/en/08-build.md +383 -383
  24. package/docs/en/09-dev-tools.md +342 -342
  25. package/docs/en/10-deployment.md +332 -332
  26. package/docs/en/11-migration.md +359 -359
  27. package/docs/en/12-dashboards-and-sessions.md +392 -392
  28. package/docs/en/README.md +112 -112
  29. package/package.json +102 -102
  30. package/src/build/ensure-build.mjs +15 -15
  31. package/src/build/paths.mjs +143 -143
  32. package/src/build/resolve-peer.mjs +36 -36
  33. package/src/build/tasks/client.mjs +268 -268
  34. package/src/build/tasks/css.mjs +124 -124
  35. package/src/build/tasks/fonts.mjs +146 -146
  36. package/src/build/tasks/icons.mjs +224 -224
  37. package/src/build/tasks/images.mjs +244 -244
  38. package/src/build/tasks/precompress.mjs +78 -78
  39. package/src/client/cache-panel/i18n.js +670 -0
  40. package/src/client/cache-panel/login.html +74 -71
  41. package/src/client/cache-panel/panel.css +756 -740
  42. package/src/client/cache-panel/panel.html +308 -307
  43. package/src/client/cache-panel/panel.js +915 -808
  44. package/src/client/devtools/report.html +185 -185
  45. package/src/client/devtools/report.js +725 -725
  46. package/src/client/dom.js +95 -95
  47. package/src/client/form.js +192 -192
  48. package/src/client/index.js +35 -35
  49. package/src/client/registry.js +297 -297
  50. package/src/client/safe-image.js +91 -91
  51. package/src/client/store.js +36 -36
  52. package/src/client/swap.js +188 -188
  53. package/src/config/pattern.js +107 -107
  54. package/src/http/control-flow.js +71 -71
  55. package/src/http/cookies.js +257 -257
  56. package/src/http/request-cache.js +46 -46
  57. package/src/http/request-context.js +162 -162
  58. package/src/index.js +83 -83
  59. package/src/init.mjs +221 -221
  60. package/src/runtime/alias-hooks.mjs +119 -119
  61. package/src/runtime/register.mjs +4 -4
  62. package/src/server/assets.js +147 -147
  63. package/src/server/cache-deps.js +42 -42
  64. package/src/server/cache-panel.js +759 -738
  65. package/src/server/cloudflare.js +607 -595
  66. package/src/server/create-app.js +291 -291
  67. package/src/server/data-cache.js +462 -462
  68. package/src/server/dev/report.js +369 -369
  69. package/src/server/dev/socket.js +170 -170
  70. package/src/server/dev/version-check.mjs +139 -139
  71. package/src/server/html-cache.js +817 -817
  72. package/src/server/metadata.js +102 -102
  73. package/src/server/middleware/compression.js +205 -205
  74. package/src/server/middleware/csrf.js +134 -134
  75. package/src/server/middleware/dev-gate.js +62 -62
  76. package/src/server/middleware/headers.js +37 -37
  77. package/src/server/middleware/redirects.js +32 -32
  78. package/src/server/middleware/static-precompressed.js +100 -100
  79. package/src/server/middleware/upstream-proxy.js +141 -141
  80. package/src/server/prewarm.js +601 -601
  81. package/src/server/redis.js +569 -569
  82. package/src/server/router.js +128 -128
  83. package/src/server/status-page.js +164 -164
  84. package/src/server/upstream-limiter.js +376 -376
  85. package/src/server/upstream-tracking.js +166 -166
  86. package/src/start.mjs +7 -7
  87. package/src/templates/layout.ejs +44 -44
  88. package/src/version.mjs +31 -31
  89. package/src/views/components/loader.js +85 -85
  90. package/src/views/helpers/html.js +102 -102
  91. package/src/views/helpers/tags.js +245 -245
@@ -1,335 +1,335 @@
1
- # 09 — Geliştirme araçları
2
-
3
- Bu belge `jskelet dev`in ne yaptığını ve neden böyle yaptığını anlatır: iki alt
4
- sürecin yönetimi, terminal çıktısının biçimi, izlenen dizinler ve `node --watch`
5
- yerine kendi watcher'ının yazılma sebebi, CSS hot-swap ile tam yenileme ayrımı,
6
- `Alt+D` ile açılan devtools overlay'i, detaylı rapor sayfası ve `DEV_TOKEN` ile
7
- kurulan dev gate. Build adımlarının kendisi [08-build.md](./08-build.md)'de.
8
-
9
- ## `jskelet dev` akışı
10
-
11
- Komut tek terminalde iki uzun ömürlü alt süreç yönetir:
12
-
13
- ```
14
- jskelet dev
15
- ├─ build watch node src/build/build.mjs --watch
16
- └─ sunucu node [--env-file=.env] --import <register.mjs> src/start.mjs
17
- ```
18
-
19
- `NODE_ENV=development` ataması platformdan bağımsız olarak burada yapılır —
20
- `cross-env` gerekmez. Alt süreçlere ayrıca `JSKELET_CHILD=1` (build banner'ını
21
- bastırır) ve TTY varsa `JSKELET_COLOR=1` (borulanmış çıktıda renk zorlar)
22
- geçirilir.
23
-
24
- Açılış sırası: banner → build adımları → sunucu hazır → `Ready` özeti. Özet hem
25
- build hem sunucu hazır olduğunda basılır; aksi hâlde arkadan gelen build
26
- satırlarının arasında kalıyordu.
27
-
28
- Sunucunun hazır olduğu, `startServer` içindeki tek satırdan anlaşılır:
29
-
30
- ```
31
- jskelet → http://localhost:3000 (development)
32
- ```
33
-
34
- Bu satırın biçimi bir sözleşmedir; dev script'i onu ayrıştırıp özet satırını ona
35
- göre basar.
36
-
37
- ### Terminal çıktısının biçimi
38
-
39
- Alt süreçlerin çıktısı olduğu gibi akmaz. İki bölge vardır ve karışmazlar:
40
-
41
- 1. **Başlangıç:** banner, hizalı build satırları, `Ready` özeti.
42
- 2. **Çalışma anı:** zaman damgalı, tek satırlık olaylar (HTTP istekleri, CSS
43
- rebuild, sunucu restart).
44
-
45
- Hata yığınları çerçeveli bir kutuya dönüşür: yığın satırları parça parça geldiği
46
- için kısa bir sessizlikten (60 ms) sonra toplanır, hata adı ve mesajı
47
- ayrıştırılır, ilk üç frame gösterilir ve proje kökü `.` ile kısaltılır. Kendi
48
- framework'ünü geliştirirken hatanın akış içinde kaybolmaması gerçekten fark
49
- yaratan ayrıntı.
50
-
51
- Renk anlam taşır: `✓` yeşil, `✖` kırmızı, `⚠` sarı, `↻` cyan; süre ve yol gri.
52
- Dekoratif renk kullanılmaz. `NO_COLOR` ayarlıysa renk hiç kullanılmaz.
53
-
54
- `Ctrl+C` (SIGINT/SIGTERM) tüm alt süreçleri kapatır. Bir alt süreç sıfır dışı
55
- kodla çıkarsa (beklenen restart hariç) hata basılır ve dev süreci de kapanır.
56
-
57
- ## Watch dizinleri
58
-
59
- Sunucu yeniden başlatma framework'ün kendi watcher'ıyla yönetilir.
60
-
61
- ```js
62
- WATCH_DIRS = [
63
- config.dirs.routes,
64
- config.dirs.views,
65
- <root>/lib,
66
- ...config.watch, // jskelet.config.mjs → watch
67
- ]
68
- ```
69
-
70
- Ayrıca `jskelet.config.mjs` dosyasının kendisi izlenir: config değişince hem
71
- sunucu hem build yeni ayarlarla açılmalı.
72
-
73
- İzlenen uzantılar: `.js`, `.mjs`, `.json`, `.ejs`.
74
-
75
- `views` de izlenir çünkü bileşenlerin çoğu `views/components/**.js` içinde ve bu
76
- modüller sunucuya bir kez import edildiği için, restart olmadan yapılan
77
- değişiklik tarayıcıya hiç yansımıyordu (şablon düzenleyip "hiçbir şey değişmedi"
78
- hissi buradan geliyor).
79
-
80
- `client/` ve `styles/` bu listede **yoktur**: onları esbuild ve CSS watcher'ları
81
- kendi içinde hallediyor ([08-build.md](./08-build.md)).
82
-
83
- Bir dizin izlenemezse uyarı basılır ve o dizinde otomatik restart olmaz; gerisi
84
- çalışır.
85
-
86
- ### Neden `node --watch` kullanılmadı
87
-
88
- `--watch-path` verilse bile Node bu kurulumda proje kökünü izliyordu. Build
89
- çıktısı (`public/assets`, `manifest.json`) ya da dev araçlarının günlüğü
90
- yazıldığında sunucu boşuna yeniden başlıyor, hatta kendini besleyen bir döngü
91
- kuruluyordu: restart → açılış uyarısı → yazma → restart.
92
-
93
- Kendi watcher'ı üç şey yapar:
94
-
95
- 1. **Yalnızca sunucu kaynaklarını izler.**
96
- 2. **Değişiklikleri birleştirir** (250 ms) ve hangi dosyaların değiştiğini
97
- bildirir.
98
- 3. **Sahte olayları eler.** Windows'ta `fs.watch` bir dosya yazıldığında
99
- komşuları için de olay üretebiliyor; `mtime` karşılaştırılmazsa tek kaydetme
100
- iki restart'a dönüşüyordu. Açılışta mevcut zamanlar önden okunur, böylece ilk
101
- sahte olay da elenir.
102
-
103
- Restart satırı değişen dosyayı ya da sayısını gösterir:
104
-
105
- ```
106
- 21:04:12 ↻ server restarting… routes/10-pages.mjs
107
- 21:04:12 server restarted 412ms
108
- ```
109
-
110
- `JSKELET_VERBOSE=1` ayarlıysa birden fazla dosya değiştiğinde tamamı listelenir.
111
-
112
- ## CSS hot-swap ve tam yenileme
113
-
114
- Dev sunucusu `.jskelet/manifest.json` dosyasını izler ve olayları canlı kanal
115
- (`<devBasePath>/ws`) üzerinden tarayıcıya yayınlar. Manifest her build turunda
116
- yeniden yazıldığı için değişiklik tespiti manifest üzerinden yapılır.
117
-
118
- | Değişen | Davranış |
119
- | --- | --- |
120
- | Yalnızca `app.css` | **CSS hot-swap:** stylesheet takas edilir, sayfa yenilenmez. Durum ve kaydırma korunur. |
121
- | `main.js`, sprite, başka bir varlık ya da birden fazla anahtar | **Tam yenileme** |
122
-
123
- Her iki durumda önce HTML önbelleği temizlenir: saklanan HTML eski hash'li varlık
124
- URL'lerini taşıyor olur ve temizlenmezse sayfa silinmiş dosyayı istemeye devam
125
- eder.
126
-
127
- Manifest olayları 120 ms birleştirilir. Watch desteklenmiyorsa live reload devre
128
- dışı kalır ve gerisi çalışır.
129
-
130
- Sunucu yeniden başladığında overlay bunu **boot kimliğinden** anlar: her süreç
131
- kendine özgü bir `boot` değeri yayınlar, overlay değişikliği görüp "restarted"
132
- bilgisini gösterir ve kendi durumunu sıfırlamaz.
133
-
134
- ## Canlı kanal
135
-
136
- Overlay'e giden her şey — istatistikler, live reload ve CSS hot-swap olayları —
137
- tek bir WebSocket üzerinden gelir (`<devBasePath>/ws`). Panel eskiden
138
- istatistikleri iki saniyede bir çekiyordu; açık her sekme, panel kapalıyken bile
139
- sunucuya sürekli istek atıyordu. Artık sunucu değişiklik oldukça iter: bir istek
140
- ya da hata kaydedildiğinde (120 ms birleştirilerek), ısıtma sürerken saniyede
141
- ve zamana bağlı alanlar (uptime, bellek, ısıtma sayacı) tazelensin diye iki
142
- saniyede bir. Kalp atışı bilinçli olarak ısıtmadan bağımsız: kanalın temposu bir
143
- arka plan işine göre değişirse panel de o işin ritmine bağlanmış olur. Bağlı
144
- panel yoksa hiçbir şey hesaplanmaz.
145
-
146
- El sıkışma HTTP `upgrade` olayında geçtiği ve o olay middleware zincirine hiç
147
- uğramadığı için kanal `listen` sonrası doğrudan sunucuya bağlanır
148
- (`attachDevSocket`). Sunucu tarafı `ws` gibi bir bağımlılık kullanmaz: yalnızca
149
- sunucu→istemci metin çerçevesi yazmak ve istemcinin ping/close çerçevelerini
150
- yanıtlamak gerekiyor.
151
-
152
- Soket kurulup sonra düşerse — yani sunucu yeniden başlıyorsa — yarım saniyede
153
- bir yeniden bağlanır ve gösterge bu sırada "bağlantı yok" der. Hiç açılamazsa
154
- aralığı kademeli açarak dört kez denenir; sayfa, sunucunun yeniden başlama
155
- penceresinde açılmış olabilir ve tek bir başarısızlık kanalın çalışmadığı
156
- anlamına gelmez. Denemeler de tutmazsa (araya giren bir proxy WebSocket'i
157
- geçirmiyor olabilir) overlay eski yola düşer: `/events` SSE akışı + `/stats`
158
- yoklaması.
159
-
160
- ## Devtools overlay
161
-
162
- Sağ altta yüzen bir baloncuk; `Alt+D` ile açılır, `Esc` ya da karartma alanına
163
- tıklamak kapatır. Yalnızca `NODE_ENV=development` iken layout tarafından
164
- basılır:
165
-
166
- ```ejs
167
- <% if (devtools) { %>
168
- <script type="module" src="<%= devBasePath %>/overlay.js"></script>
169
- <% } %>
170
- ```
171
-
172
- Overlay dosyası **build'e dâhil değildir.** Sunucu onu framework paketinden ham
173
- olarak servis eder; bu yüzden içinde bundler yoktur, tek dosya olarak çalışır ve
174
- prod çıktısına hiçbir şey eklemez. Tüm arayüz shadow DOM içinde durur, sayfanın
175
- CSS'i ile karışmaz.
176
-
177
- Gösterdikleri:
178
-
179
- - **Hatalar:** tarayıcı tarafındaki JS hataları, kaynak yükleme hataları
180
- (`img`/`script`/`link`), ve sunucudaki `console.error` / `console.warn`
181
- çıktıları. Sunucu tarafında `console` sarılır, böylece uyarılar terminalde
182
- kaybolmaz.
183
- - **İstekler:** her HTML isteğinin metodu, yolu, durumu, süresi ve
184
- `X-JSkelet-Cache` değeri. Aynı satırlar terminale de basılır.
185
- - **Web Vitals:** TTFB, FCP, LCP, CLS, INP, DCL, load, uzun task sayısı ve
186
- bloke süresi.
187
- - **Prewarm:** ısıtma turunun ilerlemesi; panelden elle tetiklenebilir, tek tek
188
- yollar tekrar denenebilir.
189
- - **Süreç:** pid, Node sürümü, uptime, RSS ve heap kullanımı.
190
- - **Sürüm:** kurulu JSkelet sürümü ve npm'deki `latest` ile karşılaştırması.
191
- Yeni bir sürüm varsa **Server** sekmesinde `update` rozeti ve yükseltme
192
- komutunu kopyalayan bir satır çıkar. Yoklama açılıştan 1,5 saniye sonra bir
193
- kez yapılır, sonucu 6 saat boyunca `os.tmpdir()` içinde saklanır ve ağ yoksa
194
- sessizce atlanır. `JSKELET_VERSION_CHECK=0` ile tamamen kapatılır.
195
-
196
- Isıtma istekleri (`user-agent: jskelet-prewarm`) hem terminalden hem istek
197
- listesinden filtrelenir: yüzlerce istek görünümü doldurmasın. İlerleme baloncuğun
198
- yanındaki rozette görünür.
199
-
200
- ### Durum neden `os.tmpdir()`'e yazılıyor
201
-
202
- İstek ve hata kayıtları süreç belleğinde durursa her yeniden başlatmada geçmiş
203
- silinir ve overlay boşalır. Bu yüzden kayıtlar restart'lar arasında bir dosyada
204
- taşınır.
205
-
206
- Dosya **proje ağacına yazılmaz**: her yazma watcher'ı tetikleyip sunucuyu
207
- yeniden başlatıyordu ve bu kendini besleyen bir döngü kuruyordu (restart →
208
- açılış uyarısı → yazma → restart). Bunun yerine dosya
209
- `os.tmpdir()/jskelet-devtools-<proje kökünün hash'i>.json` yoluna yazılır;
210
- hash sayesinde aynı makinede birden fazla JSKelet projesi birbirinin kaydını
211
- ezmez.
212
-
213
- Yazma her istekte değil, 300 ms sessizlikten sonra yapılır ve başarısızlığı dev
214
- akışını durdurmaz. En fazla 50 istek ve 50 hata tutulur.
215
-
216
- Panelin açık/kapalı durumu, aktif sekmesi ve tarayıcı hata günlüğü ise sekme
217
- belleğinde (`sessionStorage`) saklanır, böylece yenileme sonrası panel aynı
218
- sekmeyle geri gelir.
219
-
220
- ## Rapor sayfası
221
-
222
- Baloncuk anlık durumu gösterir; rapor sayfası sitenin tamamına bakan bir görünüm
223
- üretir. Adres:
224
-
225
- ```
226
- http://localhost:3000/__jskelet/dev/report
227
- ```
228
-
229
- (`brand.devBasePath` değiştirilmişse ona göre.)
230
-
231
- İçeriği:
232
-
233
- - **Sayfalar:** gezilen her sayfanın Web Vitals ölçümleri, kaynak sayısı ve
234
- toplam bayt (tür kırılımıyla), island durumu (kaç tanesi hazır, adları),
235
- tarayıcıdaki API çağrıları ve SSR çıktısının boyutu/süresi/cache durumu. Hiç
236
- gezilmemiş ama ısıtılmış sayfalar da listelenir: SSR tarafı bilinir, istemci
237
- ölçümleri boş kalır.
238
- - **Sunucu API çağrıları:** SSR sırasında yapılan dış `fetch` çağrıları — URL,
239
- host, metot, durum, süre, bayt. `globalThis.fetch` yalnızca development'ta
240
- sarılır; üretim yolu dokunulmaz kalır. Kendi sunucumuza yapılan istekler
241
- (ısıtma, sağlık kontrolü) API sayılmaz.
242
- - **Build çıktısı:** manifest'teki her varlığın ham/gzip/brotli boyutu, ve
243
- esbuild metafile'ından chunk analizi — her çıktının boyutu, hangi kaynaklardan
244
- oluştuğu, hangi chunk'ları import ettiği. Kaynaklar okunur gruplara indirgenir
245
- (paket adı ya da üst klasör), böylece "bu chunk'ın 40 kB'ı hangi kütüphaneden"
246
- sorusu yanıtlanabilir.
247
- - **HTML önbelleği:** girdi sayısı ve döküm (anahtar, bayt, durum, bayat mı, kaç
248
- saniye sonra dolacak, hangi encoding'ler saklanmış).
249
- - **Prewarm:** son turun tam sonucu.
250
- - **İstek ve hata günlükleri.**
251
-
252
- Ölçümler tarayıcı sekmesinde değil sunucuda durur; sıfırlama da sunucudan
253
- yapılır. Boyut hesapları dosya değişmedikçe tekrarlanmaz.
254
-
255
- Rapor katmanı yalnızca development'ta yüklenir, üretim çıktısına hiç girmez.
256
-
257
- ## Dev uçları
258
-
259
- `brand.devBasePath` (varsayılan `/__jskelet/dev`) altında:
260
-
261
- | Yol | Metot | İşi |
262
- | --- | --- | --- |
263
- | `/overlay.js` | GET | Overlay script'i |
264
- | `/logo.png` | GET | Overlay logosu |
265
- | `/ws` | GET (upgrade) | Canlı kanal: istatistikler, live reload ve CSS hot-swap olayları |
266
- | `/events` | GET | SSE: yalnızca WebSocket kurulamazsa kullanılan yedek olay akışı |
267
- | `/stats` | GET | Anlık istatistikler; aynı yedek yolun veri ucu |
268
- | `/report` | GET | Rapor sayfası (HTML) |
269
- | `/report.js` | GET | Rapor sayfasının script'i |
270
- | `/report/data` | GET | Raporun tek veri kaynağı (JSON) |
271
- | `/vitals` | POST | Overlay'in gönderdiği ölçüm paketi |
272
- | `/report/clear` | POST | Sayfa ölçümlerini ve sunucu API kayıtlarını sıfırlar |
273
- | `/prewarm` | POST | Isıtmayı elle tetikler. Gövdede `paths` varsa yalnızca o yollar; ısıtma zaten çalışıyorsa 409. |
274
- | `/clear` | POST | İstek ve hata günlüklerini sıfırlar |
275
-
276
- Bu uçların tamamı `mountDevtools()` tarafından yalnızca
277
- `NODE_ENV=development` iken bağlanır; prod sürecine dinamik import sayesinde
278
- hiçbir şey yüklenmez.
279
-
280
- ## Dev gate — `DEV_TOKEN`
281
-
282
- Yayına açılmamış bir ortamı gizlemek için: `DEV_TOKEN` ayarlıyken token
283
- taşımayan **her** isteğe 404 döner.
284
-
285
- ```bash
286
- DEV_TOKEN=uzun-rastgele-bir-dize npm start
287
- ```
288
-
289
- Erişim:
290
-
291
- ```
292
- https://staging.ornek.com/?dev_token=uzun-rastgele-bir-dize
293
- ```
294
-
295
- Davranış:
296
-
297
- - **403 değil 404.** 403 ortamın var olduğunu doğrular; 404 hiç yokmuş gibi
298
- davranır.
299
- - Token bir kez query parametresiyle gelirse çereze yazılır (`Path=/`,
300
- `SameSite=Lax`, 14 gün), böylece link paylaşımı yeterli olur. Çerez ve
301
- parametre adı `brand.devTokenCookie` (varsayılan `dev_token`).
302
- - `devGateBypass` listesindeki **tam** yollar her koşulda açıktır. Varsayılan:
303
- `/api/healthcheck`, `/robots.txt`, `/sitemap.xml`, `/site.webmanifest`,
304
- `/favicon.ico`. Sağlık kontrolünüz farklı bir yolda ise bu listeye eklemeyi
305
- unutmayın, aksi hâlde orkestratör 404 görür.
306
- - `DEV_TOKEN` yoksa middleware tamamen devre dışıdır ve üretimde hiçbir maliyeti
307
- olmaz.
308
- - Isıtma kendi sunucusuna istek attığı için token'ı çerez olarak taşır; yoksa tüm
309
- sayfalar 404 alır ve önbellek hiç dolmaz ([06-cache.md](./06-cache.md)).
310
-
311
- Gate middleware zincirinde `headers`tan sonra, `redirects`ten **önce** durur:
312
- yayına açılmamış bir ortam yönlendirme kurallarını bile dışarıya sızdırmamalı.
313
-
314
- ## Development ile production farkları
315
-
316
- | Konu | Development | Production |
317
- | --- | --- | --- |
318
- | EJS şablon cache'i | Kapalı | Açık |
319
- | Manifest okuma | Her istekte | Bir kez |
320
- | Görsel manifest'i | Her çağrıda | Bir kez |
321
- | Bozuk route modülü | Uyarı + atla | Fırlat |
322
- | Devtools ve rapor | Mount edilir | Hiç yüklenmez |
323
- | `globalThis.fetch` | Sarılır (ölçüm) | Dokunulmaz |
324
- | Prewarm paralelliği | 1 | 4 |
325
- | Prewarm hız freni | saniyede 4 istek | Sınırsız |
326
- | Prewarm gecikmesi | 3000 ms | 500 ms |
327
- | Eksik ikon uyarısı | Verilir | Verilmez |
328
- | Precompress | Watch'ta çalışmaz | Çalışır |
329
- | Görsel optimizasyonu | Watch'ta çalışmaz | Çalışır |
330
-
331
- ## Sırada ne var
332
-
333
- - Build adımlarının ayrıntısı: [08-build.md](./08-build.md)
334
- - Prod'a alma: [10-dagitim.md](./10-dagitim.md)
335
- - Önbelleği okumak ve temizlemek: [06-cache.md](./06-cache.md)
1
+ # 09 — Geliştirme araçları
2
+
3
+ Bu belge `jskelet dev`in ne yaptığını ve neden böyle yaptığını anlatır: iki alt
4
+ sürecin yönetimi, terminal çıktısının biçimi, izlenen dizinler ve `node --watch`
5
+ yerine kendi watcher'ının yazılma sebebi, CSS hot-swap ile tam yenileme ayrımı,
6
+ `Alt+D` ile açılan devtools overlay'i, detaylı rapor sayfası ve `DEV_TOKEN` ile
7
+ kurulan dev gate. Build adımlarının kendisi [08-build.md](./08-build.md)'de.
8
+
9
+ ## `jskelet dev` akışı
10
+
11
+ Komut tek terminalde iki uzun ömürlü alt süreç yönetir:
12
+
13
+ ```
14
+ jskelet dev
15
+ ├─ build watch node src/build/build.mjs --watch
16
+ └─ sunucu node [--env-file=.env] --import <register.mjs> src/start.mjs
17
+ ```
18
+
19
+ `NODE_ENV=development` ataması platformdan bağımsız olarak burada yapılır —
20
+ `cross-env` gerekmez. Alt süreçlere ayrıca `JSKELET_CHILD=1` (build banner'ını
21
+ bastırır) ve TTY varsa `JSKELET_COLOR=1` (borulanmış çıktıda renk zorlar)
22
+ geçirilir.
23
+
24
+ Açılış sırası: banner → build adımları → sunucu hazır → `Ready` özeti. Özet hem
25
+ build hem sunucu hazır olduğunda basılır; aksi hâlde arkadan gelen build
26
+ satırlarının arasında kalıyordu.
27
+
28
+ Sunucunun hazır olduğu, `startServer` içindeki tek satırdan anlaşılır:
29
+
30
+ ```
31
+ jskelet → http://localhost:3000 (development)
32
+ ```
33
+
34
+ Bu satırın biçimi bir sözleşmedir; dev script'i onu ayrıştırıp özet satırını ona
35
+ göre basar.
36
+
37
+ ### Terminal çıktısının biçimi
38
+
39
+ Alt süreçlerin çıktısı olduğu gibi akmaz. İki bölge vardır ve karışmazlar:
40
+
41
+ 1. **Başlangıç:** banner, hizalı build satırları, `Ready` özeti.
42
+ 2. **Çalışma anı:** zaman damgalı, tek satırlık olaylar (HTTP istekleri, CSS
43
+ rebuild, sunucu restart).
44
+
45
+ Hata yığınları çerçeveli bir kutuya dönüşür: yığın satırları parça parça geldiği
46
+ için kısa bir sessizlikten (60 ms) sonra toplanır, hata adı ve mesajı
47
+ ayrıştırılır, ilk üç frame gösterilir ve proje kökü `.` ile kısaltılır. Kendi
48
+ framework'ünü geliştirirken hatanın akış içinde kaybolmaması gerçekten fark
49
+ yaratan ayrıntı.
50
+
51
+ Renk anlam taşır: `✓` yeşil, `✖` kırmızı, `⚠` sarı, `↻` cyan; süre ve yol gri.
52
+ Dekoratif renk kullanılmaz. `NO_COLOR` ayarlıysa renk hiç kullanılmaz.
53
+
54
+ `Ctrl+C` (SIGINT/SIGTERM) tüm alt süreçleri kapatır. Bir alt süreç sıfır dışı
55
+ kodla çıkarsa (beklenen restart hariç) hata basılır ve dev süreci de kapanır.
56
+
57
+ ## Watch dizinleri
58
+
59
+ Sunucu yeniden başlatma framework'ün kendi watcher'ıyla yönetilir.
60
+
61
+ ```js
62
+ WATCH_DIRS = [
63
+ config.dirs.routes,
64
+ config.dirs.views,
65
+ <root>/lib,
66
+ ...config.watch, // jskelet.config.mjs → watch
67
+ ]
68
+ ```
69
+
70
+ Ayrıca `jskelet.config.mjs` dosyasının kendisi izlenir: config değişince hem
71
+ sunucu hem build yeni ayarlarla açılmalı.
72
+
73
+ İzlenen uzantılar: `.js`, `.mjs`, `.json`, `.ejs`.
74
+
75
+ `views` de izlenir çünkü bileşenlerin çoğu `views/components/**.js` içinde ve bu
76
+ modüller sunucuya bir kez import edildiği için, restart olmadan yapılan
77
+ değişiklik tarayıcıya hiç yansımıyordu (şablon düzenleyip "hiçbir şey değişmedi"
78
+ hissi buradan geliyor).
79
+
80
+ `client/` ve `styles/` bu listede **yoktur**: onları esbuild ve CSS watcher'ları
81
+ kendi içinde hallediyor ([08-build.md](./08-build.md)).
82
+
83
+ Bir dizin izlenemezse uyarı basılır ve o dizinde otomatik restart olmaz; gerisi
84
+ çalışır.
85
+
86
+ ### Neden `node --watch` kullanılmadı
87
+
88
+ `--watch-path` verilse bile Node bu kurulumda proje kökünü izliyordu. Build
89
+ çıktısı (`public/assets`, `manifest.json`) ya da dev araçlarının günlüğü
90
+ yazıldığında sunucu boşuna yeniden başlıyor, hatta kendini besleyen bir döngü
91
+ kuruluyordu: restart → açılış uyarısı → yazma → restart.
92
+
93
+ Kendi watcher'ı üç şey yapar:
94
+
95
+ 1. **Yalnızca sunucu kaynaklarını izler.**
96
+ 2. **Değişiklikleri birleştirir** (250 ms) ve hangi dosyaların değiştiğini
97
+ bildirir.
98
+ 3. **Sahte olayları eler.** Windows'ta `fs.watch` bir dosya yazıldığında
99
+ komşuları için de olay üretebiliyor; `mtime` karşılaştırılmazsa tek kaydetme
100
+ iki restart'a dönüşüyordu. Açılışta mevcut zamanlar önden okunur, böylece ilk
101
+ sahte olay da elenir.
102
+
103
+ Restart satırı değişen dosyayı ya da sayısını gösterir:
104
+
105
+ ```
106
+ 21:04:12 ↻ server restarting… routes/10-pages.mjs
107
+ 21:04:12 server restarted 412ms
108
+ ```
109
+
110
+ `JSKELET_VERBOSE=1` ayarlıysa birden fazla dosya değiştiğinde tamamı listelenir.
111
+
112
+ ## CSS hot-swap ve tam yenileme
113
+
114
+ Dev sunucusu `.jskelet/manifest.json` dosyasını izler ve olayları canlı kanal
115
+ (`<devBasePath>/ws`) üzerinden tarayıcıya yayınlar. Manifest her build turunda
116
+ yeniden yazıldığı için değişiklik tespiti manifest üzerinden yapılır.
117
+
118
+ | Değişen | Davranış |
119
+ | --- | --- |
120
+ | Yalnızca `app.css` | **CSS hot-swap:** stylesheet takas edilir, sayfa yenilenmez. Durum ve kaydırma korunur. |
121
+ | `main.js`, sprite, başka bir varlık ya da birden fazla anahtar | **Tam yenileme** |
122
+
123
+ Her iki durumda önce HTML önbelleği temizlenir: saklanan HTML eski hash'li varlık
124
+ URL'lerini taşıyor olur ve temizlenmezse sayfa silinmiş dosyayı istemeye devam
125
+ eder.
126
+
127
+ Manifest olayları 120 ms birleştirilir. Watch desteklenmiyorsa live reload devre
128
+ dışı kalır ve gerisi çalışır.
129
+
130
+ Sunucu yeniden başladığında overlay bunu **boot kimliğinden** anlar: her süreç
131
+ kendine özgü bir `boot` değeri yayınlar, overlay değişikliği görüp "restarted"
132
+ bilgisini gösterir ve kendi durumunu sıfırlamaz.
133
+
134
+ ## Canlı kanal
135
+
136
+ Overlay'e giden her şey — istatistikler, live reload ve CSS hot-swap olayları —
137
+ tek bir WebSocket üzerinden gelir (`<devBasePath>/ws`). Panel eskiden
138
+ istatistikleri iki saniyede bir çekiyordu; açık her sekme, panel kapalıyken bile
139
+ sunucuya sürekli istek atıyordu. Artık sunucu değişiklik oldukça iter: bir istek
140
+ ya da hata kaydedildiğinde (120 ms birleştirilerek), ısıtma sürerken saniyede
141
+ ve zamana bağlı alanlar (uptime, bellek, ısıtma sayacı) tazelensin diye iki
142
+ saniyede bir. Kalp atışı bilinçli olarak ısıtmadan bağımsız: kanalın temposu bir
143
+ arka plan işine göre değişirse panel de o işin ritmine bağlanmış olur. Bağlı
144
+ panel yoksa hiçbir şey hesaplanmaz.
145
+
146
+ El sıkışma HTTP `upgrade` olayında geçtiği ve o olay middleware zincirine hiç
147
+ uğramadığı için kanal `listen` sonrası doğrudan sunucuya bağlanır
148
+ (`attachDevSocket`). Sunucu tarafı `ws` gibi bir bağımlılık kullanmaz: yalnızca
149
+ sunucu→istemci metin çerçevesi yazmak ve istemcinin ping/close çerçevelerini
150
+ yanıtlamak gerekiyor.
151
+
152
+ Soket kurulup sonra düşerse — yani sunucu yeniden başlıyorsa — yarım saniyede
153
+ bir yeniden bağlanır ve gösterge bu sırada "bağlantı yok" der. Hiç açılamazsa
154
+ aralığı kademeli açarak dört kez denenir; sayfa, sunucunun yeniden başlama
155
+ penceresinde açılmış olabilir ve tek bir başarısızlık kanalın çalışmadığı
156
+ anlamına gelmez. Denemeler de tutmazsa (araya giren bir proxy WebSocket'i
157
+ geçirmiyor olabilir) overlay eski yola düşer: `/events` SSE akışı + `/stats`
158
+ yoklaması.
159
+
160
+ ## Devtools overlay
161
+
162
+ Sağ altta yüzen bir baloncuk; `Alt+D` ile açılır, `Esc` ya da karartma alanına
163
+ tıklamak kapatır. Yalnızca `NODE_ENV=development` iken layout tarafından
164
+ basılır:
165
+
166
+ ```ejs
167
+ <% if (devtools) { %>
168
+ <script type="module" src="<%= devBasePath %>/overlay.js"></script>
169
+ <% } %>
170
+ ```
171
+
172
+ Overlay dosyası **build'e dâhil değildir.** Sunucu onu framework paketinden ham
173
+ olarak servis eder; bu yüzden içinde bundler yoktur, tek dosya olarak çalışır ve
174
+ prod çıktısına hiçbir şey eklemez. Tüm arayüz shadow DOM içinde durur, sayfanın
175
+ CSS'i ile karışmaz.
176
+
177
+ Gösterdikleri:
178
+
179
+ - **Hatalar:** tarayıcı tarafındaki JS hataları, kaynak yükleme hataları
180
+ (`img`/`script`/`link`), ve sunucudaki `console.error` / `console.warn`
181
+ çıktıları. Sunucu tarafında `console` sarılır, böylece uyarılar terminalde
182
+ kaybolmaz.
183
+ - **İstekler:** her HTML isteğinin metodu, yolu, durumu, süresi ve
184
+ `X-JSkelet-Cache` değeri. Aynı satırlar terminale de basılır.
185
+ - **Web Vitals:** TTFB, FCP, LCP, CLS, INP, DCL, load, uzun task sayısı ve
186
+ bloke süresi.
187
+ - **Prewarm:** ısıtma turunun ilerlemesi; panelden elle tetiklenebilir, tek tek
188
+ yollar tekrar denenebilir.
189
+ - **Süreç:** pid, Node sürümü, uptime, RSS ve heap kullanımı.
190
+ - **Sürüm:** kurulu JSkelet sürümü ve npm'deki `latest` ile karşılaştırması.
191
+ Yeni bir sürüm varsa **Server** sekmesinde `update` rozeti ve yükseltme
192
+ komutunu kopyalayan bir satır çıkar. Yoklama açılıştan 1,5 saniye sonra bir
193
+ kez yapılır, sonucu 6 saat boyunca `os.tmpdir()` içinde saklanır ve ağ yoksa
194
+ sessizce atlanır. `JSKELET_VERSION_CHECK=0` ile tamamen kapatılır.
195
+
196
+ Isıtma istekleri (`user-agent: jskelet-prewarm`) hem terminalden hem istek
197
+ listesinden filtrelenir: yüzlerce istek görünümü doldurmasın. İlerleme baloncuğun
198
+ yanındaki rozette görünür.
199
+
200
+ ### Durum neden `os.tmpdir()`'e yazılıyor
201
+
202
+ İstek ve hata kayıtları süreç belleğinde durursa her yeniden başlatmada geçmiş
203
+ silinir ve overlay boşalır. Bu yüzden kayıtlar restart'lar arasında bir dosyada
204
+ taşınır.
205
+
206
+ Dosya **proje ağacına yazılmaz**: her yazma watcher'ı tetikleyip sunucuyu
207
+ yeniden başlatıyordu ve bu kendini besleyen bir döngü kuruyordu (restart →
208
+ açılış uyarısı → yazma → restart). Bunun yerine dosya
209
+ `os.tmpdir()/jskelet-devtools-<proje kökünün hash'i>.json` yoluna yazılır;
210
+ hash sayesinde aynı makinede birden fazla JSKelet projesi birbirinin kaydını
211
+ ezmez.
212
+
213
+ Yazma her istekte değil, 300 ms sessizlikten sonra yapılır ve başarısızlığı dev
214
+ akışını durdurmaz. En fazla 50 istek ve 50 hata tutulur.
215
+
216
+ Panelin açık/kapalı durumu, aktif sekmesi ve tarayıcı hata günlüğü ise sekme
217
+ belleğinde (`sessionStorage`) saklanır, böylece yenileme sonrası panel aynı
218
+ sekmeyle geri gelir.
219
+
220
+ ## Rapor sayfası
221
+
222
+ Baloncuk anlık durumu gösterir; rapor sayfası sitenin tamamına bakan bir görünüm
223
+ üretir. Adres:
224
+
225
+ ```
226
+ http://localhost:3000/__jskelet/dev/report
227
+ ```
228
+
229
+ (`brand.devBasePath` değiştirilmişse ona göre.)
230
+
231
+ İçeriği:
232
+
233
+ - **Sayfalar:** gezilen her sayfanın Web Vitals ölçümleri, kaynak sayısı ve
234
+ toplam bayt (tür kırılımıyla), island durumu (kaç tanesi hazır, adları),
235
+ tarayıcıdaki API çağrıları ve SSR çıktısının boyutu/süresi/cache durumu. Hiç
236
+ gezilmemiş ama ısıtılmış sayfalar da listelenir: SSR tarafı bilinir, istemci
237
+ ölçümleri boş kalır.
238
+ - **Sunucu API çağrıları:** SSR sırasında yapılan dış `fetch` çağrıları — URL,
239
+ host, metot, durum, süre, bayt. `globalThis.fetch` yalnızca development'ta
240
+ sarılır; üretim yolu dokunulmaz kalır. Kendi sunucumuza yapılan istekler
241
+ (ısıtma, sağlık kontrolü) API sayılmaz.
242
+ - **Build çıktısı:** manifest'teki her varlığın ham/gzip/brotli boyutu, ve
243
+ esbuild metafile'ından chunk analizi — her çıktının boyutu, hangi kaynaklardan
244
+ oluştuğu, hangi chunk'ları import ettiği. Kaynaklar okunur gruplara indirgenir
245
+ (paket adı ya da üst klasör), böylece "bu chunk'ın 40 kB'ı hangi kütüphaneden"
246
+ sorusu yanıtlanabilir.
247
+ - **HTML önbelleği:** girdi sayısı ve döküm (anahtar, bayt, durum, bayat mı, kaç
248
+ saniye sonra dolacak, hangi encoding'ler saklanmış).
249
+ - **Prewarm:** son turun tam sonucu.
250
+ - **İstek ve hata günlükleri.**
251
+
252
+ Ölçümler tarayıcı sekmesinde değil sunucuda durur; sıfırlama da sunucudan
253
+ yapılır. Boyut hesapları dosya değişmedikçe tekrarlanmaz.
254
+
255
+ Rapor katmanı yalnızca development'ta yüklenir, üretim çıktısına hiç girmez.
256
+
257
+ ## Dev uçları
258
+
259
+ `brand.devBasePath` (varsayılan `/__jskelet/dev`) altında:
260
+
261
+ | Yol | Metot | İşi |
262
+ | --- | --- | --- |
263
+ | `/overlay.js` | GET | Overlay script'i |
264
+ | `/logo.png` | GET | Overlay logosu |
265
+ | `/ws` | GET (upgrade) | Canlı kanal: istatistikler, live reload ve CSS hot-swap olayları |
266
+ | `/events` | GET | SSE: yalnızca WebSocket kurulamazsa kullanılan yedek olay akışı |
267
+ | `/stats` | GET | Anlık istatistikler; aynı yedek yolun veri ucu |
268
+ | `/report` | GET | Rapor sayfası (HTML) |
269
+ | `/report.js` | GET | Rapor sayfasının script'i |
270
+ | `/report/data` | GET | Raporun tek veri kaynağı (JSON) |
271
+ | `/vitals` | POST | Overlay'in gönderdiği ölçüm paketi |
272
+ | `/report/clear` | POST | Sayfa ölçümlerini ve sunucu API kayıtlarını sıfırlar |
273
+ | `/prewarm` | POST | Isıtmayı elle tetikler. Gövdede `paths` varsa yalnızca o yollar; ısıtma zaten çalışıyorsa 409. |
274
+ | `/clear` | POST | İstek ve hata günlüklerini sıfırlar |
275
+
276
+ Bu uçların tamamı `mountDevtools()` tarafından yalnızca
277
+ `NODE_ENV=development` iken bağlanır; prod sürecine dinamik import sayesinde
278
+ hiçbir şey yüklenmez.
279
+
280
+ ## Dev gate — `DEV_TOKEN`
281
+
282
+ Yayına açılmamış bir ortamı gizlemek için: `DEV_TOKEN` ayarlıyken token
283
+ taşımayan **her** isteğe 404 döner.
284
+
285
+ ```bash
286
+ DEV_TOKEN=uzun-rastgele-bir-dize npm start
287
+ ```
288
+
289
+ Erişim:
290
+
291
+ ```
292
+ https://staging.ornek.com/?dev_token=uzun-rastgele-bir-dize
293
+ ```
294
+
295
+ Davranış:
296
+
297
+ - **403 değil 404.** 403 ortamın var olduğunu doğrular; 404 hiç yokmuş gibi
298
+ davranır.
299
+ - Token bir kez query parametresiyle gelirse çereze yazılır (`Path=/`,
300
+ `SameSite=Lax`, 14 gün), böylece link paylaşımı yeterli olur. Çerez ve
301
+ parametre adı `brand.devTokenCookie` (varsayılan `dev_token`).
302
+ - `devGateBypass` listesindeki **tam** yollar her koşulda açıktır. Varsayılan:
303
+ `/api/healthcheck`, `/robots.txt`, `/sitemap.xml`, `/site.webmanifest`,
304
+ `/favicon.ico`. Sağlık kontrolünüz farklı bir yolda ise bu listeye eklemeyi
305
+ unutmayın, aksi hâlde orkestratör 404 görür.
306
+ - `DEV_TOKEN` yoksa middleware tamamen devre dışıdır ve üretimde hiçbir maliyeti
307
+ olmaz.
308
+ - Isıtma kendi sunucusuna istek attığı için token'ı çerez olarak taşır; yoksa tüm
309
+ sayfalar 404 alır ve önbellek hiç dolmaz ([06-cache.md](./06-cache.md)).
310
+
311
+ Gate middleware zincirinde `headers`tan sonra, `redirects`ten **önce** durur:
312
+ yayına açılmamış bir ortam yönlendirme kurallarını bile dışarıya sızdırmamalı.
313
+
314
+ ## Development ile production farkları
315
+
316
+ | Konu | Development | Production |
317
+ | --- | --- | --- |
318
+ | EJS şablon cache'i | Kapalı | Açık |
319
+ | Manifest okuma | Her istekte | Bir kez |
320
+ | Görsel manifest'i | Her çağrıda | Bir kez |
321
+ | Bozuk route modülü | Uyarı + atla | Fırlat |
322
+ | Devtools ve rapor | Mount edilir | Hiç yüklenmez |
323
+ | `globalThis.fetch` | Sarılır (ölçüm) | Dokunulmaz |
324
+ | Prewarm paralelliği | 1 | 4 |
325
+ | Prewarm hız freni | saniyede 4 istek | Sınırsız |
326
+ | Prewarm gecikmesi | 3000 ms | 500 ms |
327
+ | Eksik ikon uyarısı | Verilir | Verilmez |
328
+ | Precompress | Watch'ta çalışmaz | Çalışır |
329
+ | Görsel optimizasyonu | Watch'ta çalışmaz | Çalışır |
330
+
331
+ ## Sırada ne var
332
+
333
+ - Build adımlarının ayrıntısı: [08-build.md](./08-build.md)
334
+ - Prod'a alma: [10-dagitim.md](./10-dagitim.md)
335
+ - Önbelleği okumak ve temizlemek: [06-cache.md](./06-cache.md)