jskelet 0.6.3 → 0.6.4
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/AGENTS.md +136 -136
- package/CHANGELOG.md +628 -620
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -310
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +667 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1467 -1443
- package/docs/07-yapilandirma.md +1208 -1197
- package/docs/08-build.md +429 -429
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +348 -338
- package/docs/12-panel-ve-oturum.md +479 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -329
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +675 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1476 -1453
- package/docs/en/07-configuration.md +1229 -1219
- package/docs/en/08-build.md +447 -447
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +351 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +489 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +541 -534
- package/src/config/index.js +1500 -1469
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +232 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -70
- package/src/server/cache-control.js +45 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -553
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -233
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1196
- package/src/server/image-optimizer.js +500 -500
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -193
- package/src/server/logs/pipeline.js +165 -165
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -121
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +369 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1082
- package/src/server/redis.js +588 -588
- package/src/server/render.js +910 -910
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +6 -0
- package/types/config/index.d.ts +6 -0
- package/types/server/cache-control.d.ts +28 -0
- package/types/server/og-image.d.ts +5 -0
package/docs/08-build.md
CHANGED
|
@@ -1,429 +1,429 @@
|
|
|
1
|
-
# 08 — Build
|
|
2
|
-
|
|
3
|
-
Bu belge `jskelet build`in yaptığı her işi ve sırasını anlatır: font kopyalama,
|
|
4
|
-
ikon sprite üretimi, Tailwind CSS derlemesi, esbuild ile island bundle'ı, görsel
|
|
5
|
-
optimizasyonu, manifest yazımı ve önceden sıkıştırma. Ayrıca hash'li varlıkların
|
|
6
|
-
`asset()`/`hasAsset()` ile şablonlara nasıl ulaştığı, Tailwind'in `@source`
|
|
7
|
-
direktiflerinin neden zorunlu olduğu ve opsiyonel peer bağımlılıklarının
|
|
8
|
-
davranışı burada. Çıktının çalışma anında nasıl servis edildiği
|
|
9
|
-
[02-mimari.md](./02-mimari.md)'de, build'i tetikleyen watch akışı
|
|
10
|
-
[09-dev-araclari.md](./09-dev-araclari.md)'de.
|
|
11
|
-
|
|
12
|
-
## Hat ve sırası
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
0. Templates .jsk → .jskelet/templates/*.mjs (her zaman; dosya yoksa no-op)
|
|
16
|
-
1. Fonts config.fonts varsa
|
|
17
|
-
2. Icon sprite config.icons !== false ise
|
|
18
|
-
3. CSS styles giriş dosyası varsa
|
|
19
|
-
4. Client JS client/entries/ varsa
|
|
20
|
-
5. Images config.images !== false, watch değil ve sharp kurulu ise
|
|
21
|
-
6. Manifest .jskelet/manifest.json
|
|
22
|
-
7. Precompress watch değilse
|
|
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
|
-
|
|
29
|
-
Sıra rastgele değil:
|
|
30
|
-
|
|
31
|
-
- **CSS ikon sprite'ından sonra gelir.** Sprite bir varlıktır ve sınıf üretmez,
|
|
32
|
-
ama manifest anahtarı verir.
|
|
33
|
-
- **Precompress en sonda:** sıkıştırılacak her şey üretilmiş olmalı.
|
|
34
|
-
- **Görseller watch turunda hiç çalışmaz:** `sharp` ile yeniden kodlama pahalı.
|
|
35
|
-
|
|
36
|
-
Görevler yalnızca ilgili yapılandırma varsa çalışır. Font tanımlamayan bir proje
|
|
37
|
-
font adımını hiç görmez; bu, "framework her projeye kendi varsayımlarını
|
|
38
|
-
dayatmaz" ilkesinin build tarafındaki karşılığı.
|
|
39
|
-
|
|
40
|
-
Terminal çıktısı hizalı adım satırları ve sonunda bir `output` bloğu verir: her
|
|
41
|
-
varlığın ham ve brotli boyutu, büyükten küçüğe.
|
|
42
|
-
|
|
43
|
-
## Manifest ve hash'li varlıklar
|
|
44
|
-
|
|
45
|
-
Build çıktısı `public/assets/` altına **içerik hash'li** adlarla yazılır ve
|
|
46
|
-
mantıksal ad → public URL eşlemesi `.jskelet/manifest.json` dosyasına konur:
|
|
47
|
-
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"app.css": "/assets/app.4f2a1b9c07.css",
|
|
51
|
-
"sprite.svg": "/assets/sprite.dc973997bd.svg",
|
|
52
|
-
"main.js": "/assets/js/main.9E1AB2C3.js",
|
|
53
|
-
"inter-400.woff2": "/fonts/inter-400.woff2"
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Hash sha256'nın ilk 10 hex karakteridir: çakışma için fazlasıyla yeterli ve
|
|
58
|
-
dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
59
|
-
`Cache-Control: public, max-age=31536000, immutable` yazılabilir.
|
|
60
|
-
|
|
61
|
-
### `asset(name)` ve `hasAsset(name)`
|
|
62
|
-
|
|
63
|
-
Şablonlara otomatik geçer; sunucu kodunda `import { asset, hasAsset } from "jskelet"`.
|
|
64
|
-
|
|
65
|
-
```ejs
|
|
66
|
-
<% if (hasAsset('app.css')) { %>
|
|
67
|
-
<link rel="stylesheet" href="<%= asset('app.css') %>" data-jskelet-css="app.css">
|
|
68
|
-
<% } %>
|
|
69
|
-
<% styles.forEach(function (sheet) { %>
|
|
70
|
-
<% if (hasAsset(sheet)) { %>
|
|
71
|
-
<link rel="stylesheet" href="<%= asset(sheet) %>" data-jskelet-css="<%= sheet %>">
|
|
72
|
-
<% } %>
|
|
73
|
-
<% }); %>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
- `asset(name)` manifest'te varsa hash'li URL'i, yoksa `/assets/<name>` döner.
|
|
77
|
-
- `hasAsset(name)` manifest'te olup olmadığını söyler.
|
|
78
|
-
|
|
79
|
-
Build çalışmadıysa uygulama yine ayağa kalkar: `hasAsset()` false olur, layout
|
|
80
|
-
stylesheet ve script etiketlerini hiç basmaz. `jskelet build` unutulduğunda hata
|
|
81
|
-
yerine stilsiz ama çalışan bir sayfa görürsünüz. Manifest hiç yoksa bir kez uyarı
|
|
82
|
-
basılır: ``[assets] no manifest — run `jskelet build`.``
|
|
83
|
-
|
|
84
|
-
Manifest **dev'de her istekte** yeniden okunur (watch build hash'leri
|
|
85
|
-
değiştirir), prod'da bir kez.
|
|
86
|
-
|
|
87
|
-
### Watch modunda manifest tutarlılığı
|
|
88
|
-
|
|
89
|
-
Watch turunda yeniden derlenen varlık yeni bir hash'e yazılıp eskisi silinir. Bu
|
|
90
|
-
yüzden manifest de güncellenmek zorunda (`patchManifest`): aksi hâlde HTML
|
|
91
|
-
silinmiş dosyayı isteyip 404 alır ve sayfa dev oturumunun kalanında stilsiz ya
|
|
92
|
-
da JS'siz kalır. CSS ve client görevlerinin ikisi de her turda kendi anahtarını
|
|
93
|
-
yamalar; diğer anahtarlar korunur. CSS tarafı watch'ta `syncCssManifest` ile
|
|
94
|
-
tüm `.css` anahtarlarını günceller (silinen sayfa sheet'leri de düşer).
|
|
95
|
-
|
|
96
|
-
## CSS — Tailwind v4
|
|
97
|
-
|
|
98
|
-
Giriş dosyası `paths.styles` (varsayılan `styles/globals.css`). Dosya yoksa adım
|
|
99
|
-
uyarıyla atlanır.
|
|
100
|
-
|
|
101
|
-
Boru hattı: PostCSS + `@tailwindcss/postcss` → (varsa) lightningcss ile
|
|
102
|
-
minifikasyon → `writeAsset("app.css", …)`.
|
|
103
|
-
|
|
104
|
-
- **PostCSS boru hattı bir kez kurulur:** Tailwind'in kendi önbelleği plugin
|
|
105
|
-
örneğinde yaşıyor; her derlemede yeniden oluşturmak watch turlarını belirgin
|
|
106
|
-
şekilde yavaşlatıyor.
|
|
107
|
-
- **lightningcss opsiyoneldir:** yoksa Tailwind'in kendi çıktısı kullanılır,
|
|
108
|
-
yalnızca birkaç kB daha büyük olur.
|
|
109
|
-
- Global çıktı `app.css`'tir ve layout onu her sayfada render-blocking olarak
|
|
110
|
-
yükler. Ayrı bir "critical CSS" üretilmemesinin ölçüm gerekçesi
|
|
111
|
-
[02-mimari.md](./02-mimari.md)'de.
|
|
112
|
-
|
|
113
|
-
### Sayfa stylesheet'leri (`styles/pages/`)
|
|
114
|
-
|
|
115
|
-
Island `entries` ile aynı sözleşme. `styles/pages/*.css` altındaki her dosya
|
|
116
|
-
ayrı bir hash'li varlıktır (`home.css` → `/assets/home.<hash>.css`). Controller
|
|
117
|
-
yalnızca istediği sayfada yükler:
|
|
118
|
-
|
|
119
|
-
```js
|
|
120
|
-
return {
|
|
121
|
-
view: "pages/home",
|
|
122
|
-
styles: ["home.css"],
|
|
123
|
-
};
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Dizin, `paths.styles` dosyasının yanındaki `pages/` klasörüdür (`styles` taşınırsa
|
|
127
|
-
pages de yanında kalır). Dizin yoksa veya boşsa adım yalnızca global sheet üretir.
|
|
128
|
-
|
|
129
|
-
Sayfa CSS'i sayfaya özel kurallar içindir. Tailwind utility'leri global sheet'te
|
|
130
|
-
kalmalı — dosyada tam `@import "tailwindcss"` utility çıktısını tekrarlar.
|
|
131
|
-
|
|
132
|
-
Layout `app.css`ten sonra `styles` dizisindeki her sheet için
|
|
133
|
-
`<link data-jskelet-css="…">` basar; `hasAsset` false ise etiket yok.
|
|
134
|
-
|
|
135
|
-
### `@source` direktifleri zorunludur
|
|
136
|
-
|
|
137
|
-
Tailwind v4'ün sınıf taraması `globals.css` içindeki `@source` direktiflerine
|
|
138
|
-
bağlıdır. Otomatik tespit yalnızca stylesheet'in bulunduğu dizini tarar, bu
|
|
139
|
-
yüzden şablonlarda geçen varyantlar (`data-[active=false]:…` gibi) **sessizce
|
|
140
|
-
düşer**.
|
|
141
|
-
|
|
142
|
-
```css
|
|
143
|
-
@import "tailwindcss" source(none);
|
|
144
|
-
|
|
145
|
-
@source "../views";
|
|
146
|
-
@source "../client";
|
|
147
|
-
@source "../routes";
|
|
148
|
-
|
|
149
|
-
.wrapper {
|
|
150
|
-
max-width: 48rem;
|
|
151
|
-
margin-inline: auto;
|
|
152
|
-
padding-inline: 1rem;
|
|
153
|
-
padding-block: 2rem;
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
`source(none)` otomatik tespiti kapatır ve taramayı tamamen açık hâle getirir.
|
|
158
|
-
**Yeni bir üst dizin eklediğinizde `@source` satırını da ekleyin** — sınıfların
|
|
159
|
-
"bazen çalışmaması"nın en yaygın sebebi budur.
|
|
160
|
-
|
|
161
|
-
### CSS watch kapsamı
|
|
162
|
-
|
|
163
|
-
Watch modunda üç hedef izlenir: stylesheet'in bulunduğu dizin, `views` ve
|
|
164
|
-
`client`. Şablon ve island dosyaları da izlenir çünkü Tailwind sınıfları oradan
|
|
165
|
-
geliyor; yalnızca `styles/` izlemek yeni bir utility yazıldığında rebuild
|
|
166
|
-
etmezdi. Değişiklikler 120 ms birleştirilir.
|
|
167
|
-
|
|
168
|
-
## Client JS — esbuild
|
|
169
|
-
|
|
170
|
-
`client/entries/*.{js,ts,mts}` içindeki her kaynak dosya bir entry'dir (`.tsx`
|
|
171
|
-
yok). Manifest anahtarı her zaman `*.js` olur (`main.ts` → `main.js`). Aynı stem
|
|
172
|
-
için birden fazla uzantı build hatasıdır. Dizin yoksa ya da boşsa adım atlanır.
|
|
173
|
-
|
|
174
|
-
esbuild ayarları:
|
|
175
|
-
|
|
176
|
-
| Ayar | Değer | Sebebi |
|
|
177
|
-
| --- | --- | --- |
|
|
178
|
-
| `bundle`, `splitting` | `true` | Ortak modüller paylaşılan chunk'a çıkar |
|
|
179
|
-
| `format` | `esm` | `type="module"` script'ler |
|
|
180
|
-
| `target` | `chrome111`, `edge111`, `firefox111`, `safari16.4` | ESM + dinamik import + `IntersectionObserver` island modelinin alt sınırı; daha eskisine transpile etmek çıktıyı büyütüp hiçbir ziyaretçi kazandırmıyor |
|
|
181
|
-
| `minify` | `true` | — |
|
|
182
|
-
| `sourcemap` | yalnızca `NODE_ENV=development` | Prod'da `.map` dosyaları `public/assets` altında yayınlanmaz |
|
|
183
|
-
| `entryNames` | `[name].[hash]` | `immutable` cache |
|
|
184
|
-
| `chunkNames` | `chunks/[name].[hash]` | — |
|
|
185
|
-
| `legalComments` | `none` | — |
|
|
186
|
-
|
|
187
|
-
Çıktı `public/assets/js/` altına düşer ve her turda önce temizlenir.
|
|
188
|
-
`browserslist` okunmaz; hedef listesi kod içinde sabittir.
|
|
189
|
-
|
|
190
|
-
### `@/` alias'ı
|
|
191
|
-
|
|
192
|
-
esbuild tarafında `@/` proje köküne çözülür ve uzantı tamamlama yapılır
|
|
193
|
-
(`.js`, `.mjs`, `.ts`, `.mts`, `.json`, `/index.js`, `/index.ts`). Node
|
|
194
|
-
`alias-hooks.mjs` sunucuda yalnızca `.js` / `.mjs` / `.json` çözer; paylaşılan
|
|
195
|
-
`@/lib` dosyaları bu yüzden `.js` kalmalıdır. Client-only `.ts` import'ları
|
|
196
|
-
esbuild hattında çalışır.
|
|
197
|
-
|
|
198
|
-
### `clientEnv` gömülmesi
|
|
199
|
-
|
|
200
|
-
Tarayıcıda `process` yoktur; sunucuyla paylaşılan modüller yine de `process.env`
|
|
201
|
-
okur. `config.clientEnv` ile bildirilen anahtarlar ve `NODE_ENV` build zamanında
|
|
202
|
-
tek nesne olarak define edilir, yani listede olmayan bir anahtar okunduğunda
|
|
203
|
-
çökme yerine `undefined` döner. İsimleri secret benzeri olan anahtarlar
|
|
204
|
-
(`SECRET`, `API_KEY`, …) build'i düşürür; `PUBLIC` / `PUBLISHABLE` içerenler
|
|
205
|
-
muaf. Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
|
|
206
|
-
|
|
207
|
-
### Manifest anahtarları
|
|
208
|
-
|
|
209
|
-
Yalnızca **gerçek entry'ler** manifest'e girer: dinamik import'lar da
|
|
210
|
-
`entryPoint` taşır ve filtrelenmezse her island ayrı bir manifest anahtarı
|
|
211
|
-
olurdu. Anahtar dosya adının kendisidir (`main.js`, `chart.js`), değer hash'li
|
|
212
|
-
URL.
|
|
213
|
-
|
|
214
|
-
Bu yüzden controller `entries: ["chart.js"]` yazarken hash'i bilmek zorunda
|
|
215
|
-
değildir ([05-islands.md](./05-islands.md)).
|
|
216
|
-
|
|
217
|
-
### `metafile.json`
|
|
218
|
-
|
|
219
|
-
esbuild metafile'ı `.jskelet/metafile.json` dosyasına yazılır; dev panelindeki
|
|
220
|
-
chunk analizi giriş/çıkış kırılımını buradan okur. Yazma başarısız olursa build
|
|
221
|
-
düşmez — analiz verisi en iyi çabadır. **Çalışma zamanı bu dosyaya bağımlı
|
|
222
|
-
değildir.**
|
|
223
|
-
|
|
224
|
-
## Fontlar
|
|
225
|
-
|
|
226
|
-
`next/font/google` yerine self-host font dosyaları.
|
|
227
|
-
|
|
228
|
-
Dosyalar `public/fonts/` altında **sabit isimlerle** durur (hash yok), çünkü
|
|
229
|
-
`@font-face` içindeki `url()` yolları elle yazılıyor; hash'lemek her build'de
|
|
230
|
-
stylesheet'i de değiştirmek zorunda bırakırdı.
|
|
231
|
-
|
|
232
|
-
Dosya yoksa **bir kez** Google Fonts'tan indirilir ve **commit edilmesi
|
|
233
|
-
beklenir**: build'in ağa bağımlı olması CI'da kırılgan. İndirme başarısız olursa
|
|
234
|
-
uyarı basılır ve sayfa sistem font yığınına düşer — build durmaz.
|
|
235
|
-
|
|
236
|
-
Yalnızca latin subset'i (`U+0000-00FF`) indirilir: diğerleri çoğu site için ölü
|
|
237
|
-
ağırlık ve `unicode-range` olmadan hepsini indirmek font boyutunu katlar.
|
|
238
|
-
|
|
239
|
-
Kullanımı stylesheet'te elle yazılır:
|
|
240
|
-
|
|
241
|
-
```css
|
|
242
|
-
@font-face {
|
|
243
|
-
font-family: "Inter";
|
|
244
|
-
font-style: normal;
|
|
245
|
-
font-weight: 400;
|
|
246
|
-
font-display: swap;
|
|
247
|
-
src: url("/fonts/inter-400.woff2") format("woff2");
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
`.woff2` uzantısı ve `/fonts/` öneki varsayılan `static` kurallarında olduğu için
|
|
252
|
-
bu dosyalara otomatik olarak `immutable` cache yazılır.
|
|
253
|
-
|
|
254
|
-
## İkon sprite
|
|
255
|
-
|
|
256
|
-
**Yalnızca kaynakta kullanılan** ikonlar için bir `<symbol>` seti üretir. Tüm
|
|
257
|
-
seti göndermek 1500+ ikon, yani birkaç megabayt; kullanım taraması sprite'ı
|
|
258
|
-
tipik olarak 10-30 sembolde tutuyor. Çıktı hash'li `sprite.svg` olarak
|
|
259
|
-
`public/assets/` altına yazılır ve precompress kapsamına girer.
|
|
260
|
-
|
|
261
|
-
Kaynak **XOR** seçilir — ikisi birleştirilmez:
|
|
262
|
-
|
|
263
|
-
1. `icons.dir` (varsayılan `icons/`) **dizin olarak varsa** yalnızca oradaki
|
|
264
|
-
düz SVG'ler. Boş dizin Phosphor'a düşmez; fallback için dizini silin.
|
|
265
|
-
2. Aksi hâlde `@phosphor-icons/core` (uygulamanın `node_modules`'ünden). Kurulu
|
|
266
|
-
değilse adım sessizce atlanır.
|
|
267
|
-
|
|
268
|
-
Yerel dosya adları:
|
|
269
|
-
|
|
270
|
-
| Dosya | Sprite anahtarı |
|
|
271
|
-
| --- | --- |
|
|
272
|
-
| `icons/house.svg` | `house:regular` |
|
|
273
|
-
| `icons/house-regular.svg` | `house:regular` |
|
|
274
|
-
| `icons/arrow-right-bold.svg` | `arrow-right:bold` |
|
|
275
|
-
|
|
276
|
-
- Sembol id'si: `<kebab-ad>-<weight>`, örn. `arrow-right-bold`.
|
|
277
|
-
- `viewBox` kaynak SVG'den `<symbol>`'e taşınır; yoksa `0 0 256 256`
|
|
278
|
-
(Phosphor ve `icon()` ile uyum için önerilen kutu).
|
|
279
|
-
- Taranan dizinler varsayılan olarak `views`, `client`, `routes`, `lib`,
|
|
280
|
-
`features`, `shared`; `icons.scan` ile değiştirilebilir. Taranan uzantılar:
|
|
281
|
-
`.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
|
|
282
|
-
- Ağırlıklar: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. Tanınmayan
|
|
283
|
-
bir ağırlık `regular` sayılır.
|
|
284
|
-
|
|
285
|
-
### Tarama neyi bulur
|
|
286
|
-
|
|
287
|
-
| Kaynaktaki biçim | Bulunur mu |
|
|
288
|
-
| --- | --- |
|
|
289
|
-
| `icon({ name: "ArrowRight", weight: "bold" })` | ✓ ad + ağırlık |
|
|
290
|
-
| `icon({ name: cond ? "A" : "B" })` | ✓ her iki sabit ad |
|
|
291
|
-
| `data-icon="flag:fill"` ya da `"data-icon": "flag:fill"` | ✓ |
|
|
292
|
-
| `icon: "XLogo"` / `iconName: "XLogo"` (yapılandırma listelerinde) | ✓ ad; ağırlıklar dolaylı çağrılardan toplananlar |
|
|
293
|
-
| `icon({ name: item.icon })` | ✗ ad statik görünmez |
|
|
294
|
-
|
|
295
|
-
Son satır için iki güvenlik ağı var: ad taşıyan yapılandırma alanları
|
|
296
|
-
(`icon: "XLogo"`) ayrıca aranır, ve development'ta `icon()` sprite'taki
|
|
297
|
-
sembolleri okuyup eksik olan için tek seferlik uyarı basar:
|
|
298
|
-
|
|
299
|
-
```
|
|
300
|
-
[icon] missing from sprite: x-logo-regular — write the name as a literal or add
|
|
301
|
-
it to the build/tasks/icons.mjs scan.
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
Bu uyarıyı görürseniz ya adı sabit yazın, ya `icons.scan` listesine ilgili
|
|
305
|
-
dizini ekleyin, ya da adı bir yapılandırma alanında `icon: "XLogo"` biçiminde
|
|
306
|
-
tutun.
|
|
307
|
-
|
|
308
|
-
Kaynakta bulunamayan adlar build sonunda özet olarak uyarılır:
|
|
309
|
-
`N icons missing → …`
|
|
310
|
-
|
|
311
|
-
## Görsel optimizasyonu
|
|
312
|
-
|
|
313
|
-
`next/image` optimizer'ının build zamanı karşılığı. `public/` altındaki elle
|
|
314
|
-
konmuş png/jpg dosyaları için birkaç genişlikte webp üretir ve
|
|
315
|
-
`.jskelet/images.json` manifest'ine yazar. `image()` bu manifest'e bakıp
|
|
316
|
-
`srcset` + intrinsic `width`/`height` ekler; çağıran taraf hiçbir şey
|
|
317
|
-
değiştirmez ([04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)).
|
|
318
|
-
|
|
319
|
-
- Çıktılar hash'li olarak `public/assets/img/` altına düşer, yani `immutable`
|
|
320
|
-
cache ve precompress kapsamına girerler.
|
|
321
|
-
- **Kaynak dosyalar olduğu yerde kalır:** manifest'te olmayan bir görsel her
|
|
322
|
-
zaman orijinaliyle servis edilir.
|
|
323
|
-
- `assets` ve `fonts` dizinleri her zaman atlanır; ek dizinler `images.skip` ile.
|
|
324
|
-
- Genişlikler kaynaktan büyük olanlar elenerek kullanılır ve kaynağın kendi
|
|
325
|
-
genişliği (en fazla 1920) her zaman listeye girer. Retina ekranlarda bile
|
|
326
|
-
1920'nin üstü israf.
|
|
327
|
-
- Varyant hash'i **kaynak + genişlikten** türetilir: aynı içerik her build'de
|
|
328
|
-
aynı dosya adını verir, `immutable` cache bayatlamaz.
|
|
329
|
-
- Manifest'e kodlayıcı imzası yazılır (`webp-q78-e4`). Kalite ayarı değişince
|
|
330
|
-
imza da değişir ve tüm görseller yeniden kodlanır; aksi hâlde eski ayarla
|
|
331
|
-
üretilmiş çıktılar sessizce kalırdı.
|
|
332
|
-
- Kaynak değişmediyse ve çıktılar hâlâ yerindeyse yeniden kodlanmaz. Büyük bir
|
|
333
|
-
`public/` dizininde bu, build süresini dakikalardan saniyelere indirir.
|
|
334
|
-
- Bozuk/okunamayan tek bir görsel build'i düşürmez: uyarı basılır ve manifest'te
|
|
335
|
-
yer almadığı için orijinal dosya servis edilmeye devam eder.
|
|
336
|
-
- Manifest'te artık geçmeyen eski çıktılar silinir.
|
|
337
|
-
|
|
338
|
-
Bu adım `sharp` gerektirir. Kurulu değilse adım sessizce atlanır ve `image()`
|
|
339
|
-
orijinal dosyaya döner. Watch turunda hiç çalışmaz.
|
|
340
|
-
|
|
341
|
-
## Runtime uzak görsel proxy
|
|
342
|
-
|
|
343
|
-
`images.remote.allowHosts` verilirse `createApp` `/_jskelet/image` ucunu
|
|
344
|
-
mount eder. CMS / CDN kapakları build'e girmediği için `image()` bu host'lardaki
|
|
345
|
-
URL'leri `?url=&w=` biçiminde yeniden yazar; uç sharp ile webp üretir ve
|
|
346
|
-
`.jskelet/image-cache/` altına yazar. Dizin 256 MB'yi geçince en eski dosya
|
|
347
|
-
düşer. Upstream fetch redirect'leri elle takip
|
|
348
|
-
edilir: her hop allowlist + private IP / DNS kontrolünden geçer (açık redirect
|
|
349
|
-
SSRF kapalı). Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
|
|
350
|
-
|
|
351
|
-
## Precompress
|
|
352
|
-
|
|
353
|
-
Build çıktısı varlıkların brotli (kalite 11) ve gzip (seviye 9) kopyalarını
|
|
354
|
-
üretir: `app.<hash>.css.br`, `app.<hash>.css.gz`, …
|
|
355
|
-
|
|
356
|
-
- Yalnızca `public/assets/` kapsanır: oradaki dosyalar hash'li ve `immutable`,
|
|
357
|
-
yani içerikleri hiç değişmiyor ve her istekte yeniden sıkıştırmak boşa CPU.
|
|
358
|
-
Build'de bir kez kalite 11 ile sıkıştırmak hem sunucu yükünü sıfırlar hem de
|
|
359
|
-
çalışma anında göze alınamayacak bir oran verir (istek anındaki kalite 5'e
|
|
360
|
-
karşı).
|
|
361
|
-
- `public/` altındaki elle konmuş dosyalar küçük ve seyrek istendiği için
|
|
362
|
-
çalışma anındaki sıkıştırmaya bırakılır.
|
|
363
|
-
- Sıkıştırılan uzantılar: `.css`, `.js`, `.mjs`, `.svg`, `.json`, `.xml`,
|
|
364
|
-
`.txt`, `.map`. Zaten sıkışık formatlar (woff2, png, jpg, webp) atlanır.
|
|
365
|
-
- 1 KB altındaki dosyalar atlanır: kazanç başlık maliyetini karşılamıyor.
|
|
366
|
-
- Önceki turdan kalan `.br`/`.gz` kopyalar önce silinir, bayatlamasın.
|
|
367
|
-
- Watch modunda çalışmaz: her değişiklikte kalite-11 brotli yavaş.
|
|
368
|
-
|
|
369
|
-
Bu dosyaları `staticPrecompressed` middleware'i servis eder; kopya yoksa istek
|
|
370
|
-
`express.static`e devredilir ([02-mimari.md](./02-mimari.md)).
|
|
371
|
-
|
|
372
|
-
## Opsiyonel peer bağımlılıkları
|
|
373
|
-
|
|
374
|
-
| Paket | Gerekli olduğu adım | Yoksa ne olur |
|
|
375
|
-
| --- | --- | --- |
|
|
376
|
-
| `postcss` | CSS | CSS adımı **hata verir** (zorunlu import) |
|
|
377
|
-
| `@tailwindcss/postcss` | CSS | CSS adımı **hata verir** |
|
|
378
|
-
| `tailwindcss` | CSS (peer) | Tailwind direktifleri çözülemez |
|
|
379
|
-
| `lightningcss` | CSS minifikasyonu | Tailwind çıktısı kullanılır, birkaç kB daha büyük |
|
|
380
|
-
| `sharp` | Görsel optimizasyonu | Adım atlanır; `image()` orijinali kullanır |
|
|
381
|
-
| `@phosphor-icons/core` | İkon sprite (yerel `icons/` yoksa) | Adım atlanır; `icon()` boş `<use>` üretir |
|
|
382
|
-
|
|
383
|
-
CSS kullanmayacaksanız `paths.styles` dosyasını hiç oluşturmayın: adım uyarıyla
|
|
384
|
-
atlanır ve postcss'e ihtiyaç kalmaz.
|
|
385
|
-
|
|
386
|
-
Paketler **uygulamanın** `node_modules`'ünden çözülür, framework'ün kendisinden
|
|
387
|
-
değil. Framework `file:` ya da workspace bağlantısıyla kuruluysa kaynak
|
|
388
|
-
dosyaları kendi dizininde çalışır ve düz bir `import "postcss"` framework'ün
|
|
389
|
-
ağacına bakar — uygulamanınkine değil. Bu yüzden çözümleme uygulama kökünden
|
|
390
|
-
başlatılır.
|
|
391
|
-
|
|
392
|
-
## `.gitignore` önerisi
|
|
393
|
-
|
|
394
|
-
```
|
|
395
|
-
node_modules/
|
|
396
|
-
.jskelet/
|
|
397
|
-
public/assets/
|
|
398
|
-
.env
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
`public/fonts/` **commit edilmelidir** (build'in ağa bağımlı olmaması için),
|
|
402
|
-
`public/assets/` edilmemelidir (her build'de yeniden üretilir).
|
|
403
|
-
|
|
404
|
-
## `jskelet start` ve eksik build
|
|
405
|
-
|
|
406
|
-
`jskelet start` önce `.jskelet/manifest.json` dosyasına bakar; yoksa build'i
|
|
407
|
-
kendisi çalıştırır. Docker imajında build zaten yapıldığı için bu bir no-op;
|
|
408
|
-
amaç `npm start`ı doğrudan çalıştıran birinin stilsiz bir sayfayla
|
|
409
|
-
karşılaşmaması.
|
|
410
|
-
|
|
411
|
-
## Teşhis: sık görülen durumlar
|
|
412
|
-
|
|
413
|
-
- **Stil hiç yok.** Build çalışmamış (`hasAsset('app.css')` false) ya da
|
|
414
|
-
`paths.styles` dosyası mevcut değil. Build çıktısındaki `CSS` satırını
|
|
415
|
-
kontrol edin.
|
|
416
|
-
- **Bazı Tailwind sınıfları çalışmıyor.** `@source` direktifi eksik olan bir
|
|
417
|
-
dizinde yazılmışlar.
|
|
418
|
-
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] missing from sprite`
|
|
419
|
-
uyarısına bakın.
|
|
420
|
-
- **Island'lar hiç açılmıyor.** `main.js` manifest'te yok (entry dizini boş ya
|
|
421
|
-
da build atlanmış) veya bir build hatası var.
|
|
422
|
-
- **Dev'de sayfa aniden stilsiz kaldı.** Manifest ile diskteki dosya
|
|
423
|
-
ayrışmıştır; `jskelet dev`i yeniden başlatmak yeterli.
|
|
424
|
-
|
|
425
|
-
## Sırada ne var
|
|
426
|
-
|
|
427
|
-
- Watch akışı ve CSS hot-swap: [09-dev-araclari.md](./09-dev-araclari.md)
|
|
428
|
-
- Prod build + start ve Docker: [10-dagitim.md](./10-dagitim.md)
|
|
429
|
-
- `entries` ve island bundle'ının kullanımı: [05-islands.md](./05-islands.md)
|
|
1
|
+
# 08 — Build
|
|
2
|
+
|
|
3
|
+
Bu belge `jskelet build`in yaptığı her işi ve sırasını anlatır: font kopyalama,
|
|
4
|
+
ikon sprite üretimi, Tailwind CSS derlemesi, esbuild ile island bundle'ı, görsel
|
|
5
|
+
optimizasyonu, manifest yazımı ve önceden sıkıştırma. Ayrıca hash'li varlıkların
|
|
6
|
+
`asset()`/`hasAsset()` ile şablonlara nasıl ulaştığı, Tailwind'in `@source`
|
|
7
|
+
direktiflerinin neden zorunlu olduğu ve opsiyonel peer bağımlılıklarının
|
|
8
|
+
davranışı burada. Çıktının çalışma anında nasıl servis edildiği
|
|
9
|
+
[02-mimari.md](./02-mimari.md)'de, build'i tetikleyen watch akışı
|
|
10
|
+
[09-dev-araclari.md](./09-dev-araclari.md)'de.
|
|
11
|
+
|
|
12
|
+
## Hat ve sırası
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
0. Templates .jsk → .jskelet/templates/*.mjs (her zaman; dosya yoksa no-op)
|
|
16
|
+
1. Fonts config.fonts varsa
|
|
17
|
+
2. Icon sprite config.icons !== false ise
|
|
18
|
+
3. CSS styles giriş dosyası varsa
|
|
19
|
+
4. Client JS client/entries/ varsa
|
|
20
|
+
5. Images config.images !== false, watch değil ve sharp kurulu ise
|
|
21
|
+
6. Manifest .jskelet/manifest.json
|
|
22
|
+
7. Precompress watch değilse
|
|
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
|
+
|
|
29
|
+
Sıra rastgele değil:
|
|
30
|
+
|
|
31
|
+
- **CSS ikon sprite'ından sonra gelir.** Sprite bir varlıktır ve sınıf üretmez,
|
|
32
|
+
ama manifest anahtarı verir.
|
|
33
|
+
- **Precompress en sonda:** sıkıştırılacak her şey üretilmiş olmalı.
|
|
34
|
+
- **Görseller watch turunda hiç çalışmaz:** `sharp` ile yeniden kodlama pahalı.
|
|
35
|
+
|
|
36
|
+
Görevler yalnızca ilgili yapılandırma varsa çalışır. Font tanımlamayan bir proje
|
|
37
|
+
font adımını hiç görmez; bu, "framework her projeye kendi varsayımlarını
|
|
38
|
+
dayatmaz" ilkesinin build tarafındaki karşılığı.
|
|
39
|
+
|
|
40
|
+
Terminal çıktısı hizalı adım satırları ve sonunda bir `output` bloğu verir: her
|
|
41
|
+
varlığın ham ve brotli boyutu, büyükten küçüğe.
|
|
42
|
+
|
|
43
|
+
## Manifest ve hash'li varlıklar
|
|
44
|
+
|
|
45
|
+
Build çıktısı `public/assets/` altına **içerik hash'li** adlarla yazılır ve
|
|
46
|
+
mantıksal ad → public URL eşlemesi `.jskelet/manifest.json` dosyasına konur:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"app.css": "/assets/app.4f2a1b9c07.css",
|
|
51
|
+
"sprite.svg": "/assets/sprite.dc973997bd.svg",
|
|
52
|
+
"main.js": "/assets/js/main.9E1AB2C3.js",
|
|
53
|
+
"inter-400.woff2": "/fonts/inter-400.woff2"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Hash sha256'nın ilk 10 hex karakteridir: çakışma için fazlasıyla yeterli ve
|
|
58
|
+
dosya adlarını okunur tutuyor. Hash'li olmaları sayesinde bu dosyalara
|
|
59
|
+
`Cache-Control: public, max-age=31536000, immutable` yazılabilir.
|
|
60
|
+
|
|
61
|
+
### `asset(name)` ve `hasAsset(name)`
|
|
62
|
+
|
|
63
|
+
Şablonlara otomatik geçer; sunucu kodunda `import { asset, hasAsset } from "jskelet"`.
|
|
64
|
+
|
|
65
|
+
```ejs
|
|
66
|
+
<% if (hasAsset('app.css')) { %>
|
|
67
|
+
<link rel="stylesheet" href="<%= asset('app.css') %>" data-jskelet-css="app.css">
|
|
68
|
+
<% } %>
|
|
69
|
+
<% styles.forEach(function (sheet) { %>
|
|
70
|
+
<% if (hasAsset(sheet)) { %>
|
|
71
|
+
<link rel="stylesheet" href="<%= asset(sheet) %>" data-jskelet-css="<%= sheet %>">
|
|
72
|
+
<% } %>
|
|
73
|
+
<% }); %>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `asset(name)` manifest'te varsa hash'li URL'i, yoksa `/assets/<name>` döner.
|
|
77
|
+
- `hasAsset(name)` manifest'te olup olmadığını söyler.
|
|
78
|
+
|
|
79
|
+
Build çalışmadıysa uygulama yine ayağa kalkar: `hasAsset()` false olur, layout
|
|
80
|
+
stylesheet ve script etiketlerini hiç basmaz. `jskelet build` unutulduğunda hata
|
|
81
|
+
yerine stilsiz ama çalışan bir sayfa görürsünüz. Manifest hiç yoksa bir kez uyarı
|
|
82
|
+
basılır: ``[assets] no manifest — run `jskelet build`.``
|
|
83
|
+
|
|
84
|
+
Manifest **dev'de her istekte** yeniden okunur (watch build hash'leri
|
|
85
|
+
değiştirir), prod'da bir kez.
|
|
86
|
+
|
|
87
|
+
### Watch modunda manifest tutarlılığı
|
|
88
|
+
|
|
89
|
+
Watch turunda yeniden derlenen varlık yeni bir hash'e yazılıp eskisi silinir. Bu
|
|
90
|
+
yüzden manifest de güncellenmek zorunda (`patchManifest`): aksi hâlde HTML
|
|
91
|
+
silinmiş dosyayı isteyip 404 alır ve sayfa dev oturumunun kalanında stilsiz ya
|
|
92
|
+
da JS'siz kalır. CSS ve client görevlerinin ikisi de her turda kendi anahtarını
|
|
93
|
+
yamalar; diğer anahtarlar korunur. CSS tarafı watch'ta `syncCssManifest` ile
|
|
94
|
+
tüm `.css` anahtarlarını günceller (silinen sayfa sheet'leri de düşer).
|
|
95
|
+
|
|
96
|
+
## CSS — Tailwind v4
|
|
97
|
+
|
|
98
|
+
Giriş dosyası `paths.styles` (varsayılan `styles/globals.css`). Dosya yoksa adım
|
|
99
|
+
uyarıyla atlanır.
|
|
100
|
+
|
|
101
|
+
Boru hattı: PostCSS + `@tailwindcss/postcss` → (varsa) lightningcss ile
|
|
102
|
+
minifikasyon → `writeAsset("app.css", …)`.
|
|
103
|
+
|
|
104
|
+
- **PostCSS boru hattı bir kez kurulur:** Tailwind'in kendi önbelleği plugin
|
|
105
|
+
örneğinde yaşıyor; her derlemede yeniden oluşturmak watch turlarını belirgin
|
|
106
|
+
şekilde yavaşlatıyor.
|
|
107
|
+
- **lightningcss opsiyoneldir:** yoksa Tailwind'in kendi çıktısı kullanılır,
|
|
108
|
+
yalnızca birkaç kB daha büyük olur.
|
|
109
|
+
- Global çıktı `app.css`'tir ve layout onu her sayfada render-blocking olarak
|
|
110
|
+
yükler. Ayrı bir "critical CSS" üretilmemesinin ölçüm gerekçesi
|
|
111
|
+
[02-mimari.md](./02-mimari.md)'de.
|
|
112
|
+
|
|
113
|
+
### Sayfa stylesheet'leri (`styles/pages/`)
|
|
114
|
+
|
|
115
|
+
Island `entries` ile aynı sözleşme. `styles/pages/*.css` altındaki her dosya
|
|
116
|
+
ayrı bir hash'li varlıktır (`home.css` → `/assets/home.<hash>.css`). Controller
|
|
117
|
+
yalnızca istediği sayfada yükler:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
return {
|
|
121
|
+
view: "pages/home",
|
|
122
|
+
styles: ["home.css"],
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Dizin, `paths.styles` dosyasının yanındaki `pages/` klasörüdür (`styles` taşınırsa
|
|
127
|
+
pages de yanında kalır). Dizin yoksa veya boşsa adım yalnızca global sheet üretir.
|
|
128
|
+
|
|
129
|
+
Sayfa CSS'i sayfaya özel kurallar içindir. Tailwind utility'leri global sheet'te
|
|
130
|
+
kalmalı — dosyada tam `@import "tailwindcss"` utility çıktısını tekrarlar.
|
|
131
|
+
|
|
132
|
+
Layout `app.css`ten sonra `styles` dizisindeki her sheet için
|
|
133
|
+
`<link data-jskelet-css="…">` basar; `hasAsset` false ise etiket yok.
|
|
134
|
+
|
|
135
|
+
### `@source` direktifleri zorunludur
|
|
136
|
+
|
|
137
|
+
Tailwind v4'ün sınıf taraması `globals.css` içindeki `@source` direktiflerine
|
|
138
|
+
bağlıdır. Otomatik tespit yalnızca stylesheet'in bulunduğu dizini tarar, bu
|
|
139
|
+
yüzden şablonlarda geçen varyantlar (`data-[active=false]:…` gibi) **sessizce
|
|
140
|
+
düşer**.
|
|
141
|
+
|
|
142
|
+
```css
|
|
143
|
+
@import "tailwindcss" source(none);
|
|
144
|
+
|
|
145
|
+
@source "../views";
|
|
146
|
+
@source "../client";
|
|
147
|
+
@source "../routes";
|
|
148
|
+
|
|
149
|
+
.wrapper {
|
|
150
|
+
max-width: 48rem;
|
|
151
|
+
margin-inline: auto;
|
|
152
|
+
padding-inline: 1rem;
|
|
153
|
+
padding-block: 2rem;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`source(none)` otomatik tespiti kapatır ve taramayı tamamen açık hâle getirir.
|
|
158
|
+
**Yeni bir üst dizin eklediğinizde `@source` satırını da ekleyin** — sınıfların
|
|
159
|
+
"bazen çalışmaması"nın en yaygın sebebi budur.
|
|
160
|
+
|
|
161
|
+
### CSS watch kapsamı
|
|
162
|
+
|
|
163
|
+
Watch modunda üç hedef izlenir: stylesheet'in bulunduğu dizin, `views` ve
|
|
164
|
+
`client`. Şablon ve island dosyaları da izlenir çünkü Tailwind sınıfları oradan
|
|
165
|
+
geliyor; yalnızca `styles/` izlemek yeni bir utility yazıldığında rebuild
|
|
166
|
+
etmezdi. Değişiklikler 120 ms birleştirilir.
|
|
167
|
+
|
|
168
|
+
## Client JS — esbuild
|
|
169
|
+
|
|
170
|
+
`client/entries/*.{js,ts,mts}` içindeki her kaynak dosya bir entry'dir (`.tsx`
|
|
171
|
+
yok). Manifest anahtarı her zaman `*.js` olur (`main.ts` → `main.js`). Aynı stem
|
|
172
|
+
için birden fazla uzantı build hatasıdır. Dizin yoksa ya da boşsa adım atlanır.
|
|
173
|
+
|
|
174
|
+
esbuild ayarları:
|
|
175
|
+
|
|
176
|
+
| Ayar | Değer | Sebebi |
|
|
177
|
+
| --- | --- | --- |
|
|
178
|
+
| `bundle`, `splitting` | `true` | Ortak modüller paylaşılan chunk'a çıkar |
|
|
179
|
+
| `format` | `esm` | `type="module"` script'ler |
|
|
180
|
+
| `target` | `chrome111`, `edge111`, `firefox111`, `safari16.4` | ESM + dinamik import + `IntersectionObserver` island modelinin alt sınırı; daha eskisine transpile etmek çıktıyı büyütüp hiçbir ziyaretçi kazandırmıyor |
|
|
181
|
+
| `minify` | `true` | — |
|
|
182
|
+
| `sourcemap` | yalnızca `NODE_ENV=development` | Prod'da `.map` dosyaları `public/assets` altında yayınlanmaz |
|
|
183
|
+
| `entryNames` | `[name].[hash]` | `immutable` cache |
|
|
184
|
+
| `chunkNames` | `chunks/[name].[hash]` | — |
|
|
185
|
+
| `legalComments` | `none` | — |
|
|
186
|
+
|
|
187
|
+
Çıktı `public/assets/js/` altına düşer ve her turda önce temizlenir.
|
|
188
|
+
`browserslist` okunmaz; hedef listesi kod içinde sabittir.
|
|
189
|
+
|
|
190
|
+
### `@/` alias'ı
|
|
191
|
+
|
|
192
|
+
esbuild tarafında `@/` proje köküne çözülür ve uzantı tamamlama yapılır
|
|
193
|
+
(`.js`, `.mjs`, `.ts`, `.mts`, `.json`, `/index.js`, `/index.ts`). Node
|
|
194
|
+
`alias-hooks.mjs` sunucuda yalnızca `.js` / `.mjs` / `.json` çözer; paylaşılan
|
|
195
|
+
`@/lib` dosyaları bu yüzden `.js` kalmalıdır. Client-only `.ts` import'ları
|
|
196
|
+
esbuild hattında çalışır.
|
|
197
|
+
|
|
198
|
+
### `clientEnv` gömülmesi
|
|
199
|
+
|
|
200
|
+
Tarayıcıda `process` yoktur; sunucuyla paylaşılan modüller yine de `process.env`
|
|
201
|
+
okur. `config.clientEnv` ile bildirilen anahtarlar ve `NODE_ENV` build zamanında
|
|
202
|
+
tek nesne olarak define edilir, yani listede olmayan bir anahtar okunduğunda
|
|
203
|
+
çökme yerine `undefined` döner. İsimleri secret benzeri olan anahtarlar
|
|
204
|
+
(`SECRET`, `API_KEY`, …) build'i düşürür; `PUBLIC` / `PUBLISHABLE` içerenler
|
|
205
|
+
muaf. Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
|
|
206
|
+
|
|
207
|
+
### Manifest anahtarları
|
|
208
|
+
|
|
209
|
+
Yalnızca **gerçek entry'ler** manifest'e girer: dinamik import'lar da
|
|
210
|
+
`entryPoint` taşır ve filtrelenmezse her island ayrı bir manifest anahtarı
|
|
211
|
+
olurdu. Anahtar dosya adının kendisidir (`main.js`, `chart.js`), değer hash'li
|
|
212
|
+
URL.
|
|
213
|
+
|
|
214
|
+
Bu yüzden controller `entries: ["chart.js"]` yazarken hash'i bilmek zorunda
|
|
215
|
+
değildir ([05-islands.md](./05-islands.md)).
|
|
216
|
+
|
|
217
|
+
### `metafile.json`
|
|
218
|
+
|
|
219
|
+
esbuild metafile'ı `.jskelet/metafile.json` dosyasına yazılır; dev panelindeki
|
|
220
|
+
chunk analizi giriş/çıkış kırılımını buradan okur. Yazma başarısız olursa build
|
|
221
|
+
düşmez — analiz verisi en iyi çabadır. **Çalışma zamanı bu dosyaya bağımlı
|
|
222
|
+
değildir.**
|
|
223
|
+
|
|
224
|
+
## Fontlar
|
|
225
|
+
|
|
226
|
+
`next/font/google` yerine self-host font dosyaları.
|
|
227
|
+
|
|
228
|
+
Dosyalar `public/fonts/` altında **sabit isimlerle** durur (hash yok), çünkü
|
|
229
|
+
`@font-face` içindeki `url()` yolları elle yazılıyor; hash'lemek her build'de
|
|
230
|
+
stylesheet'i de değiştirmek zorunda bırakırdı.
|
|
231
|
+
|
|
232
|
+
Dosya yoksa **bir kez** Google Fonts'tan indirilir ve **commit edilmesi
|
|
233
|
+
beklenir**: build'in ağa bağımlı olması CI'da kırılgan. İndirme başarısız olursa
|
|
234
|
+
uyarı basılır ve sayfa sistem font yığınına düşer — build durmaz.
|
|
235
|
+
|
|
236
|
+
Yalnızca latin subset'i (`U+0000-00FF`) indirilir: diğerleri çoğu site için ölü
|
|
237
|
+
ağırlık ve `unicode-range` olmadan hepsini indirmek font boyutunu katlar.
|
|
238
|
+
|
|
239
|
+
Kullanımı stylesheet'te elle yazılır:
|
|
240
|
+
|
|
241
|
+
```css
|
|
242
|
+
@font-face {
|
|
243
|
+
font-family: "Inter";
|
|
244
|
+
font-style: normal;
|
|
245
|
+
font-weight: 400;
|
|
246
|
+
font-display: swap;
|
|
247
|
+
src: url("/fonts/inter-400.woff2") format("woff2");
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`.woff2` uzantısı ve `/fonts/` öneki varsayılan `static` kurallarında olduğu için
|
|
252
|
+
bu dosyalara otomatik olarak `immutable` cache yazılır.
|
|
253
|
+
|
|
254
|
+
## İkon sprite
|
|
255
|
+
|
|
256
|
+
**Yalnızca kaynakta kullanılan** ikonlar için bir `<symbol>` seti üretir. Tüm
|
|
257
|
+
seti göndermek 1500+ ikon, yani birkaç megabayt; kullanım taraması sprite'ı
|
|
258
|
+
tipik olarak 10-30 sembolde tutuyor. Çıktı hash'li `sprite.svg` olarak
|
|
259
|
+
`public/assets/` altına yazılır ve precompress kapsamına girer.
|
|
260
|
+
|
|
261
|
+
Kaynak **XOR** seçilir — ikisi birleştirilmez:
|
|
262
|
+
|
|
263
|
+
1. `icons.dir` (varsayılan `icons/`) **dizin olarak varsa** yalnızca oradaki
|
|
264
|
+
düz SVG'ler. Boş dizin Phosphor'a düşmez; fallback için dizini silin.
|
|
265
|
+
2. Aksi hâlde `@phosphor-icons/core` (uygulamanın `node_modules`'ünden). Kurulu
|
|
266
|
+
değilse adım sessizce atlanır.
|
|
267
|
+
|
|
268
|
+
Yerel dosya adları:
|
|
269
|
+
|
|
270
|
+
| Dosya | Sprite anahtarı |
|
|
271
|
+
| --- | --- |
|
|
272
|
+
| `icons/house.svg` | `house:regular` |
|
|
273
|
+
| `icons/house-regular.svg` | `house:regular` |
|
|
274
|
+
| `icons/arrow-right-bold.svg` | `arrow-right:bold` |
|
|
275
|
+
|
|
276
|
+
- Sembol id'si: `<kebab-ad>-<weight>`, örn. `arrow-right-bold`.
|
|
277
|
+
- `viewBox` kaynak SVG'den `<symbol>`'e taşınır; yoksa `0 0 256 256`
|
|
278
|
+
(Phosphor ve `icon()` ile uyum için önerilen kutu).
|
|
279
|
+
- Taranan dizinler varsayılan olarak `views`, `client`, `routes`, `lib`,
|
|
280
|
+
`features`, `shared`; `icons.scan` ile değiştirilebilir. Taranan uzantılar:
|
|
281
|
+
`.ejs`, `.jsk`, `.js`, `.mjs`, `.ts`, `.mts`.
|
|
282
|
+
- Ağırlıklar: `thin`, `light`, `regular`, `bold`, `fill`, `duotone`. Tanınmayan
|
|
283
|
+
bir ağırlık `regular` sayılır.
|
|
284
|
+
|
|
285
|
+
### Tarama neyi bulur
|
|
286
|
+
|
|
287
|
+
| Kaynaktaki biçim | Bulunur mu |
|
|
288
|
+
| --- | --- |
|
|
289
|
+
| `icon({ name: "ArrowRight", weight: "bold" })` | ✓ ad + ağırlık |
|
|
290
|
+
| `icon({ name: cond ? "A" : "B" })` | ✓ her iki sabit ad |
|
|
291
|
+
| `data-icon="flag:fill"` ya da `"data-icon": "flag:fill"` | ✓ |
|
|
292
|
+
| `icon: "XLogo"` / `iconName: "XLogo"` (yapılandırma listelerinde) | ✓ ad; ağırlıklar dolaylı çağrılardan toplananlar |
|
|
293
|
+
| `icon({ name: item.icon })` | ✗ ad statik görünmez |
|
|
294
|
+
|
|
295
|
+
Son satır için iki güvenlik ağı var: ad taşıyan yapılandırma alanları
|
|
296
|
+
(`icon: "XLogo"`) ayrıca aranır, ve development'ta `icon()` sprite'taki
|
|
297
|
+
sembolleri okuyup eksik olan için tek seferlik uyarı basar:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
[icon] missing from sprite: x-logo-regular — write the name as a literal or add
|
|
301
|
+
it to the build/tasks/icons.mjs scan.
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Bu uyarıyı görürseniz ya adı sabit yazın, ya `icons.scan` listesine ilgili
|
|
305
|
+
dizini ekleyin, ya da adı bir yapılandırma alanında `icon: "XLogo"` biçiminde
|
|
306
|
+
tutun.
|
|
307
|
+
|
|
308
|
+
Kaynakta bulunamayan adlar build sonunda özet olarak uyarılır:
|
|
309
|
+
`N icons missing → …`
|
|
310
|
+
|
|
311
|
+
## Görsel optimizasyonu
|
|
312
|
+
|
|
313
|
+
`next/image` optimizer'ının build zamanı karşılığı. `public/` altındaki elle
|
|
314
|
+
konmuş png/jpg dosyaları için birkaç genişlikte webp üretir ve
|
|
315
|
+
`.jskelet/images.json` manifest'ine yazar. `image()` bu manifest'e bakıp
|
|
316
|
+
`srcset` + intrinsic `width`/`height` ekler; çağıran taraf hiçbir şey
|
|
317
|
+
değiştirmez ([04-render-ve-sablonlar.md](./04-render-ve-sablonlar.md)).
|
|
318
|
+
|
|
319
|
+
- Çıktılar hash'li olarak `public/assets/img/` altına düşer, yani `immutable`
|
|
320
|
+
cache ve precompress kapsamına girerler.
|
|
321
|
+
- **Kaynak dosyalar olduğu yerde kalır:** manifest'te olmayan bir görsel her
|
|
322
|
+
zaman orijinaliyle servis edilir.
|
|
323
|
+
- `assets` ve `fonts` dizinleri her zaman atlanır; ek dizinler `images.skip` ile.
|
|
324
|
+
- Genişlikler kaynaktan büyük olanlar elenerek kullanılır ve kaynağın kendi
|
|
325
|
+
genişliği (en fazla 1920) her zaman listeye girer. Retina ekranlarda bile
|
|
326
|
+
1920'nin üstü israf.
|
|
327
|
+
- Varyant hash'i **kaynak + genişlikten** türetilir: aynı içerik her build'de
|
|
328
|
+
aynı dosya adını verir, `immutable` cache bayatlamaz.
|
|
329
|
+
- Manifest'e kodlayıcı imzası yazılır (`webp-q78-e4`). Kalite ayarı değişince
|
|
330
|
+
imza da değişir ve tüm görseller yeniden kodlanır; aksi hâlde eski ayarla
|
|
331
|
+
üretilmiş çıktılar sessizce kalırdı.
|
|
332
|
+
- Kaynak değişmediyse ve çıktılar hâlâ yerindeyse yeniden kodlanmaz. Büyük bir
|
|
333
|
+
`public/` dizininde bu, build süresini dakikalardan saniyelere indirir.
|
|
334
|
+
- Bozuk/okunamayan tek bir görsel build'i düşürmez: uyarı basılır ve manifest'te
|
|
335
|
+
yer almadığı için orijinal dosya servis edilmeye devam eder.
|
|
336
|
+
- Manifest'te artık geçmeyen eski çıktılar silinir.
|
|
337
|
+
|
|
338
|
+
Bu adım `sharp` gerektirir. Kurulu değilse adım sessizce atlanır ve `image()`
|
|
339
|
+
orijinal dosyaya döner. Watch turunda hiç çalışmaz.
|
|
340
|
+
|
|
341
|
+
## Runtime uzak görsel proxy
|
|
342
|
+
|
|
343
|
+
`images.remote.allowHosts` verilirse `createApp` `/_jskelet/image` ucunu
|
|
344
|
+
mount eder. CMS / CDN kapakları build'e girmediği için `image()` bu host'lardaki
|
|
345
|
+
URL'leri `?url=&w=` biçiminde yeniden yazar; uç sharp ile webp üretir ve
|
|
346
|
+
`.jskelet/image-cache/` altına yazar. Dizin 256 MB'yi geçince en eski dosya
|
|
347
|
+
düşer. Upstream fetch redirect'leri elle takip
|
|
348
|
+
edilir: her hop allowlist + private IP / DNS kontrolünden geçer (açık redirect
|
|
349
|
+
SSRF kapalı). Ayrıntı: [07-yapilandirma.md](./07-yapilandirma.md).
|
|
350
|
+
|
|
351
|
+
## Precompress
|
|
352
|
+
|
|
353
|
+
Build çıktısı varlıkların brotli (kalite 11) ve gzip (seviye 9) kopyalarını
|
|
354
|
+
üretir: `app.<hash>.css.br`, `app.<hash>.css.gz`, …
|
|
355
|
+
|
|
356
|
+
- Yalnızca `public/assets/` kapsanır: oradaki dosyalar hash'li ve `immutable`,
|
|
357
|
+
yani içerikleri hiç değişmiyor ve her istekte yeniden sıkıştırmak boşa CPU.
|
|
358
|
+
Build'de bir kez kalite 11 ile sıkıştırmak hem sunucu yükünü sıfırlar hem de
|
|
359
|
+
çalışma anında göze alınamayacak bir oran verir (istek anındaki kalite 5'e
|
|
360
|
+
karşı).
|
|
361
|
+
- `public/` altındaki elle konmuş dosyalar küçük ve seyrek istendiği için
|
|
362
|
+
çalışma anındaki sıkıştırmaya bırakılır.
|
|
363
|
+
- Sıkıştırılan uzantılar: `.css`, `.js`, `.mjs`, `.svg`, `.json`, `.xml`,
|
|
364
|
+
`.txt`, `.map`. Zaten sıkışık formatlar (woff2, png, jpg, webp) atlanır.
|
|
365
|
+
- 1 KB altındaki dosyalar atlanır: kazanç başlık maliyetini karşılamıyor.
|
|
366
|
+
- Önceki turdan kalan `.br`/`.gz` kopyalar önce silinir, bayatlamasın.
|
|
367
|
+
- Watch modunda çalışmaz: her değişiklikte kalite-11 brotli yavaş.
|
|
368
|
+
|
|
369
|
+
Bu dosyaları `staticPrecompressed` middleware'i servis eder; kopya yoksa istek
|
|
370
|
+
`express.static`e devredilir ([02-mimari.md](./02-mimari.md)).
|
|
371
|
+
|
|
372
|
+
## Opsiyonel peer bağımlılıkları
|
|
373
|
+
|
|
374
|
+
| Paket | Gerekli olduğu adım | Yoksa ne olur |
|
|
375
|
+
| --- | --- | --- |
|
|
376
|
+
| `postcss` | CSS | CSS adımı **hata verir** (zorunlu import) |
|
|
377
|
+
| `@tailwindcss/postcss` | CSS | CSS adımı **hata verir** |
|
|
378
|
+
| `tailwindcss` | CSS (peer) | Tailwind direktifleri çözülemez |
|
|
379
|
+
| `lightningcss` | CSS minifikasyonu | Tailwind çıktısı kullanılır, birkaç kB daha büyük |
|
|
380
|
+
| `sharp` | Görsel optimizasyonu | Adım atlanır; `image()` orijinali kullanır |
|
|
381
|
+
| `@phosphor-icons/core` | İkon sprite (yerel `icons/` yoksa) | Adım atlanır; `icon()` boş `<use>` üretir |
|
|
382
|
+
|
|
383
|
+
CSS kullanmayacaksanız `paths.styles` dosyasını hiç oluşturmayın: adım uyarıyla
|
|
384
|
+
atlanır ve postcss'e ihtiyaç kalmaz.
|
|
385
|
+
|
|
386
|
+
Paketler **uygulamanın** `node_modules`'ünden çözülür, framework'ün kendisinden
|
|
387
|
+
değil. Framework `file:` ya da workspace bağlantısıyla kuruluysa kaynak
|
|
388
|
+
dosyaları kendi dizininde çalışır ve düz bir `import "postcss"` framework'ün
|
|
389
|
+
ağacına bakar — uygulamanınkine değil. Bu yüzden çözümleme uygulama kökünden
|
|
390
|
+
başlatılır.
|
|
391
|
+
|
|
392
|
+
## `.gitignore` önerisi
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
node_modules/
|
|
396
|
+
.jskelet/
|
|
397
|
+
public/assets/
|
|
398
|
+
.env
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
`public/fonts/` **commit edilmelidir** (build'in ağa bağımlı olmaması için),
|
|
402
|
+
`public/assets/` edilmemelidir (her build'de yeniden üretilir).
|
|
403
|
+
|
|
404
|
+
## `jskelet start` ve eksik build
|
|
405
|
+
|
|
406
|
+
`jskelet start` önce `.jskelet/manifest.json` dosyasına bakar; yoksa build'i
|
|
407
|
+
kendisi çalıştırır. Docker imajında build zaten yapıldığı için bu bir no-op;
|
|
408
|
+
amaç `npm start`ı doğrudan çalıştıran birinin stilsiz bir sayfayla
|
|
409
|
+
karşılaşmaması.
|
|
410
|
+
|
|
411
|
+
## Teşhis: sık görülen durumlar
|
|
412
|
+
|
|
413
|
+
- **Stil hiç yok.** Build çalışmamış (`hasAsset('app.css')` false) ya da
|
|
414
|
+
`paths.styles` dosyası mevcut değil. Build çıktısındaki `CSS` satırını
|
|
415
|
+
kontrol edin.
|
|
416
|
+
- **Bazı Tailwind sınıfları çalışmıyor.** `@source` direktifi eksik olan bir
|
|
417
|
+
dizinde yazılmışlar.
|
|
418
|
+
- **İkon boş görünüyor.** Sprite'ta o sembol yok; dev'de `[icon] missing from sprite`
|
|
419
|
+
uyarısına bakın.
|
|
420
|
+
- **Island'lar hiç açılmıyor.** `main.js` manifest'te yok (entry dizini boş ya
|
|
421
|
+
da build atlanmış) veya bir build hatası var.
|
|
422
|
+
- **Dev'de sayfa aniden stilsiz kaldı.** Manifest ile diskteki dosya
|
|
423
|
+
ayrışmıştır; `jskelet dev`i yeniden başlatmak yeterli.
|
|
424
|
+
|
|
425
|
+
## Sırada ne var
|
|
426
|
+
|
|
427
|
+
- Watch akışı ve CSS hot-swap: [09-dev-araclari.md](./09-dev-araclari.md)
|
|
428
|
+
- Prod build + start ve Docker: [10-dagitim.md](./10-dagitim.md)
|
|
429
|
+
- `entries` ve island bundle'ının kullanımı: [05-islands.md](./05-islands.md)
|