jskelet 0.1.1

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 (72) hide show
  1. package/AGENTS.md +127 -0
  2. package/CHANGELOG.md +40 -0
  3. package/LICENSE +21 -0
  4. package/README.md +342 -0
  5. package/bin/jskelet.mjs +104 -0
  6. package/docs/01-baslangic.md +285 -0
  7. package/docs/02-mimari.md +287 -0
  8. package/docs/03-routing.md +437 -0
  9. package/docs/04-render-ve-sablonlar.md +490 -0
  10. package/docs/05-islands.md +429 -0
  11. package/docs/06-cache.md +409 -0
  12. package/docs/07-yapilandirma.md +673 -0
  13. package/docs/08-build.md +366 -0
  14. package/docs/09-dev-araclari.md +302 -0
  15. package/docs/10-dagitim.md +329 -0
  16. package/docs/11-tasima.md +352 -0
  17. package/docs/README.md +82 -0
  18. package/package.json +97 -0
  19. package/src/build/build.mjs +138 -0
  20. package/src/build/ensure-build.mjs +15 -0
  21. package/src/build/paths.mjs +118 -0
  22. package/src/build/resolve-peer.mjs +36 -0
  23. package/src/build/tasks/client.mjs +268 -0
  24. package/src/build/tasks/css.mjs +124 -0
  25. package/src/build/tasks/fonts.mjs +146 -0
  26. package/src/build/tasks/icons.mjs +224 -0
  27. package/src/build/tasks/images.mjs +244 -0
  28. package/src/build/tasks/precompress.mjs +78 -0
  29. package/src/client/devtools/overlay.js +1763 -0
  30. package/src/client/devtools/report.html +185 -0
  31. package/src/client/devtools/report.js +712 -0
  32. package/src/client/dom.js +95 -0
  33. package/src/client/index.js +26 -0
  34. package/src/client/registry.js +223 -0
  35. package/src/client/safe-image.js +91 -0
  36. package/src/client/store.js +36 -0
  37. package/src/config/defaults.js +102 -0
  38. package/src/config/index.js +433 -0
  39. package/src/config/pattern.js +107 -0
  40. package/src/dev-server.mjs +383 -0
  41. package/src/http/control-flow.js +56 -0
  42. package/src/http/request-cache.js +46 -0
  43. package/src/index.js +35 -0
  44. package/src/init.mjs +220 -0
  45. package/src/log.mjs +332 -0
  46. package/src/logo.png +0 -0
  47. package/src/runtime/alias-hooks.mjs +119 -0
  48. package/src/runtime/register.mjs +4 -0
  49. package/src/server/assets.js +119 -0
  50. package/src/server/create-app.js +167 -0
  51. package/src/server/dev/devtools.js +383 -0
  52. package/src/server/dev/report.js +351 -0
  53. package/src/server/head-hints.js +132 -0
  54. package/src/server/html-cache.js +166 -0
  55. package/src/server/metadata.js +102 -0
  56. package/src/server/middleware/compression.js +205 -0
  57. package/src/server/middleware/dev-gate.js +62 -0
  58. package/src/server/middleware/headers.js +37 -0
  59. package/src/server/middleware/redirects.js +32 -0
  60. package/src/server/middleware/static-precompressed.js +100 -0
  61. package/src/server/middleware/upstream-proxy.js +141 -0
  62. package/src/server/prewarm.js +283 -0
  63. package/src/server/render.js +356 -0
  64. package/src/server/router.js +121 -0
  65. package/src/server/status-page.js +164 -0
  66. package/src/server/upstream-tracking.js +51 -0
  67. package/src/start.mjs +7 -0
  68. package/src/templates/layout.ejs +44 -0
  69. package/src/version.mjs +17 -0
  70. package/src/views/components/loader.js +85 -0
  71. package/src/views/helpers/html.js +102 -0
  72. package/src/views/helpers/tags.js +193 -0
