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/src/build/tasks/css.mjs
CHANGED
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tailwind v4 → global `app.css` + isteğe bağlı sayfa stylesheet'leri.
|
|
3
|
-
*
|
|
4
|
-
* Global sheet her sayfada yüklenir (`paths.styles`). Sayfa sheet'leri
|
|
5
|
-
* `styles/pages/*.css` altındadır ve yalnızca controller `styles: ["home.css"]`
|
|
6
|
-
* bildiren sayfalarda basılır — island `entries` ile aynı sözleşme.
|
|
7
|
-
*
|
|
8
|
-
* Ayrı bir "critical CSS" üretilmez. Ölçümde inline kritik CSS ilk ekranı tam
|
|
9
|
-
* kapsamadığı için sheet gelince sayfa yeniden akıyordu (bir liste sayfasında
|
|
10
|
-
* CLS 0.307) ve aynı ~27 KB her HTML yanıtında tekrar ediyordu. Sıkıştırılmış
|
|
11
|
-
* global sheet'i render-blocking bırakmak hem daha hızlı hem CLS'siz; ikinci
|
|
12
|
-
* ziyarette zaten immutable önbellekten geliyor. Sayfa sheet'leri de
|
|
13
|
-
* render-blocking; yalnızca ilgili sayfada ek maliyet.
|
|
14
|
-
*
|
|
15
|
-
* Tailwind utility'leri global sheet'te kalmalı. Sayfa CSS'inde tam
|
|
16
|
-
* `@import "tailwindcss"` utility çıktısını tekrarlar; sayfaya özel kurallar
|
|
17
|
-
* yeter.
|
|
18
|
-
*
|
|
19
|
-
* Tailwind'in sınıf taraması `globals.css` içindeki `@source` direktiflerine
|
|
20
|
-
* bağlıdır. Otomatik tespit yalnızca stylesheet'in bulunduğu dizini tarar,
|
|
21
|
-
* bu yüzden şablonlarda geçen varyantlar (`data-[active=false]:…`) sessizce
|
|
22
|
-
* düşer. Yeni bir üst dizin eklediğinizde `@source` satırını da ekleyin.
|
|
23
|
-
*/
|
|
24
|
-
import fs from "node:fs";
|
|
25
|
-
import path from "node:path";
|
|
26
|
-
import { paths, writeAsset, writeManifest } from "../paths.mjs";
|
|
27
|
-
import { importFromApp, tryImportFromApp } from "../resolve-peer.mjs";
|
|
28
|
-
import * as log from "../../log.mjs";
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* PostCSS boru hattı bir kez kurulur: Tailwind'in kendi önbelleği plugin-++
|
|
32
|
-
* örneğinde yaşıyor, her derlemede yeniden oluşturmak watch turlarını
|
|
33
|
-
* belirgin şekilde yavaşlatıyor.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} root
|
|
36
|
-
* @returns {Promise<(inputPath: string) => Promise<string>>}
|
|
37
|
-
*/
|
|
38
|
-
async function createCompiler(root) {
|
|
39
|
-
const [{ default: postcss }, { default: tailwindcss }] = await Promise.all([
|
|
40
|
-
importFromApp(root, "postcss"),
|
|
41
|
-
importFromApp(root, "@tailwindcss/postcss"),
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
// lightningcss opsiyonel: yoksa Tailwind'in kendi çıktısı kullanılır,
|
|
45
|
-
// yalnızca birkaç kB daha büyük olur.
|
|
46
|
-
const lightning = await tryImportFromApp(root, "lightningcss");
|
|
47
|
-
const processor = postcss([tailwindcss()]);
|
|
48
|
-
|
|
49
|
-
return async (inputPath) => {
|
|
50
|
-
const css = fs.readFileSync(inputPath, "utf8");
|
|
51
|
-
const result = await processor.process(css, { from: inputPath, to: inputPath });
|
|
52
|
-
|
|
53
|
-
if (!lightning) return result.css;
|
|
54
|
-
|
|
55
|
-
const minified = lightning.transform({
|
|
56
|
-
filename: path.basename(inputPath),
|
|
57
|
-
code: Buffer.from(result.css),
|
|
58
|
-
minify: true,
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return minified.code.toString("utf8");
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* `paths.styles` dosyasının yanında `pages/` — config'te ayrı alan yok.
|
|
67
|
-
*
|
|
68
|
-
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
69
|
-
* @returns {string}
|
|
70
|
-
*/
|
|
71
|
-
function pageStylesDir(config) {
|
|
72
|
-
return path.join(path.dirname(config.dirs.styles), "pages");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {string} dir
|
|
77
|
-
* @returns {string[]} Mutlak yollar, dosya adına göre sıralı.
|
|
78
|
-
*/
|
|
79
|
-
function listPageStyles(dir) {
|
|
80
|
-
if (!fs.existsSync(dir)) return [];
|
|
81
|
-
return fs
|
|
82
|
-
.readdirSync(dir)
|
|
83
|
-
.filter((name) => name.endsWith(".css"))
|
|
84
|
-
.sort()
|
|
85
|
-
.map((name) => path.join(dir, name));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
90
|
-
* @param {{ watch?: boolean }} [options]
|
|
91
|
-
* @returns {Promise<Record<string, string>>}
|
|
92
|
-
*/
|
|
93
|
-
export async function buildCss(config, { watch = false } = {}) {
|
|
94
|
-
const input = config.dirs.styles;
|
|
95
|
-
const pagesDir = pageStylesDir(config);
|
|
96
|
-
const compile = await createCompiler(config.root);
|
|
97
|
-
|
|
98
|
-
const run = async () => {
|
|
99
|
-
const started = Date.now();
|
|
100
|
-
/** @type {Record<string, string>} */
|
|
101
|
-
const urls = {};
|
|
102
|
-
let bytes = 0;
|
|
103
|
-
|
|
104
|
-
const appCss = await compile(input);
|
|
105
|
-
const appUrl = writeAsset("app.css", appCss);
|
|
106
|
-
urls["app.css"] = appUrl;
|
|
107
|
-
bytes += Buffer.byteLength(appCss);
|
|
108
|
-
|
|
109
|
-
/** @type {string[]} */
|
|
110
|
-
const keep = [path.basename(appUrl)];
|
|
111
|
-
|
|
112
|
-
for (const file of listPageStyles(pagesDir)) {
|
|
113
|
-
const name = path.basename(file);
|
|
114
|
-
const css = await compile(file);
|
|
115
|
-
const url = writeAsset(name, css);
|
|
116
|
-
urls[name] = url;
|
|
117
|
-
bytes += Buffer.byteLength(css);
|
|
118
|
-
keep.push(path.basename(url));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Önce yaz, sonra eski hash'leri sil — aynı hash'e düşen içerikte 404
|
|
122
|
-
// penceresi olmasın (CDN immutable zehirlenmesi). Silinen sayfa sheet'leri
|
|
123
|
-
// de burada gider; önek listesi tutmaya gerek yok.
|
|
124
|
-
pruneCssOutputs(keep);
|
|
125
|
-
|
|
126
|
-
return { urls, bytes, elapsed: Date.now() - started };
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const first = await run();
|
|
130
|
-
const pageCount = Object.keys(first.urls).length - 1;
|
|
131
|
-
log.detail(
|
|
132
|
-
pageCount > 0
|
|
133
|
-
? `${log.size(first.bytes)}, ${pageCount} page ${pageCount === 1 ? "sheet" : "sheets"}`
|
|
134
|
-
: log.size(first.bytes),
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
if (watch) {
|
|
138
|
-
watchCssSources(config, async () => {
|
|
139
|
-
try {
|
|
140
|
-
const result = await run();
|
|
141
|
-
// Yeni hash manifest'e yazılmazsa HTML silinmiş dosyayı ister.
|
|
142
|
-
// Silinen sayfa sheet anahtarları da düşsün.
|
|
143
|
-
syncCssManifest(result.urls);
|
|
144
|
-
log.event({
|
|
145
|
-
scope: "css",
|
|
146
|
-
message: "rebuilt",
|
|
147
|
-
note: log.size(result.bytes),
|
|
148
|
-
time: result.elapsed,
|
|
149
|
-
});
|
|
150
|
-
} catch (error) {
|
|
151
|
-
log.event({
|
|
152
|
-
symbol: log.symbols.fail,
|
|
153
|
-
scope: "css",
|
|
154
|
-
message: "failed",
|
|
155
|
-
note: error.message,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return first.urls;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* `public/assets/` kökündeki stylesheet çıktıları. `js/` ve `img/` altındakilere
|
|
166
|
-
* dokunulmaz.
|
|
167
|
-
*
|
|
168
|
-
* @param {string[]} keep Yeni yazılan dosya adları (`app.<hash>.css`, …)
|
|
169
|
-
*/
|
|
170
|
-
function pruneCssOutputs(keep) {
|
|
171
|
-
if (!fs.existsSync(paths.assets)) return;
|
|
172
|
-
|
|
173
|
-
for (const file of fs.readdirSync(paths.assets)) {
|
|
174
|
-
if (!/\.css(?:\.(?:br|gz))?$/i.test(file)) continue;
|
|
175
|
-
if (keep.some((name) => file === name || file.startsWith(`${name}.`))) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
fs.rmSync(path.join(paths.assets, file), { force: true });
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Watch turunda CSS anahtarlarını toplu günceller; artık üretilmeyen sayfa
|
|
184
|
-
* sheet'lerini manifest'ten siler.
|
|
185
|
-
*
|
|
186
|
-
* @param {Record<string, string>} urls
|
|
187
|
-
*/
|
|
188
|
-
function syncCssManifest(urls) {
|
|
189
|
-
const file = path.join(paths.generated, "manifest.json");
|
|
190
|
-
/** @type {Record<string, string>} */
|
|
191
|
-
let current = {};
|
|
192
|
-
try {
|
|
193
|
-
current = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
194
|
-
} catch {
|
|
195
|
-
// İlk yazımda oluşur.
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** @type {Record<string, string>} */
|
|
199
|
-
const next = {};
|
|
200
|
-
for (const [key, value] of Object.entries(current)) {
|
|
201
|
-
if (key.endsWith(".css") && !(key in urls)) continue;
|
|
202
|
-
next[key] = value;
|
|
203
|
-
}
|
|
204
|
-
for (const [key, url] of Object.entries(urls)) {
|
|
205
|
-
next[key] = url;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (JSON.stringify(current) === JSON.stringify(next)) return;
|
|
209
|
-
writeManifest(next);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Şablon ve island dosyaları da izlenir: Tailwind sınıfları oradan geliyor,
|
|
214
|
-
* yalnızca `styles/` izlemek yeni bir utility yazıldığında rebuild etmez.
|
|
215
|
-
* `styles/pages` zaten `dirname(styles)` altında.
|
|
216
|
-
*
|
|
217
|
-
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
218
|
-
* @param {() => void} onChange
|
|
219
|
-
*/
|
|
220
|
-
function watchCssSources(config, onChange) {
|
|
221
|
-
const targets = [
|
|
222
|
-
path.dirname(config.dirs.styles),
|
|
223
|
-
config.dirs.views,
|
|
224
|
-
config.dirs.client,
|
|
225
|
-
];
|
|
226
|
-
|
|
227
|
-
let timer = null;
|
|
228
|
-
for (const target of targets) {
|
|
229
|
-
if (!fs.existsSync(target)) continue;
|
|
230
|
-
fs.watch(target, { recursive: true }, () => {
|
|
231
|
-
clearTimeout(timer);
|
|
232
|
-
timer = setTimeout(onChange, 120);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind v4 → global `app.css` + isteğe bağlı sayfa stylesheet'leri.
|
|
3
|
+
*
|
|
4
|
+
* Global sheet her sayfada yüklenir (`paths.styles`). Sayfa sheet'leri
|
|
5
|
+
* `styles/pages/*.css` altındadır ve yalnızca controller `styles: ["home.css"]`
|
|
6
|
+
* bildiren sayfalarda basılır — island `entries` ile aynı sözleşme.
|
|
7
|
+
*
|
|
8
|
+
* Ayrı bir "critical CSS" üretilmez. Ölçümde inline kritik CSS ilk ekranı tam
|
|
9
|
+
* kapsamadığı için sheet gelince sayfa yeniden akıyordu (bir liste sayfasında
|
|
10
|
+
* CLS 0.307) ve aynı ~27 KB her HTML yanıtında tekrar ediyordu. Sıkıştırılmış
|
|
11
|
+
* global sheet'i render-blocking bırakmak hem daha hızlı hem CLS'siz; ikinci
|
|
12
|
+
* ziyarette zaten immutable önbellekten geliyor. Sayfa sheet'leri de
|
|
13
|
+
* render-blocking; yalnızca ilgili sayfada ek maliyet.
|
|
14
|
+
*
|
|
15
|
+
* Tailwind utility'leri global sheet'te kalmalı. Sayfa CSS'inde tam
|
|
16
|
+
* `@import "tailwindcss"` utility çıktısını tekrarlar; sayfaya özel kurallar
|
|
17
|
+
* yeter.
|
|
18
|
+
*
|
|
19
|
+
* Tailwind'in sınıf taraması `globals.css` içindeki `@source` direktiflerine
|
|
20
|
+
* bağlıdır. Otomatik tespit yalnızca stylesheet'in bulunduğu dizini tarar,
|
|
21
|
+
* bu yüzden şablonlarda geçen varyantlar (`data-[active=false]:…`) sessizce
|
|
22
|
+
* düşer. Yeni bir üst dizin eklediğinizde `@source` satırını da ekleyin.
|
|
23
|
+
*/
|
|
24
|
+
import fs from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { paths, writeAsset, writeManifest } from "../paths.mjs";
|
|
27
|
+
import { importFromApp, tryImportFromApp } from "../resolve-peer.mjs";
|
|
28
|
+
import * as log from "../../log.mjs";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* PostCSS boru hattı bir kez kurulur: Tailwind'in kendi önbelleği plugin-++
|
|
32
|
+
* örneğinde yaşıyor, her derlemede yeniden oluşturmak watch turlarını
|
|
33
|
+
* belirgin şekilde yavaşlatıyor.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} root
|
|
36
|
+
* @returns {Promise<(inputPath: string) => Promise<string>>}
|
|
37
|
+
*/
|
|
38
|
+
async function createCompiler(root) {
|
|
39
|
+
const [{ default: postcss }, { default: tailwindcss }] = await Promise.all([
|
|
40
|
+
importFromApp(root, "postcss"),
|
|
41
|
+
importFromApp(root, "@tailwindcss/postcss"),
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// lightningcss opsiyonel: yoksa Tailwind'in kendi çıktısı kullanılır,
|
|
45
|
+
// yalnızca birkaç kB daha büyük olur.
|
|
46
|
+
const lightning = await tryImportFromApp(root, "lightningcss");
|
|
47
|
+
const processor = postcss([tailwindcss()]);
|
|
48
|
+
|
|
49
|
+
return async (inputPath) => {
|
|
50
|
+
const css = fs.readFileSync(inputPath, "utf8");
|
|
51
|
+
const result = await processor.process(css, { from: inputPath, to: inputPath });
|
|
52
|
+
|
|
53
|
+
if (!lightning) return result.css;
|
|
54
|
+
|
|
55
|
+
const minified = lightning.transform({
|
|
56
|
+
filename: path.basename(inputPath),
|
|
57
|
+
code: Buffer.from(result.css),
|
|
58
|
+
minify: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return minified.code.toString("utf8");
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `paths.styles` dosyasının yanında `pages/` — config'te ayrı alan yok.
|
|
67
|
+
*
|
|
68
|
+
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
function pageStylesDir(config) {
|
|
72
|
+
return path.join(path.dirname(config.dirs.styles), "pages");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} dir
|
|
77
|
+
* @returns {string[]} Mutlak yollar, dosya adına göre sıralı.
|
|
78
|
+
*/
|
|
79
|
+
function listPageStyles(dir) {
|
|
80
|
+
if (!fs.existsSync(dir)) return [];
|
|
81
|
+
return fs
|
|
82
|
+
.readdirSync(dir)
|
|
83
|
+
.filter((name) => name.endsWith(".css"))
|
|
84
|
+
.sort()
|
|
85
|
+
.map((name) => path.join(dir, name));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
90
|
+
* @param {{ watch?: boolean }} [options]
|
|
91
|
+
* @returns {Promise<Record<string, string>>}
|
|
92
|
+
*/
|
|
93
|
+
export async function buildCss(config, { watch = false } = {}) {
|
|
94
|
+
const input = config.dirs.styles;
|
|
95
|
+
const pagesDir = pageStylesDir(config);
|
|
96
|
+
const compile = await createCompiler(config.root);
|
|
97
|
+
|
|
98
|
+
const run = async () => {
|
|
99
|
+
const started = Date.now();
|
|
100
|
+
/** @type {Record<string, string>} */
|
|
101
|
+
const urls = {};
|
|
102
|
+
let bytes = 0;
|
|
103
|
+
|
|
104
|
+
const appCss = await compile(input);
|
|
105
|
+
const appUrl = writeAsset("app.css", appCss);
|
|
106
|
+
urls["app.css"] = appUrl;
|
|
107
|
+
bytes += Buffer.byteLength(appCss);
|
|
108
|
+
|
|
109
|
+
/** @type {string[]} */
|
|
110
|
+
const keep = [path.basename(appUrl)];
|
|
111
|
+
|
|
112
|
+
for (const file of listPageStyles(pagesDir)) {
|
|
113
|
+
const name = path.basename(file);
|
|
114
|
+
const css = await compile(file);
|
|
115
|
+
const url = writeAsset(name, css);
|
|
116
|
+
urls[name] = url;
|
|
117
|
+
bytes += Buffer.byteLength(css);
|
|
118
|
+
keep.push(path.basename(url));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Önce yaz, sonra eski hash'leri sil — aynı hash'e düşen içerikte 404
|
|
122
|
+
// penceresi olmasın (CDN immutable zehirlenmesi). Silinen sayfa sheet'leri
|
|
123
|
+
// de burada gider; önek listesi tutmaya gerek yok.
|
|
124
|
+
pruneCssOutputs(keep);
|
|
125
|
+
|
|
126
|
+
return { urls, bytes, elapsed: Date.now() - started };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const first = await run();
|
|
130
|
+
const pageCount = Object.keys(first.urls).length - 1;
|
|
131
|
+
log.detail(
|
|
132
|
+
pageCount > 0
|
|
133
|
+
? `${log.size(first.bytes)}, ${pageCount} page ${pageCount === 1 ? "sheet" : "sheets"}`
|
|
134
|
+
: log.size(first.bytes),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (watch) {
|
|
138
|
+
watchCssSources(config, async () => {
|
|
139
|
+
try {
|
|
140
|
+
const result = await run();
|
|
141
|
+
// Yeni hash manifest'e yazılmazsa HTML silinmiş dosyayı ister.
|
|
142
|
+
// Silinen sayfa sheet anahtarları da düşsün.
|
|
143
|
+
syncCssManifest(result.urls);
|
|
144
|
+
log.event({
|
|
145
|
+
scope: "css",
|
|
146
|
+
message: "rebuilt",
|
|
147
|
+
note: log.size(result.bytes),
|
|
148
|
+
time: result.elapsed,
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
log.event({
|
|
152
|
+
symbol: log.symbols.fail,
|
|
153
|
+
scope: "css",
|
|
154
|
+
message: "failed",
|
|
155
|
+
note: error.message,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return first.urls;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* `public/assets/` kökündeki stylesheet çıktıları. `js/` ve `img/` altındakilere
|
|
166
|
+
* dokunulmaz.
|
|
167
|
+
*
|
|
168
|
+
* @param {string[]} keep Yeni yazılan dosya adları (`app.<hash>.css`, …)
|
|
169
|
+
*/
|
|
170
|
+
function pruneCssOutputs(keep) {
|
|
171
|
+
if (!fs.existsSync(paths.assets)) return;
|
|
172
|
+
|
|
173
|
+
for (const file of fs.readdirSync(paths.assets)) {
|
|
174
|
+
if (!/\.css(?:\.(?:br|gz))?$/i.test(file)) continue;
|
|
175
|
+
if (keep.some((name) => file === name || file.startsWith(`${name}.`))) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
fs.rmSync(path.join(paths.assets, file), { force: true });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Watch turunda CSS anahtarlarını toplu günceller; artık üretilmeyen sayfa
|
|
184
|
+
* sheet'lerini manifest'ten siler.
|
|
185
|
+
*
|
|
186
|
+
* @param {Record<string, string>} urls
|
|
187
|
+
*/
|
|
188
|
+
function syncCssManifest(urls) {
|
|
189
|
+
const file = path.join(paths.generated, "manifest.json");
|
|
190
|
+
/** @type {Record<string, string>} */
|
|
191
|
+
let current = {};
|
|
192
|
+
try {
|
|
193
|
+
current = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
194
|
+
} catch {
|
|
195
|
+
// İlk yazımda oluşur.
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** @type {Record<string, string>} */
|
|
199
|
+
const next = {};
|
|
200
|
+
for (const [key, value] of Object.entries(current)) {
|
|
201
|
+
if (key.endsWith(".css") && !(key in urls)) continue;
|
|
202
|
+
next[key] = value;
|
|
203
|
+
}
|
|
204
|
+
for (const [key, url] of Object.entries(urls)) {
|
|
205
|
+
next[key] = url;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (JSON.stringify(current) === JSON.stringify(next)) return;
|
|
209
|
+
writeManifest(next);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Şablon ve island dosyaları da izlenir: Tailwind sınıfları oradan geliyor,
|
|
214
|
+
* yalnızca `styles/` izlemek yeni bir utility yazıldığında rebuild etmez.
|
|
215
|
+
* `styles/pages` zaten `dirname(styles)` altında.
|
|
216
|
+
*
|
|
217
|
+
* @param {import('../../config/index.js').ResolvedConfig} config
|
|
218
|
+
* @param {() => void} onChange
|
|
219
|
+
*/
|
|
220
|
+
function watchCssSources(config, onChange) {
|
|
221
|
+
const targets = [
|
|
222
|
+
path.dirname(config.dirs.styles),
|
|
223
|
+
config.dirs.views,
|
|
224
|
+
config.dirs.client,
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
let timer = null;
|
|
228
|
+
for (const target of targets) {
|
|
229
|
+
if (!fs.existsSync(target)) continue;
|
|
230
|
+
fs.watch(target, { recursive: true }, () => {
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
timer = setTimeout(onChange, 120);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|