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/compile/resolve.js
CHANGED
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* View kökleri ve `.jsk` dosya keşfi.
|
|
3
|
-
*
|
|
4
|
-
* Sıra: views, sonra shared/views, sonra her feature altındaki views.
|
|
5
|
-
* Aynı view id birden fazla kökte varsa son bulunan kazanır.
|
|
6
|
-
*/
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { toPascalCase } from "./codegen.js";
|
|
10
|
-
import { CompileError } from "./errors.js";
|
|
11
|
-
import { scanNamedExports } from "./scan-exports.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Runtime'ın camelCase → PascalCase alias'ı ile aynı kural.
|
|
15
|
-
* @param {string} name
|
|
16
|
-
* @returns {string}
|
|
17
|
-
*/
|
|
18
|
-
export function toComponentTag(name) {
|
|
19
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {{ root: string, dirs: Record<string, string> }} config
|
|
24
|
-
* @returns {string[]} Mutlak view kökleri (var olanlar).
|
|
25
|
-
*/
|
|
26
|
-
export function getViewRoots(config) {
|
|
27
|
-
/** @type {string[]} */
|
|
28
|
-
const roots = [];
|
|
29
|
-
const { dirs, root } = config;
|
|
30
|
-
|
|
31
|
-
if (dirs.views && fs.existsSync(dirs.views)) {
|
|
32
|
-
roots.push(dirs.views);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const sharedViews = dirs.shared
|
|
36
|
-
? path.join(dirs.shared, "views")
|
|
37
|
-
: path.join(root, "shared", "views");
|
|
38
|
-
if (fs.existsSync(sharedViews)) roots.push(sharedViews);
|
|
39
|
-
|
|
40
|
-
const featuresDir = dirs.features ?? path.join(root, "features");
|
|
41
|
-
if (fs.existsSync(featuresDir)) {
|
|
42
|
-
const features = fs
|
|
43
|
-
.readdirSync(featuresDir, { withFileTypes: true })
|
|
44
|
-
.filter((e) => e.isDirectory())
|
|
45
|
-
.map((e) => e.name)
|
|
46
|
-
.sort((a, b) => a.localeCompare(b));
|
|
47
|
-
|
|
48
|
-
for (const name of features) {
|
|
49
|
-
const views = path.join(featuresDir, name, "views");
|
|
50
|
-
if (fs.existsSync(views)) roots.push(views);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return roots;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Bileşen dizinleri: her view kökü altındaki `components/`.
|
|
59
|
-
* @param {{ root: string, dirs: Record<string, string> }} config
|
|
60
|
-
* @returns {string[]}
|
|
61
|
-
*/
|
|
62
|
-
export function getComponentDirs(config) {
|
|
63
|
-
return getViewRoots(config)
|
|
64
|
-
.map((root) => path.join(root, "components"))
|
|
65
|
-
.filter((dir) => fs.existsSync(dir));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @param {string[]} viewRoots
|
|
70
|
-
* @returns {Map<string, string>} viewId → mutlak `.jsk` yolu
|
|
71
|
-
*/
|
|
72
|
-
export function discoverJskFiles(viewRoots) {
|
|
73
|
-
/** @type {Map<string, string>} */
|
|
74
|
-
const map = new Map();
|
|
75
|
-
|
|
76
|
-
for (const root of viewRoots) {
|
|
77
|
-
collectJsk(root, root, map);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return map;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @param {string} dir
|
|
85
|
-
* @param {string} root
|
|
86
|
-
* @param {Map<string, string>} out
|
|
87
|
-
*/
|
|
88
|
-
function collectJsk(dir, root, out) {
|
|
89
|
-
if (!fs.existsSync(dir)) return;
|
|
90
|
-
|
|
91
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
92
|
-
const full = path.join(dir, entry.name);
|
|
93
|
-
if (entry.isDirectory()) {
|
|
94
|
-
collectJsk(full, root, out);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (!entry.name.endsWith(".jsk")) continue;
|
|
98
|
-
const rel = path.relative(root, full).split(path.sep).join("/");
|
|
99
|
-
const viewId = rel.replace(/\.jsk$/i, "");
|
|
100
|
-
out.set(viewId, full);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* `components/foo-bar.jsk` → `FooBar`
|
|
106
|
-
* @param {string} viewId
|
|
107
|
-
* @returns {string | null}
|
|
108
|
-
*/
|
|
109
|
-
export function componentNameFromViewId(viewId) {
|
|
110
|
-
const parts = viewId.split("/");
|
|
111
|
-
if (parts[0] !== "components" || parts.length < 2) return null;
|
|
112
|
-
const base = parts[parts.length - 1];
|
|
113
|
-
return toPascalCase(base);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Bilinen bileşen adları: JS named export'lar + derlenecek `.jsk` bileşenleri
|
|
118
|
-
* + yerleşik etiketler.
|
|
119
|
-
*
|
|
120
|
-
* JS tarafında dosya adı varsayılmaz; kaynak metinden `export` adları okunur.
|
|
121
|
-
* Aynı export (veya aynı PascalCase etiket) iki dosyada varsa derleme hatası.
|
|
122
|
-
*
|
|
123
|
-
* @param {string[]} componentDirs
|
|
124
|
-
* @param {Map<string, string>} jskFiles
|
|
125
|
-
* @returns {Set<string>}
|
|
126
|
-
*/
|
|
127
|
-
export function collectKnownComponents(componentDirs, jskFiles) {
|
|
128
|
-
const known = new Set([
|
|
129
|
-
"Link",
|
|
130
|
-
"Image",
|
|
131
|
-
"Icon",
|
|
132
|
-
"CsrfField",
|
|
133
|
-
"PreloadImage",
|
|
134
|
-
"Stylesheets",
|
|
135
|
-
"BodyScripts",
|
|
136
|
-
"JsonLd",
|
|
137
|
-
]);
|
|
138
|
-
|
|
139
|
-
/** @type {Map<string, string>} export adı → göreli yol */
|
|
140
|
-
const byName = new Map();
|
|
141
|
-
/** @type {Map<string, string>} PascalCase etiket → göreli yol */
|
|
142
|
-
const byTag = new Map();
|
|
143
|
-
|
|
144
|
-
for (const [viewId] of jskFiles) {
|
|
145
|
-
const name = componentNameFromViewId(viewId);
|
|
146
|
-
if (!name) continue;
|
|
147
|
-
known.add(name);
|
|
148
|
-
byTag.set(name, `${viewId}.jsk`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
for (const dir of componentDirs) {
|
|
152
|
-
collectJsComponentNames(dir, known, byName, byTag);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return known;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @param {string} name
|
|
160
|
-
* @param {string} origin
|
|
161
|
-
* @param {Map<string, string>} byName
|
|
162
|
-
* @param {Map<string, string>} byTag
|
|
163
|
-
*/
|
|
164
|
-
function registerExportOrigin(name, origin, byName, byTag) {
|
|
165
|
-
const previousName = byName.get(name);
|
|
166
|
-
if (previousName && previousName !== origin) {
|
|
167
|
-
throw new CompileError(
|
|
168
|
-
`Component '${name}' is defined twice: ${previousName} and ${origin}`,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
byName.set(name, origin);
|
|
172
|
-
|
|
173
|
-
const tag = toComponentTag(name);
|
|
174
|
-
const previousTag = byTag.get(tag);
|
|
175
|
-
if (previousTag && previousTag !== origin) {
|
|
176
|
-
throw new CompileError(
|
|
177
|
-
`Component '${tag}' is defined twice: ${previousTag} and ${origin}`,
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
byTag.set(tag, origin);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* @param {string} dir
|
|
185
|
-
* @param {Set<string>} out
|
|
186
|
-
* @param {Map<string, string>} byName
|
|
187
|
-
* @param {Map<string, string>} byTag
|
|
188
|
-
*/
|
|
189
|
-
function collectJsComponentNames(dir, out, byName, byTag) {
|
|
190
|
-
if (!fs.existsSync(dir)) return;
|
|
191
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
192
|
-
const full = path.join(dir, entry.name);
|
|
193
|
-
if (entry.isDirectory()) {
|
|
194
|
-
collectJsComponentNames(full, out, byName, byTag);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (!entry.name.endsWith(".js")) continue;
|
|
198
|
-
if (entry.name === "loader.js" || entry.name === "index.js") continue;
|
|
199
|
-
|
|
200
|
-
// Kimlik mutlak yol: çoklu kökte iki `list.js` aynı göreli ada sahip olabilir.
|
|
201
|
-
const origin = full.split(path.sep).join("/");
|
|
202
|
-
const source = fs.readFileSync(full, "utf8");
|
|
203
|
-
const exports = scanNamedExports(source);
|
|
204
|
-
|
|
205
|
-
for (const name of exports) {
|
|
206
|
-
registerExportOrigin(name, origin, byName, byTag);
|
|
207
|
-
out.add(name);
|
|
208
|
-
out.add(toComponentTag(name));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* View kökleri ve `.jsk` dosya keşfi.
|
|
3
|
+
*
|
|
4
|
+
* Sıra: views, sonra shared/views, sonra her feature altındaki views.
|
|
5
|
+
* Aynı view id birden fazla kökte varsa son bulunan kazanır.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { toPascalCase } from "./codegen.js";
|
|
10
|
+
import { CompileError } from "./errors.js";
|
|
11
|
+
import { scanNamedExports } from "./scan-exports.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Runtime'ın camelCase → PascalCase alias'ı ile aynı kural.
|
|
15
|
+
* @param {string} name
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
export function toComponentTag(name) {
|
|
19
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {{ root: string, dirs: Record<string, string> }} config
|
|
24
|
+
* @returns {string[]} Mutlak view kökleri (var olanlar).
|
|
25
|
+
*/
|
|
26
|
+
export function getViewRoots(config) {
|
|
27
|
+
/** @type {string[]} */
|
|
28
|
+
const roots = [];
|
|
29
|
+
const { dirs, root } = config;
|
|
30
|
+
|
|
31
|
+
if (dirs.views && fs.existsSync(dirs.views)) {
|
|
32
|
+
roots.push(dirs.views);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sharedViews = dirs.shared
|
|
36
|
+
? path.join(dirs.shared, "views")
|
|
37
|
+
: path.join(root, "shared", "views");
|
|
38
|
+
if (fs.existsSync(sharedViews)) roots.push(sharedViews);
|
|
39
|
+
|
|
40
|
+
const featuresDir = dirs.features ?? path.join(root, "features");
|
|
41
|
+
if (fs.existsSync(featuresDir)) {
|
|
42
|
+
const features = fs
|
|
43
|
+
.readdirSync(featuresDir, { withFileTypes: true })
|
|
44
|
+
.filter((e) => e.isDirectory())
|
|
45
|
+
.map((e) => e.name)
|
|
46
|
+
.sort((a, b) => a.localeCompare(b));
|
|
47
|
+
|
|
48
|
+
for (const name of features) {
|
|
49
|
+
const views = path.join(featuresDir, name, "views");
|
|
50
|
+
if (fs.existsSync(views)) roots.push(views);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return roots;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Bileşen dizinleri: her view kökü altındaki `components/`.
|
|
59
|
+
* @param {{ root: string, dirs: Record<string, string> }} config
|
|
60
|
+
* @returns {string[]}
|
|
61
|
+
*/
|
|
62
|
+
export function getComponentDirs(config) {
|
|
63
|
+
return getViewRoots(config)
|
|
64
|
+
.map((root) => path.join(root, "components"))
|
|
65
|
+
.filter((dir) => fs.existsSync(dir));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {string[]} viewRoots
|
|
70
|
+
* @returns {Map<string, string>} viewId → mutlak `.jsk` yolu
|
|
71
|
+
*/
|
|
72
|
+
export function discoverJskFiles(viewRoots) {
|
|
73
|
+
/** @type {Map<string, string>} */
|
|
74
|
+
const map = new Map();
|
|
75
|
+
|
|
76
|
+
for (const root of viewRoots) {
|
|
77
|
+
collectJsk(root, root, map);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return map;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} dir
|
|
85
|
+
* @param {string} root
|
|
86
|
+
* @param {Map<string, string>} out
|
|
87
|
+
*/
|
|
88
|
+
function collectJsk(dir, root, out) {
|
|
89
|
+
if (!fs.existsSync(dir)) return;
|
|
90
|
+
|
|
91
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
92
|
+
const full = path.join(dir, entry.name);
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
collectJsk(full, root, out);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!entry.name.endsWith(".jsk")) continue;
|
|
98
|
+
const rel = path.relative(root, full).split(path.sep).join("/");
|
|
99
|
+
const viewId = rel.replace(/\.jsk$/i, "");
|
|
100
|
+
out.set(viewId, full);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* `components/foo-bar.jsk` → `FooBar`
|
|
106
|
+
* @param {string} viewId
|
|
107
|
+
* @returns {string | null}
|
|
108
|
+
*/
|
|
109
|
+
export function componentNameFromViewId(viewId) {
|
|
110
|
+
const parts = viewId.split("/");
|
|
111
|
+
if (parts[0] !== "components" || parts.length < 2) return null;
|
|
112
|
+
const base = parts[parts.length - 1];
|
|
113
|
+
return toPascalCase(base);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Bilinen bileşen adları: JS named export'lar + derlenecek `.jsk` bileşenleri
|
|
118
|
+
* + yerleşik etiketler.
|
|
119
|
+
*
|
|
120
|
+
* JS tarafında dosya adı varsayılmaz; kaynak metinden `export` adları okunur.
|
|
121
|
+
* Aynı export (veya aynı PascalCase etiket) iki dosyada varsa derleme hatası.
|
|
122
|
+
*
|
|
123
|
+
* @param {string[]} componentDirs
|
|
124
|
+
* @param {Map<string, string>} jskFiles
|
|
125
|
+
* @returns {Set<string>}
|
|
126
|
+
*/
|
|
127
|
+
export function collectKnownComponents(componentDirs, jskFiles) {
|
|
128
|
+
const known = new Set([
|
|
129
|
+
"Link",
|
|
130
|
+
"Image",
|
|
131
|
+
"Icon",
|
|
132
|
+
"CsrfField",
|
|
133
|
+
"PreloadImage",
|
|
134
|
+
"Stylesheets",
|
|
135
|
+
"BodyScripts",
|
|
136
|
+
"JsonLd",
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
/** @type {Map<string, string>} export adı → göreli yol */
|
|
140
|
+
const byName = new Map();
|
|
141
|
+
/** @type {Map<string, string>} PascalCase etiket → göreli yol */
|
|
142
|
+
const byTag = new Map();
|
|
143
|
+
|
|
144
|
+
for (const [viewId] of jskFiles) {
|
|
145
|
+
const name = componentNameFromViewId(viewId);
|
|
146
|
+
if (!name) continue;
|
|
147
|
+
known.add(name);
|
|
148
|
+
byTag.set(name, `${viewId}.jsk`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const dir of componentDirs) {
|
|
152
|
+
collectJsComponentNames(dir, known, byName, byTag);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return known;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {string} name
|
|
160
|
+
* @param {string} origin
|
|
161
|
+
* @param {Map<string, string>} byName
|
|
162
|
+
* @param {Map<string, string>} byTag
|
|
163
|
+
*/
|
|
164
|
+
function registerExportOrigin(name, origin, byName, byTag) {
|
|
165
|
+
const previousName = byName.get(name);
|
|
166
|
+
if (previousName && previousName !== origin) {
|
|
167
|
+
throw new CompileError(
|
|
168
|
+
`Component '${name}' is defined twice: ${previousName} and ${origin}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
byName.set(name, origin);
|
|
172
|
+
|
|
173
|
+
const tag = toComponentTag(name);
|
|
174
|
+
const previousTag = byTag.get(tag);
|
|
175
|
+
if (previousTag && previousTag !== origin) {
|
|
176
|
+
throw new CompileError(
|
|
177
|
+
`Component '${tag}' is defined twice: ${previousTag} and ${origin}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
byTag.set(tag, origin);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {string} dir
|
|
185
|
+
* @param {Set<string>} out
|
|
186
|
+
* @param {Map<string, string>} byName
|
|
187
|
+
* @param {Map<string, string>} byTag
|
|
188
|
+
*/
|
|
189
|
+
function collectJsComponentNames(dir, out, byName, byTag) {
|
|
190
|
+
if (!fs.existsSync(dir)) return;
|
|
191
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
192
|
+
const full = path.join(dir, entry.name);
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
collectJsComponentNames(full, out, byName, byTag);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!entry.name.endsWith(".js")) continue;
|
|
198
|
+
if (entry.name === "loader.js" || entry.name === "index.js") continue;
|
|
199
|
+
|
|
200
|
+
// Kimlik mutlak yol: çoklu kökte iki `list.js` aynı göreli ada sahip olabilir.
|
|
201
|
+
const origin = full.split(path.sep).join("/");
|
|
202
|
+
const source = fs.readFileSync(full, "utf8");
|
|
203
|
+
const exports = scanNamedExports(source);
|
|
204
|
+
|
|
205
|
+
for (const name of exports) {
|
|
206
|
+
registerExportOrigin(name, origin, byName, byTag);
|
|
207
|
+
out.add(name);
|
|
208
|
+
out.add(toComponentTag(name));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Kaynak metinden named export adlarını çıkarır — modülü çalıştırmadan.
|
|
3
|
-
* Compile-time bilinen bileşen listesi için; `default` yok sayılır.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Yorumları kaba şekilde siler; string içindeki sahte eşleşmeler nadirdir.
|
|
8
|
-
* @param {string} source
|
|
9
|
-
* @returns {string}
|
|
10
|
-
*/
|
|
11
|
-
function stripComments(source) {
|
|
12
|
-
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* `export { a, b as C, default as X }` listesinden dışa verilen adları toplar.
|
|
17
|
-
* @param {string} clause
|
|
18
|
-
* @param {Set<string>} out
|
|
19
|
-
*/
|
|
20
|
-
function addExportList(clause, out) {
|
|
21
|
-
for (const part of clause.split(",")) {
|
|
22
|
-
const trimmed = part.trim();
|
|
23
|
-
if (!trimmed) continue;
|
|
24
|
-
const bits = trimmed.split(/\s+as\s+/i);
|
|
25
|
-
const exported = (bits[1] ?? bits[0]).trim();
|
|
26
|
-
if (!exported || exported === "default") continue;
|
|
27
|
-
out.add(exported);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {string} source
|
|
33
|
-
* @returns {string[]}
|
|
34
|
-
*/
|
|
35
|
-
export function scanNamedExports(source) {
|
|
36
|
-
const text = stripComments(source);
|
|
37
|
-
/** @type {Set<string>} */
|
|
38
|
-
const names = new Set();
|
|
39
|
-
|
|
40
|
-
for (const match of text.matchAll(
|
|
41
|
-
/\bexport\s+(?:async\s+)?(?:function\*?|class|const|let|var)\s+([A-Za-z_$][\w$]*)/g,
|
|
42
|
-
)) {
|
|
43
|
-
names.add(match[1]);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
for (const match of text.matchAll(/\bexport\s*\{([^}]+)\}/g)) {
|
|
47
|
-
addExportList(match[1], names);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return [...names];
|
|
51
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Kaynak metinden named export adlarını çıkarır — modülü çalıştırmadan.
|
|
3
|
+
* Compile-time bilinen bileşen listesi için; `default` yok sayılır.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Yorumları kaba şekilde siler; string içindeki sahte eşleşmeler nadirdir.
|
|
8
|
+
* @param {string} source
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
function stripComments(source) {
|
|
12
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `export { a, b as C, default as X }` listesinden dışa verilen adları toplar.
|
|
17
|
+
* @param {string} clause
|
|
18
|
+
* @param {Set<string>} out
|
|
19
|
+
*/
|
|
20
|
+
function addExportList(clause, out) {
|
|
21
|
+
for (const part of clause.split(",")) {
|
|
22
|
+
const trimmed = part.trim();
|
|
23
|
+
if (!trimmed) continue;
|
|
24
|
+
const bits = trimmed.split(/\s+as\s+/i);
|
|
25
|
+
const exported = (bits[1] ?? bits[0]).trim();
|
|
26
|
+
if (!exported || exported === "default") continue;
|
|
27
|
+
out.add(exported);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} source
|
|
33
|
+
* @returns {string[]}
|
|
34
|
+
*/
|
|
35
|
+
export function scanNamedExports(source) {
|
|
36
|
+
const text = stripComments(source);
|
|
37
|
+
/** @type {Set<string>} */
|
|
38
|
+
const names = new Set();
|
|
39
|
+
|
|
40
|
+
for (const match of text.matchAll(
|
|
41
|
+
/\bexport\s+(?:async\s+)?(?:function\*?|class|const|let|var)\s+([A-Za-z_$][\w$]*)/g,
|
|
42
|
+
)) {
|
|
43
|
+
names.add(match[1]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const match of text.matchAll(/\bexport\s*\{([^}]+)\}/g)) {
|
|
47
|
+
addExportList(match[1], names);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return [...names];
|
|
51
|
+
}
|