@@ -0,0 +1,95 @@
1
+ /** Island'ların paylaştığı küçük DOM yardımcıları. */
2
+
3
+ /**
4
+ * @param {ParentNode} root
5
+ * @param {string} selector
6
+ * @returns {HTMLElement | null}
7
+ */
8
+ export function qs(root, selector) {
9
+ return /** @type {HTMLElement | null} */ (root.querySelector(selector));
10
+ }
11
+
12
+ /**
13
+ * @param {ParentNode} root
14
+ * @param {string} selector
15
+ * @returns {HTMLElement[]}
16
+ */
17
+ export function qsa(root, selector) {
18
+ return /** @type {HTMLElement[]} */ ([...root.querySelectorAll(selector)]);
19
+ }
20
+
21
+ /**
22
+ * Otomatik temizlenebilir listener.
23
+ * @param {EventTarget} target
24
+ * @param {string} type
25
+ * @param {EventListenerOrEventListenerObject} handler
26
+ * @param {AddEventListenerOptions | boolean} [options]
27
+ * @returns {() => void}
28
+ */
29
+ export function on(target, type, handler, options) {
30
+ target.addEventListener(type, handler, options);
31
+ return () => target.removeEventListener(type, handler, options);
32
+ }
33
+
34
+ /**
35
+ * `data-*` üzerinden delege edilmiş click.
36
+ * @param {HTMLElement} root
37
+ * @param {string} selector
38
+ * @param {(event: MouseEvent, target: HTMLElement) => void} handler
39
+ * @returns {() => void}
40
+ */
41
+ export function onClick(root, selector, handler) {
42
+ return on(root, "click", (event) => {
43
+ const target = /** @type {HTMLElement} */ (event.target)?.closest(selector);
44
+ if (!target || !root.contains(target)) return;
45
+ handler(/** @type {MouseEvent} */ (event), /** @type {HTMLElement} */ (target));
46
+ });
47
+ }
48
+
49
+ /**
50
+ * @param {number} ms
51
+ * @param {(...args: any[]) => void} fn
52
+ * @returns {(...args: any[]) => void}
53
+ */
54
+ export function debounce(ms, fn) {
55
+ let timer = null;
56
+ return (...args) => {
57
+ clearTimeout(timer);
58
+ timer = setTimeout(() => fn(...args), ms);
59
+ };
60
+ }
61
+
62
+ /**
63
+ * requestAnimationFrame ile birleştirilmiş çağrı.
64
+ * @param {() => void} fn
65
+ * @returns {() => void}
66
+ */
67
+ export function raf(fn) {
68
+ let frame = 0;
69
+ return () => {
70
+ if (frame) return;
71
+ frame = requestAnimationFrame(() => {
72
+ frame = 0;
73
+ fn();
74
+ });
75
+ };
76
+ }
77
+
78
+ /**
79
+ * @param {HTMLElement} element
80
+ * @param {string} name
81
+ * @param {boolean} active
82
+ */
83
+ export function toggleClass(element, name, active) {
84
+ element.classList.toggle(name, active);
85
+ }
86
+
87
+ /**
88
+ * Overlay portal hedefi: layout'ta `<div id="jskelet-overlays">` varsa
89
+ * modal/drawer içeriği oraya taşınır, yoksa `body`ye. Portal, `overflow`
90
+ * ya da `transform` taşıyan bir ata elementin `position: fixed` overlay'i
91
+ * kırpmasını engeller.
92
+ */
93
+ export function getOverlayRoot() {
94
+ return document.getElementById("jskelet-overlays") ?? document.body;
95
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * JSkelet island runtime'ının tarayıcı tarafı API'si.
3
+ *
4
+ * `client/entries/main.js` içinde:
5
+ *
6
+ * import { registerAll, start } from "jskelet/client";
7
+ *
8
+ * registerAll({
9
+ * counter: () => import("../islands/counter.js"),
10
+ * });
11
+ *
12
+ * start();
13
+ */
14
+ export { register, registerAll, hydrate, observeDocument, start } from "./registry.js";
15
+ export { createStore } from "./store.js";
16
+ export {
17
+ debounce,
18
+ on,
19
+ onClick,
20
+ qs,
21
+ qsa,
22
+ raf,
23
+ toggleClass,
24
+ getOverlayRoot,
25
+ } from "./dom.js";
26
+ export { startSafeImages } from "./safe-image.js";
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Island registry.
3
+ *
4
+ * Sunucu HTML'i tamdır; JS yalnızca davranış ekler. Bir element
5
+ * `data-island="ad"` taşıdığında ilgili modül dinamik import edilir ve
6
+ * `mount(element, props)` çağrılır.
7
+ *
8
+ * Hidrasyon **varsayılan olarak görünürlüğe bağlıdır**: her island bir
9
+ * IntersectionObserver'a verilir, ekranda olanlar zaten ilk gözlemde tetiklenir,
10
+ * ekran dışındakiler kaydırılana kadar hiç indirilmez. Ana sayfadaki grafik
11
+ * kütüphanesi gibi ağır modüller böylece ilk yükten tamamen çıkar.
12
+ *
13
+ * `data-island-eager` görünürlükten bağımsız, hemen bağlanmayı zorlar
14
+ * (header, çerez bandı gibi global davranışlar için).
15
+ *
16
+ * `data-island-idle` görünür olsa bile sayfa sakinleşene (load + boş zaman)
17
+ * kadar bekletir. İlk ekranda görünen ama kritik olmayan ağır modüller
18
+ * (ör. grafik kütüphanesi çeken mini grafik) LCP ile yarışmasın diye.
19
+ *
20
+ * `data-island-props` JSON ise parse edilip props olarak geçer.
21
+ */
22
+
23
+ /** @typedef {(element: HTMLElement, props: object) => void | (() => void)} MountFn */
24
+
25
+ /** @type {Map<string, () => Promise<{ mount: MountFn }>>} */
26
+ const loaders = new Map();
27
+
28
+ /** @type {WeakMap<HTMLElement, Set<string>>} */
29
+ const mounted = new WeakMap();
30
+
31
+ /**
32
+ * @param {string} name
33
+ * @param {() => Promise<{ mount: MountFn }>} loader
34
+ */
35
+ export function register(name, loader) {
36
+ loaders.set(name, loader);
37
+ }
38
+
39
+ /** @param {Record<string, () => Promise<{ mount: MountFn }>>} entries */
40
+ export function registerAll(entries) {
41
+ for (const [name, loader] of Object.entries(entries)) {
42
+ register(name, loader);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * @param {HTMLElement} element
48
+ * @returns {object}
49
+ */
50
+ function readProps(element) {
51
+ const raw = element.dataset.islandProps;
52
+ if (!raw) return {};
53
+
54
+ try {
55
+ return JSON.parse(raw);
56
+ } catch (error) {
57
+ console.error(
58
+ `[island] ${element.dataset.island}: geçersiz data-island-props`,
59
+ error,
60
+ );
61
+ return {};
62
+ }
63
+ }
64
+
65
+ /**
66
+ * @param {HTMLElement} element
67
+ * @returns {boolean}
68
+ */
69
+ function isMounted(element) {
70
+ const name = element.dataset.island;
71
+ if (!name) return true;
72
+ return mounted.get(element)?.has(name) ?? false;
73
+ }
74
+
75
+ /** @param {HTMLElement} element */
76
+ function markMounted(element) {
77
+ const name = element.dataset.island;
78
+ if (!name) return;
79
+
80
+ let names = mounted.get(element);
81
+ if (!names) {
82
+ names = new Set();
83
+ mounted.set(element, names);
84
+ }
85
+ names.add(name);
86
+ }
87
+
88
+ /** @param {HTMLElement} element */
89
+ async function mountIsland(element) {
90
+ const name = element.dataset.island;
91
+ if (!name || isMounted(element)) return;
92
+
93
+ const loader = loaders.get(name);
94
+ if (!loader) {
95
+ console.warn(`[island] kayıtlı değil: ${name}`);
96
+ return;
97
+ }
98
+
99
+ markMounted(element);
100
+
101
+ try {
102
+ const module = await loader();
103
+ module.mount(element, readProps(element));
104
+ element.dataset.islandReady = "true";
105
+ } catch (error) {
106
+ console.error(`[island] ${name} yüklenemedi`, error);
107
+ }
108
+ }
109
+
110
+ /** @type {IntersectionObserver | null} */
111
+ let lazyObserver = null;
112
+
113
+ function getLazyObserver() {
114
+ if (lazyObserver) return lazyObserver;
115
+
116
+ lazyObserver = new IntersectionObserver(
117
+ (entries) => {
118
+ for (const entry of entries) {
119
+ if (!entry.isIntersecting) continue;
120
+ lazyObserver.unobserve(entry.target);
121
+ mountWhenReady(/** @type {HTMLElement} */ (entry.target));
122
+ }
123
+ },
124
+ { rootMargin: "200px 0px" },
125
+ );
126
+
127
+ return lazyObserver;
128
+ }
129
+
130
+ /**
131
+ * Bağlama işini boş zamana kaydır: aynı anda görünen çok sayıda island'ın
132
+ * tek bir uzun task'a dönüşmesini engeller (TBT/INP).
133
+ *
134
+ * @param {() => void} fn
135
+ */
136
+ function schedule(fn) {
137
+ if (typeof requestIdleCallback === "function") {
138
+ requestIdleCallback(fn, { timeout: 500 });
139
+ return;
140
+ }
141
+ setTimeout(fn, 0);
142
+ }
143
+
144
+ /**
145
+ * `load` tamamlanıp ana iş parçacığı boşalana kadar bekletir.
146
+ *
147
+ * @param {() => void} fn
148
+ */
149
+ function afterLoad(fn) {
150
+ if (document.readyState === "complete") {
151
+ schedule(fn);
152
+ return;
153
+ }
154
+ window.addEventListener("load", () => schedule(fn), { once: true });
155
+ }
156
+
157
+ /**
158
+ * @param {HTMLElement} node
159
+ */
160
+ function mountWhenReady(node) {
161
+ if ("islandIdle" in node.dataset) {
162
+ afterLoad(() => mountIsland(node));
163
+ return;
164
+ }
165
+ schedule(() => mountIsland(node));
166
+ }
167
+
168
+ /** @param {ParentNode} [root] */
169
+ export function hydrate(root = document) {
170
+ /** @type {HTMLElement[]} */
171
+ const pending = [];
172
+
173
+ for (const element of root.querySelectorAll("[data-island]")) {
174
+ const node = /** @type {HTMLElement} */ (element);
175
+ if (!isMounted(node)) pending.push(node);
176
+ }
177
+
178
+ if (!pending.length) return;
179
+
180
+ // Ölçümler tek seferde okunur; araya yazma girmediği için tek reflow olur.
181
+ const boxed = pending.map((node) => node.getClientRects().length > 0);
182
+
183
+ pending.forEach((node, i) => {
184
+ // `hidden` bir drawer/dialog'un düzen kutusu yoktur ve IntersectionObserver
185
+ // onu asla bildirmez; bu yüzden gizli olanlar doğrudan bağlanır.
186
+ if ("islandEager" in node.dataset || !boxed[i]) {
187
+ mountIsland(node);
188
+ return;
189
+ }
190
+
191
+ getLazyObserver().observe(node);
192
+ });
193
+ }
194
+
195
+ /** Sonradan DOM'a eklenen island'ları da yakalar (infinite scroll, portal). */
196
+ export function observeDocument() {
197
+ const observer = new MutationObserver((mutations) => {
198
+ for (const mutation of mutations) {
199
+ for (const node of mutation.addedNodes) {
200
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
201
+ const element = /** @type {HTMLElement} */ (node);
202
+ if (element.dataset?.island) hydrate(element.parentNode ?? document);
203
+ if (element.querySelector?.("[data-island]")) hydrate(element);
204
+ }
205
+ }
206
+ });
207
+
208
+ observer.observe(document.body, { childList: true, subtree: true });
209
+ return observer;
210
+ }
211
+
212
+ export function start() {
213
+ const run = () => {
214
+ hydrate();
215
+ observeDocument();
216
+ };
217
+
218
+ if (document.readyState === "loading") {
219
+ document.addEventListener("DOMContentLoaded", run, { once: true });
220
+ } else {
221
+ run();
222
+ }
223
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Yüklenemeyen görseller için tek bir belge dinleyicisi.
3
+ *
4
+ * Bu iş bilinçli olarak **island değil**. Görsel ağırlıklı bir sayfada 80+
5
+ * `<img>` olabiliyor ve her birine ayrı island bağlamak (gözlemci + dinamik
6
+ * import + mount) sırf hata ihtimali için ciddi bir hidrasyon yükü. `error`
7
+ * olayı kabarmaz ama yakalama fazında görülebilir, bu yüzden tek dinleyici
8
+ * hepsini karşılar ve sonradan DOM'a eklenen görseller de kendiliğinden
9
+ * kapsanır.
10
+ *
11
+ * Kullanım: görsele `data-safe-image` ekleyin. Kendi hata görünümünüzü
12
+ * vermek için görseli `data-safe-image-host` taşıyan bir sarmalayıcıya alın
13
+ * ve içine `<template data-safe-image-fallback>` koyun — framework hiçbir
14
+ * stil dayatmaz, yalnızca değiştirme işini yapar.
15
+ */
16
+
17
+ const MARKER = "data-safe-image";
18
+
19
+ /**
20
+ * Şablon verilmemişse yerine geçen minimal blok: görselin ölçülerini korur
21
+ * ki değiştirme sırasında düzen kaymasın (CLS).
22
+ *
23
+ * @param {HTMLImageElement} img
24
+ * @returns {HTMLElement}
25
+ */
26
+ function buildFallback(img) {
27
+ const element = document.createElement("div");
28
+ const label = img.dataset.fallbackLabel || img.getAttribute("alt") || "";
29
+
30
+ element.className = [img.className, img.dataset.fallbackClass ?? ""]
31
+ .filter(Boolean)
32
+ .join(" ");
33
+ element.dataset.safeImageFallback = "";
34
+
35
+ if (img.width && img.height) {
36
+ element.style.width = `${img.width}px`;
37
+ element.style.height = `${img.height}px`;
38
+ }
39
+
40
+ element.setAttribute("role", "img");
41
+ if (label) element.setAttribute("aria-label", label);
42
+
43
+ return element;
44
+ }
45
+
46
+ /**
47
+ * @param {HTMLImageElement} img
48
+ */
49
+ function replace(img) {
50
+ if (!img.isConnected) return;
51
+
52
+ const host = img.closest("[data-safe-image-host]");
53
+ const template = host?.querySelector("template[data-safe-image-fallback]");
54
+
55
+ if (host && template) {
56
+ host.replaceWith(template.content.cloneNode(true));
57
+ return;
58
+ }
59
+
60
+ img.replaceWith(buildFallback(img));
61
+ }
62
+
63
+ /**
64
+ * @returns {void}
65
+ */
66
+ export function startSafeImages() {
67
+ document.addEventListener(
68
+ "error",
69
+ (event) => {
70
+ const target = /** @type {HTMLElement} */ (event.target);
71
+ if (target?.tagName !== "IMG" || !target.hasAttribute(MARKER)) return;
72
+ replace(/** @type {HTMLImageElement} */ (target));
73
+ },
74
+ // `error` kabarmaz; yalnızca yakalama fazında görülür.
75
+ true,
76
+ );
77
+
78
+ // JS çalışmadan önce başarısız olmuş görseller olay üretmez; bir kez taranır.
79
+ const sweep = () => {
80
+ for (const img of document.querySelectorAll(`img[${MARKER}]`)) {
81
+ const image = /** @type {HTMLImageElement} */ (img);
82
+ if (image.complete && image.naturalWidth === 0) replace(image);
83
+ }
84
+ };
85
+
86
+ if (typeof requestIdleCallback === "function") {
87
+ requestIdleCallback(sweep, { timeout: 2000 });
88
+ } else {
89
+ setTimeout(sweep, 0);
90
+ }
91
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * React Context yerine kullanılan minimal pub/sub store.
3
+ * `useSyncExternalStore` köprüsünün yerini alır: doğrudan `subscribe`.
4
+ */
5
+
6
+ /**
7
+ * @param {T} initial
8
+ * @returns {{
9
+ * get: () => T,
10
+ * set: (next: T | ((prev: T) => T)) => void,
11
+ * subscribe: (listener: (value: T) => void) => () => void,
12
+ * }}
13
+ * @template T
14
+ */
15
+ export function createStore(initial) {
16
+ let value = initial;
17
+ /** @type {Set<(value: T) => void>} */
18
+ const listeners = new Set();
19
+
20
+ return {
21
+ get: () => value,
22
+
23
+ set(next) {
24
+ const resolved =
25
+ typeof next === "function" ? /** @type {Function} */ (next)(value) : next;
26
+ if (resolved === value) return;
27
+ value = resolved;
28
+ for (const listener of listeners) listener(value);
29
+ },
30
+
31
+ subscribe(listener) {
32
+ listeners.add(listener);
33
+ return () => listeners.delete(listener);
34
+ },
35
+ };
36
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Framework varsayılanları.
3
+ *
4
+ * Buradaki hiçbir değer domain bilgisi taşımaz: uygulamaya özel her şey
5
+ * `jskelet.config.mjs` üzerinden gelir. Varsayılanların ayrı dosyada olması,
6
+ * "framework ne yapıyor, uygulama ne ekliyor" ayrımını okunur tutar.
7
+ */
8
+
9
+ /** Uzun süre cache'lenecek hash'li çıktı dizini. */
10
+ export const IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
11
+
12
+ /**
13
+ * Proje kökündeki dizin adları. `jskelet.config.mjs` içinde `paths` ile
14
+ * tek tek ezilebilir.
15
+ */
16
+ export const DEFAULT_DIRS = {
17
+ /** EJS layout + sayfalar + bileşenler. */
18
+ views: "views",
19
+ /** Statik dosyalar; build çıktısı da buraya yazılır. */
20
+ public: "public",
21
+ /** Island runtime ve entry'ler. */
22
+ client: "client",
23
+ /** Route modülleri. */
24
+ routes: "routes",
25
+ /** Tailwind/PostCSS giriş dosyası. */
26
+ styles: "styles/globals.css",
27
+ /** Build ara çıktıları (manifest, metafile, images). */
28
+ generated: ".jskelet",
29
+ };
30
+
31
+ /**
32
+ * Uzantı ve önek bazlı statik dosya tespiti. Bu listeye uyan yollara
33
+ * `IMMUTABLE_CACHE` yazılır.
34
+ */
35
+ export const DEFAULT_STATIC = {
36
+ extensions: [".svg", ".png", ".webp", ".avif", ".ico", ".woff2"],
37
+ prefixes: ["/assets/", "/fonts/"],
38
+ };
39
+
40
+ /** Dev gate'in hiçbir koşulda kapatmadığı yollar. */
41
+ export const DEFAULT_DEV_GATE_BYPASS = [
42
+ "/api/healthcheck",
43
+ "/robots.txt",
44
+ "/sitemap.xml",
45
+ "/site.webmanifest",
46
+ "/favicon.ico",
47
+ ];
48
+
49
+ /** Prewarm ayarlarının kod içi varsayılanları. */
50
+ export const DEFAULT_PREWARM = {
51
+ enabled: true,
52
+ max: 400,
53
+ intervalSeconds: 0,
54
+ /** Paralel ısıtma isteği; dev'de sunucu tek süreç olduğu için düşürülür. */
55
+ concurrency: 4,
56
+ /** İki tur arasında beklenen süre: upstream'e ani yük binmesin. */
57
+ delayMs: 0,
58
+ };
59
+
60
+ /** Oturuma bağlı sayfalar ısıtılmaz; uygulama kendi listesini verebilir. */
61
+ export const DEFAULT_PREWARM_SKIP = ["/api/", "/_fragment/", "/__jskelet/"];
62
+
63
+ /**
64
+ * Site içi gezinme ipuçları (Speculation Rules + view transition).
65
+ *
66
+ * Varsayılan bilinçli olarak ölçülü: `prefetch` açık, çünkü yalnızca belgeyi
67
+ * indirir ve sayfanın JS'ini çalıştırmaz — yanlış tahmin edilse bile tek
68
+ * maliyeti bir istektir. `prerender` kapalı, çünkü hedef sayfanın script'leri
69
+ * gerçekten çalışır; ölçüm kodunu `prerenderingchange` olayına bağlamayan bir
70
+ * uygulamada ziyaret sayıları şişer. `viewTransition` kapalı, çünkü uygulamanın
71
+ * kendi geçiş animasyonlarıyla çakışabilir.
72
+ */
73
+ export const DEFAULT_NAVIGATION = {
74
+ /** `false` ya da eagerness: "conservative" | "moderate" | "eager". */
75
+ prefetch: /** @type {false | "conservative" | "moderate" | "eager"} */ ("moderate"),
76
+ /** @type {false | "conservative" | "moderate" | "eager"} */
77
+ prerender: false,
78
+ /** `@view-transition { navigation: auto }` basılsın mı. */
79
+ viewTransition: false,
80
+ };
81
+
82
+ /**
83
+ * Hiçbir koşulda önden getirilmeyecek yollar. Yan etkisi olan ya da gezinme
84
+ * hedefi olmayan uçların spekülatif istekle tetiklenmesi gerçek bir hata
85
+ * kaynağı; uygulama kendi listesini `navigation.exclude` ile ekler.
86
+ */
87
+ export const DEFAULT_NAVIGATION_EXCLUDE = ["/api/*", "/_fragment/*"];
88
+
89
+ /**
90
+ * Markalama. Header adı ve dev overlay yolu tek yerden değişsin diye
91
+ * config'ten okunur — fork eden proje kendi adını verebilir.
92
+ */
93
+ export const DEFAULT_BRAND = {
94
+ name: "JSkelet",
95
+ /** `<html lang>`; uygulama kendi dilini config'te bildirir. */
96
+ lang: "en",
97
+ poweredBy: "JSkelet",
98
+ cacheHeader: "X-JSkelet-Cache",
99
+ devBasePath: "/__jskelet/dev",
100
+ prewarmUserAgent: "jskelet-prewarm",
101
+ devTokenCookie: "dev_token",
102
+ };