jskelet 0.6.5 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/docs/07-yapilandirma.md +41 -0
- package/docs/08-build.md +22 -2
- package/docs/en/07-configuration.md +41 -0
- package/docs/en/08-build.md +22 -2
- package/package.json +1 -1
- package/src/build/chunk-groups.mjs +331 -0
- package/src/build/tasks/client.mjs +53 -6
- package/src/config/defaults.js +25 -0
- package/src/config/index.js +125 -0
- package/types/config/defaults.d.ts +24 -0
- package/types/config/index.d.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -28,6 +28,11 @@ one is listed under a **Breaking** heading.
|
|
|
28
28
|
|
|
29
29
|
### Added
|
|
30
30
|
|
|
31
|
+
- `build.client` configures esbuild code splitting and chunk groups:
|
|
32
|
+
`splitting`, declarative `groups` (bare package / path / `@/` matchers), and
|
|
33
|
+
a `chunkGroup(id)` hook. Matched modules are co-located into one shared
|
|
34
|
+
chunk (webpack-`manualChunks` trade-off: weaker tree-shaking inside the
|
|
35
|
+
group). The client build step also prints the total JS file count.
|
|
31
36
|
- `ogImage({ fonts })` registers each `ttf` or `otf` (`path` and `family`)
|
|
32
37
|
with fontconfig so card and raw-SVG text use the site's own files. A slim
|
|
33
38
|
image still needs the `fontconfig` package. `woff` / `woff2` are not drawn
|
package/docs/07-yapilandirma.md
CHANGED
|
@@ -91,6 +91,11 @@ export default {
|
|
|
91
91
|
icons: { dir: "icons", scan: ["views", "client", "routes", "lib"] },
|
|
92
92
|
images: { widths: [400, 800, 1200], quality: 78, skip: ["indirmeler"] },
|
|
93
93
|
clientEnv: ["PUBLIC_WS_URL"],
|
|
94
|
+
build: {
|
|
95
|
+
client: {
|
|
96
|
+
groups: { charts: ["chart.js"] },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
94
99
|
|
|
95
100
|
async headers() {
|
|
96
101
|
return [
|
|
@@ -585,6 +590,42 @@ images: {
|
|
|
585
590
|
ayarla `src` / `srcset`'i `/_jskelet/image?url=…&w=96` biçimine çevirir.
|
|
586
591
|
Elle URL kurmak için `remoteImageUrl(src, { width })` (`jskelet`).
|
|
587
592
|
|
|
593
|
+
## `build`
|
|
594
|
+
|
|
595
|
+
**Tip:** `{ client?: { splitting?, groups?, chunkGroup? } }` — **Varsayılan:**
|
|
596
|
+
`{ client: { splitting: true, groups: {}, chunkGroup: null } }`
|
|
597
|
+
|
|
598
|
+
Client JS (esbuild) bundling knobs. `fonts` / `icons` / `images` / `clientEnv`
|
|
599
|
+
hâlâ top-level kalır; bu bölüm yalnızca splitting ve chunk gruplaması içindir.
|
|
600
|
+
|
|
601
|
+
| Alan | Tip | Varsayılan | Anlamı |
|
|
602
|
+
| --- | --- | --- | --- |
|
|
603
|
+
| `client.splitting` | `boolean` | `true` | esbuild code splitting. `false` iken `groups` / `chunkGroup` yok sayılır (bir kez uyarı). |
|
|
604
|
+
| `client.groups` | `Record<string, string[]>` | `{}` | İsim → matcher listesi. Bare paket (`chart.js`), yol öneki (`./client/chrome/`) veya `@/` öneki. |
|
|
605
|
+
| `client.chunkGroup` | `(id: string) => string \| null \| undefined` | `null` | Modül kimliğini (mutlak yol veya bare specifier) grup adına map eder. **`groups` ile çakışınca hook kazanır.** |
|
|
606
|
+
|
|
607
|
+
esbuild native `manualChunks` sunmaz. Framework, gruba düşen import'ları bir
|
|
608
|
+
barrel üzerinden aynı paylaşılan chunk'a çeker. Bedel: grup içi tree-shaking
|
|
609
|
+
zayıflar — bir üyeyi kullanan sayfa grubun geri kalanını da indirebilir
|
|
610
|
+
(webpack `manualChunks` ile aynı takas). Çıktı dosya adı genelde
|
|
611
|
+
`chunks/chunk.<hash>.js` kalır; grup adı config anahtarıdır, dosya adı değil.
|
|
612
|
+
Ayrıntı: [08-build.md](./08-build.md).
|
|
613
|
+
|
|
614
|
+
```js
|
|
615
|
+
build: {
|
|
616
|
+
client: {
|
|
617
|
+
splitting: true,
|
|
618
|
+
groups: {
|
|
619
|
+
charts: ["chart.js", "chartjs-adapter-date-fns"],
|
|
620
|
+
chrome: ["./client/chrome/", "@/client/chrome/"],
|
|
621
|
+
},
|
|
622
|
+
chunkGroup(id) {
|
|
623
|
+
if (id.includes("node_modules/lodash")) return "vendor";
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
```
|
|
628
|
+
|
|
588
629
|
## `clientEnv`
|
|
589
630
|
|
|
590
631
|
**Tip:** `string[]` — **Varsayılan:** `[]`
|
package/docs/08-build.md
CHANGED
|
@@ -175,7 +175,8 @@ esbuild ayarları:
|
|
|
175
175
|
|
|
176
176
|
| Ayar | Değer | Sebebi |
|
|
177
177
|
| --- | --- | --- |
|
|
178
|
-
| `bundle
|
|
178
|
+
| `bundle` | `true` | — |
|
|
179
|
+
| `splitting` | `config.build.client.splitting` (varsayılan `true`) | Ortak modüller paylaşılan chunk'a çıkar; kapatılınca gruplama da düşer |
|
|
179
180
|
| `format` | `esm` | `type="module"` script'ler |
|
|
180
181
|
| `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
182
|
| `minify` | `true` | — |
|
|
@@ -185,7 +186,26 @@ esbuild ayarları:
|
|
|
185
186
|
| `legalComments` | `none` | — |
|
|
186
187
|
|
|
187
188
|
Çıktı `public/assets/js/` altına düşer ve her turda önce temizlenir.
|
|
188
|
-
`browserslist` okunmaz; hedef listesi kod içinde sabittir.
|
|
189
|
+
`browserslist` okunmaz; hedef listesi kod içinde sabittir. Adım satırı entry
|
|
190
|
+
sayısına ek olarak toplam JS dosya sayısını yazar (`1 entry, 48 files`) — çok
|
|
191
|
+
chunk teşhisini kolaylaştırır.
|
|
192
|
+
|
|
193
|
+
### Chunk gruplama (`build.client.groups` / `chunkGroup`)
|
|
194
|
+
|
|
195
|
+
esbuild native `manualChunks` sunmaz. `build.client.groups` ve
|
|
196
|
+
`build.client.chunkGroup` bir plugin ile uygulanır:
|
|
197
|
+
|
|
198
|
+
1. Eşleşen import bir re-export sarmalayıcısına yönlenir.
|
|
199
|
+
2. Sarmalayıcı `jskelet-group:<name>` barrel'ını side-effect olarak çeker.
|
|
200
|
+
3. Barrel gruptaki tüm bilinen üyeleri (bare paketler + yol önekindeki
|
|
201
|
+
`.{js,ts,mts}` dosyaları + resolve sırasında keşfedilenler) statik import
|
|
202
|
+
eder — gruba dokunan entry'ler aynı üye kümesine ulaşır ve tek paylaşılan
|
|
203
|
+
chunk oluşur.
|
|
204
|
+
|
|
205
|
+
**Trade-off:** grup içi tree-shaking zayıflar; bir üyeyi kullanan sayfa grubun
|
|
206
|
+
geri kalanını da indirebilir. Grup adı config anahtarıdır; çıktı dosyası
|
|
207
|
+
çoğunlukla `chunks/chunk.<hash>.js` kalır. Config sözlüğü:
|
|
208
|
+
[07-yapilandirma.md](./07-yapilandirma.md#build).
|
|
189
209
|
|
|
190
210
|
### `@/` alias'ı
|
|
191
211
|
|
|
@@ -95,6 +95,11 @@ export default {
|
|
|
95
95
|
icons: { dir: "icons", scan: ["views", "client", "routes", "lib"] },
|
|
96
96
|
images: { widths: [400, 800, 1200], quality: 78, skip: ["downloads"] },
|
|
97
97
|
clientEnv: ["PUBLIC_WS_URL"],
|
|
98
|
+
build: {
|
|
99
|
+
client: {
|
|
100
|
+
groups: { charts: ["chart.js"] },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
98
103
|
|
|
99
104
|
async headers() {
|
|
100
105
|
return [
|
|
@@ -598,6 +603,42 @@ images: {
|
|
|
598
603
|
rewrites `src` / `srcset` to `/_jskelet/image?url=…&w=96`. To build URLs by
|
|
599
604
|
hand, use `remoteImageUrl(src, { width })` from `jskelet`.
|
|
600
605
|
|
|
606
|
+
## `build`
|
|
607
|
+
|
|
608
|
+
**Type:** `{ client?: { splitting?, groups?, chunkGroup? } }` — **Default:**
|
|
609
|
+
`{ client: { splitting: true, groups: {}, chunkGroup: null } }`
|
|
610
|
+
|
|
611
|
+
Client JS (esbuild) bundling knobs. `fonts` / `icons` / `images` / `clientEnv`
|
|
612
|
+
stay top-level; this section is only for splitting and chunk grouping.
|
|
613
|
+
|
|
614
|
+
| Field | Type | Default | Meaning |
|
|
615
|
+
| --- | --- | --- | --- |
|
|
616
|
+
| `client.splitting` | `boolean` | `true` | esbuild code splitting. When `false`, `groups` / `chunkGroup` are ignored (one warning). |
|
|
617
|
+
| `client.groups` | `Record<string, string[]>` | `{}` | Name → matcher list. Bare package (`chart.js`), path prefix (`./client/chrome/`), or `@/` prefix. |
|
|
618
|
+
| `client.chunkGroup` | `(id: string) => string \| null \| undefined` | `null` | Maps a module id (absolute path or bare specifier) to a group name. **When it conflicts with `groups`, the hook wins.** |
|
|
619
|
+
|
|
620
|
+
esbuild has no native `manualChunks`. The framework pulls imports that land in a
|
|
621
|
+
group into the same shared chunk via a barrel. Trade-off: tree-shaking inside a
|
|
622
|
+
group weakens — a page that uses one member may download the rest of the group
|
|
623
|
+
(same trade-off as webpack `manualChunks`). Output filenames usually stay
|
|
624
|
+
`chunks/chunk.<hash>.js`; the group name is a config key, not the file name.
|
|
625
|
+
Details: [08-build.md](./08-build.md).
|
|
626
|
+
|
|
627
|
+
```js
|
|
628
|
+
build: {
|
|
629
|
+
client: {
|
|
630
|
+
splitting: true,
|
|
631
|
+
groups: {
|
|
632
|
+
charts: ["chart.js", "chartjs-adapter-date-fns"],
|
|
633
|
+
chrome: ["./client/chrome/", "@/client/chrome/"],
|
|
634
|
+
},
|
|
635
|
+
chunkGroup(id) {
|
|
636
|
+
if (id.includes("node_modules/lodash")) return "vendor";
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
```
|
|
641
|
+
|
|
601
642
|
## `clientEnv`
|
|
602
643
|
|
|
603
644
|
**Type:** `string[]` — **Default:** `[]`
|
package/docs/en/08-build.md
CHANGED
|
@@ -183,7 +183,8 @@ esbuild settings:
|
|
|
183
183
|
|
|
184
184
|
| Setting | Value | Reason |
|
|
185
185
|
| --- | --- | --- |
|
|
186
|
-
| `bundle
|
|
186
|
+
| `bundle` | `true` | — |
|
|
187
|
+
| `splitting` | `config.build.client.splitting` (default `true`) | Shared modules move into a shared chunk; turning it off also disables grouping |
|
|
187
188
|
| `format` | `esm` | `type="module"` scripts |
|
|
188
189
|
| `target` | `chrome111`, `edge111`, `firefox111`, `safari16.4` | The lower bound of the ESM + dynamic import + `IntersectionObserver` island model; transpiling to anything older grows the output without winning a single visitor |
|
|
189
190
|
| `minify` | `true` | — |
|
|
@@ -193,7 +194,26 @@ esbuild settings:
|
|
|
193
194
|
| `legalComments` | `none` | — |
|
|
194
195
|
|
|
195
196
|
The output lands under `public/assets/js/` and is cleaned first on every pass.
|
|
196
|
-
`browserslist` is not read; the target list is hard-coded.
|
|
197
|
+
`browserslist` is not read; the target list is hard-coded. The step line prints
|
|
198
|
+
the total JS file count next to the entry count (`1 entry, 48 files`) so large
|
|
199
|
+
chunk graphs are easier to spot.
|
|
200
|
+
|
|
201
|
+
### Chunk grouping (`build.client.groups` / `chunkGroup`)
|
|
202
|
+
|
|
203
|
+
esbuild has no native `manualChunks`. `build.client.groups` and
|
|
204
|
+
`build.client.chunkGroup` are applied through a plugin:
|
|
205
|
+
|
|
206
|
+
1. A matching import is redirected to a re-export wrapper.
|
|
207
|
+
2. The wrapper side-effect-imports the `jskelet-group:<name>` barrel.
|
|
208
|
+
3. The barrel statically imports every known member of the group (bare packages,
|
|
209
|
+
`.{js,ts,mts}` files under path prefixes, plus members discovered at resolve
|
|
210
|
+
time) — every entry that touches the group reaches the same member set and
|
|
211
|
+
one shared chunk is produced.
|
|
212
|
+
|
|
213
|
+
**Trade-off:** tree-shaking inside a group weakens; a page that uses one member
|
|
214
|
+
may download the rest of the group. The group name is a config key; the output
|
|
215
|
+
file is usually still `chunks/chunk.<hash>.js`. Config reference:
|
|
216
|
+
[07-configuration.md](./07-configuration.md#build).
|
|
197
217
|
|
|
198
218
|
### The `@/` alias
|
|
199
219
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jskelet",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"description": "A framework that feels like no framework: Express 5 + build-time .jsk SSR (optional EJS peer), vanilla JS islands, Tailwind v4 and an in-process HTML TTL cache.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild manualChunks karşılığı: `build.client.groups` / `chunkGroup`.
|
|
3
|
+
*
|
|
4
|
+
* esbuild native grup ataması sunmaz. Bir gruba düşen import, re-export
|
|
5
|
+
* sarmalayıcısı üzerinden gider; sarmalayıcı gruba ait barrel'ı side-effect
|
|
6
|
+
* olarak çeker. Barrel tüm bilinen üyeleri statik import eder — gruba
|
|
7
|
+
* dokunan entry'ler aynı üye kümesine ulaşır ve paylaşılan tek chunk oluşur.
|
|
8
|
+
*
|
|
9
|
+
* Bedel: grup içi tree-shaking zayıflar (webpack manualChunks ile aynı
|
|
10
|
+
* takas). `splitting: false` iken plugin hiç eklenmez.
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
const GROUP_NS = "jskelet-group";
|
|
16
|
+
const REEXPORT_NS = "jskelet-group-reexport";
|
|
17
|
+
const GROUP_PREFIX = `${GROUP_NS}:`;
|
|
18
|
+
|
|
19
|
+
/** Barrel / re-export sarmalayıcısının kendi import'ları tekrar sarılmasın. */
|
|
20
|
+
const SKIP_NS = new Set([GROUP_NS, REEXPORT_NS]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} ChunkGroupOptions
|
|
24
|
+
* @property {string} root
|
|
25
|
+
* @property {Record<string, string[]>} groups
|
|
26
|
+
* @property {((id: string) => string | null | undefined) | null} [chunkGroup]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Grup adı dosya yoluna uygun mu (`chunks/<name>.[hash]`).
|
|
31
|
+
*
|
|
32
|
+
* @param {string} name
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
export function isSafeGroupName(name) {
|
|
36
|
+
return typeof name === "string" && /^[A-Za-z0-9_-]+$/.test(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Matcher'ın bare paket, `@/` veya göreli yol öneki olup olmadığını ayırır.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} matcher
|
|
43
|
+
* @returns {"bare" | "path"}
|
|
44
|
+
*/
|
|
45
|
+
export function matcherKind(matcher) {
|
|
46
|
+
if (matcher.startsWith("./") || matcher.startsWith("../") || matcher.startsWith("@/")) {
|
|
47
|
+
return "path";
|
|
48
|
+
}
|
|
49
|
+
return "bare";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Yol önekini proje köküne göre mutlak dizin / dosya önekine çevirir.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} root
|
|
56
|
+
* @param {string} matcher
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
export function resolvePathMatcher(root, matcher) {
|
|
60
|
+
if (matcher.startsWith("@/")) {
|
|
61
|
+
return path.resolve(root, matcher.slice(2));
|
|
62
|
+
}
|
|
63
|
+
return path.resolve(root, matcher);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Bare paket: exact veya `pkg/...` alt yolu.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} matcher
|
|
70
|
+
* @param {string} importPath
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
export function matchBarePackage(matcher, importPath) {
|
|
74
|
+
return importPath === matcher || importPath.startsWith(`${matcher}/`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Mutlak yol, matcher önekiyle başlıyor mu (dizin sınırı korunur).
|
|
79
|
+
*
|
|
80
|
+
* @param {string} absMatcher
|
|
81
|
+
* @param {string} absPath
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
export function matchPathPrefix(absMatcher, absPath) {
|
|
85
|
+
const normalizedMatcher = path.resolve(absMatcher);
|
|
86
|
+
const normalizedPath = path.resolve(absPath);
|
|
87
|
+
if (normalizedPath === normalizedMatcher) return true;
|
|
88
|
+
const prefix = normalizedMatcher.endsWith(path.sep)
|
|
89
|
+
? normalizedMatcher
|
|
90
|
+
: normalizedMatcher + path.sep;
|
|
91
|
+
return normalizedPath.startsWith(prefix);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Declarative path matcher altındaki `.{js,ts,mts}` dosyalarını listeler.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} root
|
|
98
|
+
* @param {string} matcher
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
*/
|
|
101
|
+
export function listPathMatcherMembers(root, matcher) {
|
|
102
|
+
const base = resolvePathMatcher(root, matcher);
|
|
103
|
+
if (!fs.existsSync(base)) return [];
|
|
104
|
+
|
|
105
|
+
const stat = fs.statSync(base);
|
|
106
|
+
if (stat.isFile()) return [base];
|
|
107
|
+
|
|
108
|
+
/** @type {string[]} */
|
|
109
|
+
const out = [];
|
|
110
|
+
walkJsFiles(base, out);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} dir
|
|
116
|
+
* @param {string[]} out
|
|
117
|
+
*/
|
|
118
|
+
function walkJsFiles(dir, out) {
|
|
119
|
+
let entries;
|
|
120
|
+
try {
|
|
121
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
122
|
+
} catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const full = path.join(dir, entry.name);
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
129
|
+
walkJsFiles(full, out);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!entry.isFile()) continue;
|
|
133
|
+
const ext = path.extname(entry.name);
|
|
134
|
+
if (ext === ".js" || ext === ".ts" || ext === ".mts" || ext === ".mjs") {
|
|
135
|
+
out.push(full);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Import yolu / çözümlenmiş dosya için grup adını bulur.
|
|
142
|
+
* `chunkGroup` hook'u `groups` map'inden önce gelir.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} importPath
|
|
145
|
+
* @param {string | null} resolvedPath
|
|
146
|
+
* @param {ChunkGroupOptions} options
|
|
147
|
+
* @returns {string | null}
|
|
148
|
+
*/
|
|
149
|
+
export function resolveGroupName(importPath, resolvedPath, options) {
|
|
150
|
+
const { root, groups, chunkGroup } = options;
|
|
151
|
+
|
|
152
|
+
if (typeof chunkGroup === "function") {
|
|
153
|
+
for (const id of [resolvedPath, importPath].filter(Boolean)) {
|
|
154
|
+
try {
|
|
155
|
+
const result = chunkGroup(/** @type {string} */ (id));
|
|
156
|
+
if (typeof result === "string" && isSafeGroupName(result.trim())) {
|
|
157
|
+
return result.trim();
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.warn(
|
|
161
|
+
"[build] build.client.chunkGroup() threw, ignoring this module",
|
|
162
|
+
error,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const [name, matchers] of Object.entries(groups)) {
|
|
169
|
+
for (const matcher of matchers) {
|
|
170
|
+
if (matcherKind(matcher) === "bare") {
|
|
171
|
+
if (matchBarePackage(matcher, importPath)) return name;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const absMatcher = resolvePathMatcher(root, matcher);
|
|
175
|
+
if (resolvedPath && matchPathPrefix(absMatcher, resolvedPath)) return name;
|
|
176
|
+
if (
|
|
177
|
+
importPath.startsWith("./") ||
|
|
178
|
+
importPath.startsWith("../") ||
|
|
179
|
+
importPath.startsWith("@/")
|
|
180
|
+
) {
|
|
181
|
+
// resolveDir bilinmeden göreli yolu kesinleştiremeyiz; @/ yeter.
|
|
182
|
+
if (importPath.startsWith("@/")) {
|
|
183
|
+
const absImport = path.resolve(root, importPath.slice(2));
|
|
184
|
+
if (matchPathPrefix(absMatcher, absImport)) return name;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {ChunkGroupOptions} options
|
|
195
|
+
* @returns {import('esbuild').Plugin | null}
|
|
196
|
+
*/
|
|
197
|
+
export function chunkGroupsPlugin(options) {
|
|
198
|
+
const { root, groups, chunkGroup } = options;
|
|
199
|
+
const hasGroups = Object.keys(groups).length > 0;
|
|
200
|
+
if (!hasGroups && typeof chunkGroup !== "function") return null;
|
|
201
|
+
|
|
202
|
+
/** @type {Map<string, Set<string>>} */
|
|
203
|
+
const members = new Map();
|
|
204
|
+
|
|
205
|
+
for (const name of Object.keys(groups)) {
|
|
206
|
+
if (!isSafeGroupName(name)) continue;
|
|
207
|
+
members.set(name, new Set());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Declarative matcher'lardan bilinen üyeleri önceden doldur.
|
|
211
|
+
for (const [name, matchers] of Object.entries(groups)) {
|
|
212
|
+
if (!isSafeGroupName(name)) continue;
|
|
213
|
+
const set = members.get(name) ?? new Set();
|
|
214
|
+
members.set(name, set);
|
|
215
|
+
for (const matcher of matchers) {
|
|
216
|
+
if (matcherKind(matcher) === "bare") {
|
|
217
|
+
set.add(matcher);
|
|
218
|
+
} else {
|
|
219
|
+
for (const file of listPathMatcherMembers(root, matcher)) {
|
|
220
|
+
set.add(file);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @param {string} group
|
|
228
|
+
* @param {string} member
|
|
229
|
+
*/
|
|
230
|
+
function addMember(group, member) {
|
|
231
|
+
if (!isSafeGroupName(group)) return;
|
|
232
|
+
let set = members.get(group);
|
|
233
|
+
if (!set) {
|
|
234
|
+
set = new Set();
|
|
235
|
+
members.set(group, set);
|
|
236
|
+
}
|
|
237
|
+
set.add(member);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
name: "jskelet-chunk-groups",
|
|
242
|
+
setup(build) {
|
|
243
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
244
|
+
if (args.pluginData?.jskeletChunkGroupSkip) return undefined;
|
|
245
|
+
|
|
246
|
+
// Re-export sarmalayıcısı ve entry `jskelet-group:name` ister; namespace
|
|
247
|
+
// SKIP olsa bile bu specifier'ı biz çözmeliyiz.
|
|
248
|
+
if (args.path.startsWith(GROUP_PREFIX)) {
|
|
249
|
+
const name = args.path.slice(GROUP_PREFIX.length);
|
|
250
|
+
if (!isSafeGroupName(name)) return undefined;
|
|
251
|
+
if (!members.has(name)) members.set(name, new Set());
|
|
252
|
+
return { path: name, namespace: GROUP_NS };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Barrel / re-export içindeki gerçek üye import'ları tekrar sarılmasın.
|
|
256
|
+
if (SKIP_NS.has(args.namespace)) return undefined;
|
|
257
|
+
|
|
258
|
+
// data:/http: vb. ve entry-point'leri dokunma.
|
|
259
|
+
if (args.kind === "entry-point") return undefined;
|
|
260
|
+
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(args.path)) return undefined;
|
|
261
|
+
|
|
262
|
+
// Önce hook / bare matcher ile ucuz eşleşme; path önekleri resolve ister.
|
|
263
|
+
let group = resolveGroupName(args.path, null, options);
|
|
264
|
+
|
|
265
|
+
const resolved = await build.resolve(args.path, {
|
|
266
|
+
importer: args.importer,
|
|
267
|
+
namespace: args.namespace,
|
|
268
|
+
resolveDir: args.resolveDir,
|
|
269
|
+
kind: args.kind,
|
|
270
|
+
pluginData: { jskeletChunkGroupSkip: true },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (resolved.errors?.length || !resolved.path) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Virtual / zaten bizim namespace.
|
|
278
|
+
if (resolved.namespace && SKIP_NS.has(resolved.namespace)) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
group = resolveGroupName(args.path, resolved.path, options) ?? group;
|
|
283
|
+
if (!group) return undefined;
|
|
284
|
+
|
|
285
|
+
// Üye olarak çözümlenmiş yolu yaz — barrel aynı dosyayı çeker.
|
|
286
|
+
addMember(group, resolved.path);
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
path: `${group}::${resolved.path}`,
|
|
290
|
+
namespace: REEXPORT_NS,
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
build.onLoad({ filter: /.*/, namespace: GROUP_NS }, (args) => {
|
|
295
|
+
const set = members.get(args.path) ?? new Set();
|
|
296
|
+
const lines = [...set].map((member) => `import ${JSON.stringify(member)};`);
|
|
297
|
+
return {
|
|
298
|
+
contents: lines.length ? `${lines.join("\n")}\n` : "export {}\n",
|
|
299
|
+
resolveDir: root,
|
|
300
|
+
loader: "js",
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
build.onLoad({ filter: /.*/, namespace: REEXPORT_NS }, (args) => {
|
|
305
|
+
const sep = args.path.indexOf("::");
|
|
306
|
+
if (sep === -1) {
|
|
307
|
+
return { contents: "export {}\n", loader: "js" };
|
|
308
|
+
}
|
|
309
|
+
const group = args.path.slice(0, sep);
|
|
310
|
+
const target = args.path.slice(sep + 2);
|
|
311
|
+
const targetLit = JSON.stringify(target);
|
|
312
|
+
const groupLit = JSON.stringify(`${GROUP_PREFIX}${group}`);
|
|
313
|
+
|
|
314
|
+
// Side-effect barrel + re-export. Named-only modüllerde
|
|
315
|
+
// `__ns.default` undefined olur (uyarı → logOverride ile susturulur);
|
|
316
|
+
// `export { default } from` ise hard error verdiği için kullanılmaz.
|
|
317
|
+
return {
|
|
318
|
+
contents: `
|
|
319
|
+
import ${groupLit};
|
|
320
|
+
import * as __ns from ${targetLit};
|
|
321
|
+
export * from ${targetLit};
|
|
322
|
+
export default __ns.default;
|
|
323
|
+
`,
|
|
324
|
+
resolveDir: root,
|
|
325
|
+
loader: "js",
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
@@ -15,6 +15,7 @@ import fs from "node:fs";
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import process from "node:process";
|
|
17
17
|
import * as esbuild from "esbuild";
|
|
18
|
+
import { chunkGroupsPlugin } from "../chunk-groups.mjs";
|
|
18
19
|
import { paths, patchManifest, pruneAssets } from "../paths.mjs";
|
|
19
20
|
import * as log from "../../log.mjs";
|
|
20
21
|
|
|
@@ -187,13 +188,34 @@ export async function buildClient(config, { watch = false } = {}) {
|
|
|
187
188
|
.split(path.sep)
|
|
188
189
|
.join("/");
|
|
189
190
|
|
|
191
|
+
const clientBuild = config.build?.client ?? {
|
|
192
|
+
splitting: true,
|
|
193
|
+
groups: {},
|
|
194
|
+
chunkGroup: null,
|
|
195
|
+
};
|
|
196
|
+
const splitting = clientBuild.splitting !== false;
|
|
197
|
+
const groups = splitting ? (clientBuild.groups ?? {}) : {};
|
|
198
|
+
const chunkGroup = splitting ? (clientBuild.chunkGroup ?? null) : null;
|
|
199
|
+
|
|
200
|
+
const groupPlugin = splitting
|
|
201
|
+
? chunkGroupsPlugin({
|
|
202
|
+
root: config.root,
|
|
203
|
+
groups,
|
|
204
|
+
chunkGroup,
|
|
205
|
+
})
|
|
206
|
+
: null;
|
|
207
|
+
|
|
208
|
+
/** @type {esbuild.Plugin[]} */
|
|
209
|
+
const plugins = [aliasPlugin(config.root)];
|
|
210
|
+
if (groupPlugin) plugins.push(groupPlugin);
|
|
211
|
+
|
|
190
212
|
/** @type {esbuild.BuildOptions} */
|
|
191
213
|
const options = {
|
|
192
214
|
entryPoints,
|
|
193
215
|
outdir: outDir,
|
|
194
216
|
absWorkingDir: config.root,
|
|
195
217
|
bundle: true,
|
|
196
|
-
splitting
|
|
218
|
+
splitting,
|
|
197
219
|
format: "esm",
|
|
198
220
|
target: ["chrome111", "edge111", "firefox111", "safari16.4"],
|
|
199
221
|
minify: true,
|
|
@@ -203,10 +225,15 @@ export async function buildClient(config, { watch = false } = {}) {
|
|
|
203
225
|
entryNames: "[name].[hash]",
|
|
204
226
|
chunkNames: "chunks/[name].[hash]",
|
|
205
227
|
legalComments: "none",
|
|
206
|
-
plugins
|
|
228
|
+
plugins,
|
|
207
229
|
define: {
|
|
208
230
|
"process.env": JSON.stringify(publicEnv(config.clientEnv)),
|
|
209
231
|
},
|
|
232
|
+
// Grup sarmalayıcısı her üyede `export { default }` yazar; named-only
|
|
233
|
+
// modüllerde bu uyarı gürültü — davranış yine doğru (default undefined).
|
|
234
|
+
...(groupPlugin
|
|
235
|
+
? { logOverride: { "import-is-undefined": "silent" } }
|
|
236
|
+
: {}),
|
|
210
237
|
};
|
|
211
238
|
|
|
212
239
|
if (watch) {
|
|
@@ -218,8 +245,7 @@ export async function buildClient(config, { watch = false } = {}) {
|
|
|
218
245
|
await context.watch();
|
|
219
246
|
writeMetafile(result.metafile);
|
|
220
247
|
const manifest = toManifest(result.metafile, config, entryRoot);
|
|
221
|
-
|
|
222
|
-
log.detail(`${entries} ${entries === 1 ? "entry" : "entries"}`);
|
|
248
|
+
log.detail(summarizeClientBuild(manifest, result.metafile));
|
|
223
249
|
return manifest;
|
|
224
250
|
}
|
|
225
251
|
|
|
@@ -230,11 +256,32 @@ export async function buildClient(config, { watch = false } = {}) {
|
|
|
230
256
|
(sum, output) => sum + output.bytes,
|
|
231
257
|
0,
|
|
232
258
|
);
|
|
233
|
-
|
|
234
|
-
|
|
259
|
+
log.detail(
|
|
260
|
+
`${summarizeClientBuild(manifest, result.metafile)}, ${log.size(bytes)}`,
|
|
261
|
+
);
|
|
235
262
|
return manifest;
|
|
236
263
|
}
|
|
237
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Entry + inject edilen group entry'ler metafile'da durur; teşhis için
|
|
267
|
+
* hem gerçek manifest entry sayısını hem toplam çıktı dosyasını yaz.
|
|
268
|
+
*
|
|
269
|
+
* @param {Record<string, string>} manifest
|
|
270
|
+
* @param {esbuild.Metafile} [metafile]
|
|
271
|
+
* @returns {string}
|
|
272
|
+
*/
|
|
273
|
+
function summarizeClientBuild(manifest, metafile) {
|
|
274
|
+
const entries = Object.keys(manifest).length;
|
|
275
|
+
const entryLabel = `${entries} ${entries === 1 ? "entry" : "entries"}`;
|
|
276
|
+
if (!metafile) return entryLabel;
|
|
277
|
+
|
|
278
|
+
const outputs = Object.keys(metafile.outputs).filter(
|
|
279
|
+
(file) => file.endsWith(".js") || file.endsWith(".mjs"),
|
|
280
|
+
);
|
|
281
|
+
const chunks = outputs.length;
|
|
282
|
+
return `${entryLabel}, ${chunks} ${chunks === 1 ? "file" : "files"}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
238
285
|
/**
|
|
239
286
|
* Metafile diske yazılır: dev panelindeki chunk analizi giriş/çıkış
|
|
240
287
|
* kırılımını buradan okur. Çalışma zamanı bu dosyaya bağımlı değildir.
|
package/src/config/defaults.js
CHANGED
|
@@ -470,6 +470,31 @@ export const DEFAULT_SECURITY = {
|
|
|
470
470
|
},
|
|
471
471
|
};
|
|
472
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Client bundle (esbuild) ayarları.
|
|
475
|
+
*
|
|
476
|
+
* `groups` / `chunkGroup` esbuild'in native manualChunks'ı olmadığı için
|
|
477
|
+
* framework plugin'i ile uygulanır — ayrıntı `src/build/chunk-groups.mjs`.
|
|
478
|
+
*/
|
|
479
|
+
export const DEFAULT_BUILD = {
|
|
480
|
+
client: {
|
|
481
|
+
/** esbuild `splitting`; kapalıyken gruplama yok sayılır. */
|
|
482
|
+
splitting: true,
|
|
483
|
+
/**
|
|
484
|
+
* İsim → matcher listesi. Bare paket (`chart.js`), yol öneki
|
|
485
|
+
* (`./client/chrome/`) veya `@/` öneki.
|
|
486
|
+
* @type {Record<string, string[]>}
|
|
487
|
+
*/
|
|
488
|
+
groups: {},
|
|
489
|
+
/**
|
|
490
|
+
* Modül kimliğini (mutlak yol veya bare specifier) grup adına map eder.
|
|
491
|
+
* `groups` ile çakışınca hook kazanır.
|
|
492
|
+
* @type {((id: string) => string | null | undefined) | null}
|
|
493
|
+
*/
|
|
494
|
+
chunkGroup: null,
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
|
|
473
498
|
/**
|
|
474
499
|
* Build-zamanı görsel adımı + opsiyonel runtime uzak görsel proxy.
|
|
475
500
|
*
|
package/src/config/index.js
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
ON_VISIT_CONCURRENCY_CEILING,
|
|
48
48
|
ON_VISIT_PER_PAGE_CEILING,
|
|
49
49
|
ON_VISIT_RPS_CEILING,
|
|
50
|
+
DEFAULT_BUILD,
|
|
50
51
|
DEFAULT_IMAGES,
|
|
51
52
|
DEFAULT_LOGS,
|
|
52
53
|
DEFAULT_NAVIGATION,
|
|
@@ -160,6 +161,17 @@ const CONFIG_FILE = "jskelet.config.mjs";
|
|
|
160
161
|
* @property {{ scan?: string[], dir: string } | false} icons
|
|
161
162
|
* @property {ImagesConfig | false} images
|
|
162
163
|
* @property {string[]} clientEnv Client bundle'a gömülecek env anahtarları.
|
|
164
|
+
* @property {BuildConfig} build Client bundling (esbuild splitting / chunk groups).
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @typedef {object} BuildClientConfig
|
|
169
|
+
* @property {boolean} splitting
|
|
170
|
+
* @property {Record<string, string[]>} groups
|
|
171
|
+
* @property {((id: string) => string | null | undefined) | null} chunkGroup
|
|
172
|
+
*
|
|
173
|
+
* @typedef {object} BuildConfig
|
|
174
|
+
* @property {BuildClientConfig} client
|
|
163
175
|
*/
|
|
164
176
|
|
|
165
177
|
/**
|
|
@@ -1139,6 +1151,118 @@ function normalizeSecurity(raw) {
|
|
|
1139
1151
|
};
|
|
1140
1152
|
}
|
|
1141
1153
|
|
|
1154
|
+
/**
|
|
1155
|
+
* Client build ayarları (`build.client`). Bozuk tipler uyarıyla varsayılana düşer.
|
|
1156
|
+
*
|
|
1157
|
+
* @param {unknown} raw
|
|
1158
|
+
* @returns {BuildConfig}
|
|
1159
|
+
*/
|
|
1160
|
+
export function normalizeBuild(raw) {
|
|
1161
|
+
if (raw == null) {
|
|
1162
|
+
return {
|
|
1163
|
+
client: {
|
|
1164
|
+
splitting: DEFAULT_BUILD.client.splitting,
|
|
1165
|
+
groups: {},
|
|
1166
|
+
chunkGroup: null,
|
|
1167
|
+
},
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
1172
|
+
console.warn("[config] build must be an object, ignoring it");
|
|
1173
|
+
return {
|
|
1174
|
+
client: {
|
|
1175
|
+
splitting: DEFAULT_BUILD.client.splitting,
|
|
1176
|
+
groups: {},
|
|
1177
|
+
chunkGroup: null,
|
|
1178
|
+
},
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const source = /** @type {Record<string, any>} */ (raw);
|
|
1183
|
+
const clientRaw = source.client;
|
|
1184
|
+
|
|
1185
|
+
if (clientRaw == null) {
|
|
1186
|
+
return {
|
|
1187
|
+
client: {
|
|
1188
|
+
splitting: DEFAULT_BUILD.client.splitting,
|
|
1189
|
+
groups: {},
|
|
1190
|
+
chunkGroup: null,
|
|
1191
|
+
},
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
if (typeof clientRaw !== "object" || Array.isArray(clientRaw)) {
|
|
1196
|
+
console.warn("[config] build.client must be an object, ignoring it");
|
|
1197
|
+
return {
|
|
1198
|
+
client: {
|
|
1199
|
+
splitting: DEFAULT_BUILD.client.splitting,
|
|
1200
|
+
groups: {},
|
|
1201
|
+
chunkGroup: null,
|
|
1202
|
+
},
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const client = /** @type {Record<string, any>} */ (clientRaw);
|
|
1207
|
+
const splitting = client.splitting !== false;
|
|
1208
|
+
|
|
1209
|
+
/** @type {Record<string, string[]>} */
|
|
1210
|
+
const groups = {};
|
|
1211
|
+
if (client.groups != null) {
|
|
1212
|
+
if (typeof client.groups !== "object" || Array.isArray(client.groups)) {
|
|
1213
|
+
console.warn("[config] build.client.groups must be an object, ignoring it");
|
|
1214
|
+
} else {
|
|
1215
|
+
for (const [name, matchers] of Object.entries(client.groups)) {
|
|
1216
|
+
if (!isSafeGroupName(name)) {
|
|
1217
|
+
console.warn(
|
|
1218
|
+
`[config] build.client.groups key "${name}" is invalid ` +
|
|
1219
|
+
"(use letters, digits, _ or -); skipping",
|
|
1220
|
+
);
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
const list = asArray(matchers, `build.client.groups.${name}`)
|
|
1224
|
+
.filter((entry) => typeof entry === "string" && entry.trim())
|
|
1225
|
+
.map((entry) => String(entry).trim());
|
|
1226
|
+
if (list.length) groups[name] = list;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/** @type {((id: string) => string | null | undefined) | null} */
|
|
1232
|
+
let chunkGroup = null;
|
|
1233
|
+
if (client.chunkGroup != null) {
|
|
1234
|
+
if (typeof client.chunkGroup === "function") {
|
|
1235
|
+
chunkGroup = client.chunkGroup;
|
|
1236
|
+
} else {
|
|
1237
|
+
console.warn(
|
|
1238
|
+
"[config] build.client.chunkGroup must be a function, ignoring it",
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (
|
|
1244
|
+
!splitting &&
|
|
1245
|
+
(Object.keys(groups).length > 0 || chunkGroup)
|
|
1246
|
+
) {
|
|
1247
|
+
console.warn(
|
|
1248
|
+
"[config] build.client.groups / chunkGroup require splitting; " +
|
|
1249
|
+
"they are ignored while splitting is false",
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
return { client: { splitting, groups, chunkGroup } };
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Chunk dosya adına güvenli grup adı.
|
|
1258
|
+
*
|
|
1259
|
+
* @param {string} name
|
|
1260
|
+
* @returns {boolean}
|
|
1261
|
+
*/
|
|
1262
|
+
function isSafeGroupName(name) {
|
|
1263
|
+
return typeof name === "string" && /^[A-Za-z0-9_-]+$/.test(name);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1142
1266
|
/**
|
|
1143
1267
|
* İkon sprite ayarları. `false` → adım atlanır. `dir` varsayılanı `"icons"`:
|
|
1144
1268
|
* o dizin varsa yalnızca yerel SVG'ler; yoksa Phosphor.
|
|
@@ -1423,6 +1547,7 @@ export async function loadConfig(options = {}) {
|
|
|
1423
1547
|
icons: normalizeIcons(source.icons),
|
|
1424
1548
|
images: normalizeImages(source.images),
|
|
1425
1549
|
clientEnv: source.clientEnv ?? [],
|
|
1550
|
+
build: normalizeBuild(source.build),
|
|
1426
1551
|
};
|
|
1427
1552
|
|
|
1428
1553
|
if (
|
|
@@ -420,6 +420,30 @@ export declare const DEFAULT_SECURITY: {
|
|
|
420
420
|
headerName: string;
|
|
421
421
|
};
|
|
422
422
|
};
|
|
423
|
+
/**
|
|
424
|
+
* Client bundle (esbuild) ayarları.
|
|
425
|
+
*
|
|
426
|
+
* `groups` / `chunkGroup` esbuild'in native manualChunks'ı olmadığı için
|
|
427
|
+
* framework plugin'i ile uygulanır — ayrıntı `src/build/chunk-groups.mjs`.
|
|
428
|
+
*/
|
|
429
|
+
export declare const DEFAULT_BUILD: {
|
|
430
|
+
client: {
|
|
431
|
+
/** esbuild `splitting`; kapalıyken gruplama yok sayılır. */
|
|
432
|
+
splitting: boolean;
|
|
433
|
+
/**
|
|
434
|
+
* İsim → matcher listesi. Bare paket (`chart.js`), yol öneki
|
|
435
|
+
* (`./client/chrome/`) veya `@/` öneki.
|
|
436
|
+
* @type {Record<string, string[]>}
|
|
437
|
+
*/
|
|
438
|
+
groups: Record<string, string[]>;
|
|
439
|
+
/**
|
|
440
|
+
* Modül kimliğini (mutlak yol veya bare specifier) grup adına map eder.
|
|
441
|
+
* `groups` ile çakışınca hook kazanır.
|
|
442
|
+
* @type {((id: string) => string | null | undefined) | null}
|
|
443
|
+
*/
|
|
444
|
+
chunkGroup: ((id: string) => string | null | undefined) | null;
|
|
445
|
+
};
|
|
446
|
+
};
|
|
423
447
|
/**
|
|
424
448
|
* Build-zamanı görsel adımı + opsiyonel runtime uzak görsel proxy.
|
|
425
449
|
*
|
package/types/config/index.d.ts
CHANGED
|
@@ -220,6 +220,18 @@ export type ResolvedConfig = {
|
|
|
220
220
|
* Client bundle'a gömülecek env anahtarları.
|
|
221
221
|
*/
|
|
222
222
|
clientEnv: string[];
|
|
223
|
+
/**
|
|
224
|
+
* Client bundling (esbuild splitting / chunk groups).
|
|
225
|
+
*/
|
|
226
|
+
build: BuildConfig;
|
|
227
|
+
};
|
|
228
|
+
export type BuildClientConfig = {
|
|
229
|
+
splitting: boolean;
|
|
230
|
+
groups: Record<string, string[]>;
|
|
231
|
+
chunkGroup: ((id: string) => string | null | undefined) | null;
|
|
232
|
+
};
|
|
233
|
+
export type BuildConfig = {
|
|
234
|
+
client: BuildClientConfig;
|
|
223
235
|
};
|
|
224
236
|
export type ImagesRemoteConfig = {
|
|
225
237
|
enabled: boolean;
|
|
@@ -279,6 +291,13 @@ export type SecurityConfig = {
|
|
|
279
291
|
headerName: string;
|
|
280
292
|
};
|
|
281
293
|
};
|
|
294
|
+
/**
|
|
295
|
+
* Client build ayarları (`build.client`). Bozuk tipler uyarıyla varsayılana düşer.
|
|
296
|
+
*
|
|
297
|
+
* @param {unknown} raw
|
|
298
|
+
* @returns {BuildConfig}
|
|
299
|
+
*/
|
|
300
|
+
export declare function normalizeBuild(raw: unknown): BuildConfig;
|
|
282
301
|
/**
|
|
283
302
|
* Config'i okur, normalize eder ve modül durumuna yazar. Sunucu ve build
|
|
284
303
|
* süreçleri açılışta bir kez çağırır.
|