jskelet 0.3.5 → 0.4.0
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 +17 -0
- package/bin/jskelet.mjs +20 -7
- package/docs/02-mimari.md +3 -0
- package/docs/03-routing.md +19 -3
- package/docs/04-render-ve-sablonlar.md +60 -6
- package/docs/07-yapilandirma.md +7 -5
- package/docs/08-build.md +5 -0
- package/docs/en/02-architecture.md +3 -0
- package/docs/en/03-routing.md +19 -4
- package/docs/en/04-rendering.md +61 -9
- package/docs/en/07-configuration.md +5 -3
- package/docs/en/08-build.md +5 -0
- package/package.json +2 -2
- package/src/build/build.mjs +6 -0
- package/src/build/ensure-build.mjs +5 -1
- package/src/build/tasks/icons.mjs +2 -2
- package/src/build/tasks/templates.mjs +20 -0
- package/src/compile/codegen.js +332 -0
- package/src/compile/compile-all.js +158 -0
- package/src/compile/errors.js +66 -0
- package/src/compile/expr.js +404 -0
- package/src/compile/index.js +15 -0
- package/src/compile/parse.js +485 -0
- package/src/compile/resolve.js +159 -0
- package/src/config/defaults.js +6 -2
- package/src/config/index.js +7 -0
- package/src/dev-server.mjs +4 -2
- package/src/generate.mjs +163 -0
- package/src/init.mjs +8 -6
- package/src/server/render.js +153 -40
- package/src/server/router.js +26 -3
- package/src/views/components/loader.js +26 -13
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Şablon ifadeleri — kısıtlı alt küme. İstek anında yorumlanmaz; build'de
|
|
3
|
+
* JS kaynak metnine çevrilir. Çağrı, atama ve `new` yok.
|
|
4
|
+
*/
|
|
5
|
+
import { CompileError } from "./errors.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {{ type: 'Literal', value: string | number | boolean | null, raw: string }} LiteralNode
|
|
9
|
+
* @typedef {{ type: 'Ident', name: string }} IdentNode
|
|
10
|
+
* @typedef {{ type: 'Member', object: ExprNode, property: string, optional: boolean }} MemberNode
|
|
11
|
+
* @typedef {{ type: 'Index', object: ExprNode, index: ExprNode, optional: boolean }} IndexNode
|
|
12
|
+
* @typedef {{ type: 'Unary', op: '!' | '-', argument: ExprNode }} UnaryNode
|
|
13
|
+
* @typedef {{ type: 'Binary', op: string, left: ExprNode, right: ExprNode }} BinaryNode
|
|
14
|
+
* @typedef {{ type: 'Cond', test: ExprNode, consequent: ExprNode, alternate: ExprNode }} CondNode
|
|
15
|
+
* @typedef {LiteralNode | IdentNode | MemberNode | IndexNode | UnaryNode | BinaryNode | CondNode} ExprNode
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const IDENT_START = /[A-Za-z_$]/
|
|
19
|
+
const IDENT_CONT = /[A-Za-z0-9_$]/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} source
|
|
23
|
+
* @param {number} start
|
|
24
|
+
* @param {number} end
|
|
25
|
+
* @param {{ file?: string, templateSource?: string, templateIndex?: number }} [meta]
|
|
26
|
+
* @returns {ExprNode}
|
|
27
|
+
*/
|
|
28
|
+
export function parseExpr(source, start = 0, end = source.length, meta = {}) {
|
|
29
|
+
const parser = new ExprParser(source, start, end, meta);
|
|
30
|
+
const node = parser.parseCond();
|
|
31
|
+
parser.skipWs();
|
|
32
|
+
if (parser.i < parser.end) {
|
|
33
|
+
throw parser.error(`Unexpected token in expression`);
|
|
34
|
+
}
|
|
35
|
+
return node;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* İfadeyi JS kaynak metnine çevirir. `locals` içindeki isimler çıplak kalır;
|
|
40
|
+
* diğerleri `data.` öneki alır.
|
|
41
|
+
*
|
|
42
|
+
* @param {ExprNode} node
|
|
43
|
+
* @param {Set<string>} [locals]
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
export function codegenExpr(node, locals = new Set()) {
|
|
47
|
+
switch (node.type) {
|
|
48
|
+
case "Literal":
|
|
49
|
+
return node.raw;
|
|
50
|
+
case "Ident":
|
|
51
|
+
if (locals.has(node.name)) return node.name;
|
|
52
|
+
return `data.${node.name}`;
|
|
53
|
+
case "Member": {
|
|
54
|
+
const obj = codegenExpr(node.object, locals);
|
|
55
|
+
const op = node.optional ? "?." : ".";
|
|
56
|
+
return `${obj}${op}${node.property}`;
|
|
57
|
+
}
|
|
58
|
+
case "Index": {
|
|
59
|
+
const obj = codegenExpr(node.object, locals);
|
|
60
|
+
const idx = codegenExpr(node.index, locals);
|
|
61
|
+
const op = node.optional ? "?.[" : "[";
|
|
62
|
+
return `${obj}${op}${idx}]`;
|
|
63
|
+
}
|
|
64
|
+
case "Unary":
|
|
65
|
+
return `(${node.op}${codegenExpr(node.argument, locals)})`;
|
|
66
|
+
case "Binary":
|
|
67
|
+
return `(${codegenExpr(node.left, locals)} ${node.op} ${codegenExpr(node.right, locals)})`;
|
|
68
|
+
case "Cond":
|
|
69
|
+
return `(${codegenExpr(node.test, locals)} ? ${codegenExpr(node.consequent, locals)} : ${codegenExpr(node.alternate, locals)})`;
|
|
70
|
+
default:
|
|
71
|
+
throw new Error(`Unknown expr node: ${/** @type {{ type: string }} */ (node).type}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* İfadede geçen kök tanımlayıcılar (data alanları / local adayları).
|
|
77
|
+
* @param {ExprNode} node
|
|
78
|
+
* @param {Set<string>} [out]
|
|
79
|
+
* @returns {Set<string>}
|
|
80
|
+
*/
|
|
81
|
+
export function collectIdents(node, out = new Set()) {
|
|
82
|
+
switch (node.type) {
|
|
83
|
+
case "Literal":
|
|
84
|
+
break;
|
|
85
|
+
case "Ident":
|
|
86
|
+
out.add(node.name);
|
|
87
|
+
break;
|
|
88
|
+
case "Member":
|
|
89
|
+
collectIdents(node.object, out);
|
|
90
|
+
break;
|
|
91
|
+
case "Index":
|
|
92
|
+
collectIdents(node.object, out);
|
|
93
|
+
collectIdents(node.index, out);
|
|
94
|
+
break;
|
|
95
|
+
case "Unary":
|
|
96
|
+
collectIdents(node.argument, out);
|
|
97
|
+
break;
|
|
98
|
+
case "Binary":
|
|
99
|
+
collectIdents(node.left, out);
|
|
100
|
+
collectIdents(node.right, out);
|
|
101
|
+
break;
|
|
102
|
+
case "Cond":
|
|
103
|
+
collectIdents(node.test, out);
|
|
104
|
+
collectIdents(node.consequent, out);
|
|
105
|
+
collectIdents(node.alternate, out);
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class ExprParser {
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} source
|
|
116
|
+
* @param {number} start
|
|
117
|
+
* @param {number} end
|
|
118
|
+
* @param {{ file?: string, templateSource?: string, templateIndex?: number }} meta
|
|
119
|
+
*/
|
|
120
|
+
constructor(source, start, end, meta) {
|
|
121
|
+
this.source = source;
|
|
122
|
+
this.i = start;
|
|
123
|
+
this.end = end;
|
|
124
|
+
this.meta = meta;
|
|
125
|
+
this.base = start;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} message
|
|
130
|
+
* @returns {CompileError}
|
|
131
|
+
*/
|
|
132
|
+
error(message) {
|
|
133
|
+
const offset = this.meta.templateIndex != null ? this.meta.templateIndex + (this.i - this.base) : this.i;
|
|
134
|
+
return new CompileError(message, {
|
|
135
|
+
file: this.meta.file,
|
|
136
|
+
source: this.meta.templateSource ?? this.source,
|
|
137
|
+
index: this.meta.templateSource ? offset : this.i,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
skipWs() {
|
|
142
|
+
while (this.i < this.end && /\s/.test(this.source[this.i])) this.i += 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
peek() {
|
|
146
|
+
return this.i < this.end ? this.source[this.i] : "";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} expected
|
|
151
|
+
*/
|
|
152
|
+
eat(expected) {
|
|
153
|
+
this.skipWs();
|
|
154
|
+
if (!this.source.startsWith(expected, this.i)) {
|
|
155
|
+
throw this.error(`Expected '${expected}'`);
|
|
156
|
+
}
|
|
157
|
+
this.i += expected.length;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} expected
|
|
162
|
+
* @returns {boolean}
|
|
163
|
+
*/
|
|
164
|
+
match(expected) {
|
|
165
|
+
this.skipWs();
|
|
166
|
+
if (!this.source.startsWith(expected, this.i)) return false;
|
|
167
|
+
this.i += expected.length;
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @returns {ExprNode} */
|
|
172
|
+
parseCond() {
|
|
173
|
+
const test = this.parseOr();
|
|
174
|
+
this.skipWs();
|
|
175
|
+
if (!this.match("?")) return test;
|
|
176
|
+
const consequent = this.parseCond();
|
|
177
|
+
this.eat(":");
|
|
178
|
+
const alternate = this.parseCond();
|
|
179
|
+
return { type: "Cond", test, consequent, alternate };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** @returns {ExprNode} */
|
|
183
|
+
parseOr() {
|
|
184
|
+
let left = this.parseAnd();
|
|
185
|
+
for (;;) {
|
|
186
|
+
this.skipWs();
|
|
187
|
+
if (!this.match("||")) break;
|
|
188
|
+
left = { type: "Binary", op: "||", left, right: this.parseAnd() };
|
|
189
|
+
}
|
|
190
|
+
return left;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** @returns {ExprNode} */
|
|
194
|
+
parseAnd() {
|
|
195
|
+
let left = this.parseEquality();
|
|
196
|
+
for (;;) {
|
|
197
|
+
this.skipWs();
|
|
198
|
+
if (!this.match("&&")) break;
|
|
199
|
+
left = { type: "Binary", op: "&&", left, right: this.parseEquality() };
|
|
200
|
+
}
|
|
201
|
+
return left;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @returns {ExprNode} */
|
|
205
|
+
parseEquality() {
|
|
206
|
+
let left = this.parseCompare();
|
|
207
|
+
for (;;) {
|
|
208
|
+
this.skipWs();
|
|
209
|
+
let op = null;
|
|
210
|
+
if (this.match("===")) op = "===";
|
|
211
|
+
else if (this.match("!==")) op = "!==";
|
|
212
|
+
else if (this.match("==")) op = "==";
|
|
213
|
+
else if (this.match("!=")) op = "!=";
|
|
214
|
+
if (!op) break;
|
|
215
|
+
left = { type: "Binary", op, left, right: this.parseCompare() };
|
|
216
|
+
}
|
|
217
|
+
return left;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** @returns {ExprNode} */
|
|
221
|
+
parseCompare() {
|
|
222
|
+
let left = this.parseAdd();
|
|
223
|
+
for (;;) {
|
|
224
|
+
this.skipWs();
|
|
225
|
+
let op = null;
|
|
226
|
+
if (this.match("<=")) op = "<=";
|
|
227
|
+
else if (this.match(">=")) op = ">=";
|
|
228
|
+
else if (this.match("<")) op = "<";
|
|
229
|
+
else if (this.match(">")) op = ">";
|
|
230
|
+
if (!op) break;
|
|
231
|
+
left = { type: "Binary", op, left, right: this.parseAdd() };
|
|
232
|
+
}
|
|
233
|
+
return left;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** @returns {ExprNode} */
|
|
237
|
+
parseAdd() {
|
|
238
|
+
let left = this.parseMul();
|
|
239
|
+
for (;;) {
|
|
240
|
+
this.skipWs();
|
|
241
|
+
const ch = this.peek();
|
|
242
|
+
if (ch !== "+" && ch !== "-") break;
|
|
243
|
+
// `?.` / sayılar ile karışmasın diye yalnızca tek karakter op.
|
|
244
|
+
this.i += 1;
|
|
245
|
+
left = { type: "Binary", op: ch, left, right: this.parseMul() };
|
|
246
|
+
}
|
|
247
|
+
return left;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** @returns {ExprNode} */
|
|
251
|
+
parseMul() {
|
|
252
|
+
let left = this.parseUnary();
|
|
253
|
+
for (;;) {
|
|
254
|
+
this.skipWs();
|
|
255
|
+
const ch = this.peek();
|
|
256
|
+
if (ch !== "*" && ch !== "/" && ch !== "%") break;
|
|
257
|
+
this.i += 1;
|
|
258
|
+
left = { type: "Binary", op: ch, left, right: this.parseUnary() };
|
|
259
|
+
}
|
|
260
|
+
return left;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @returns {ExprNode} */
|
|
264
|
+
parseUnary() {
|
|
265
|
+
this.skipWs();
|
|
266
|
+
if (this.match("!")) {
|
|
267
|
+
return { type: "Unary", op: "!", argument: this.parseUnary() };
|
|
268
|
+
}
|
|
269
|
+
if (this.peek() === "-" && !/[0-9.]/.test(this.source[this.i + 1] ?? "")) {
|
|
270
|
+
this.i += 1;
|
|
271
|
+
return { type: "Unary", op: "-", argument: this.parseUnary() };
|
|
272
|
+
}
|
|
273
|
+
return this.parsePostfix();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** @returns {ExprNode} */
|
|
277
|
+
parsePostfix() {
|
|
278
|
+
let node = this.parsePrimary();
|
|
279
|
+
for (;;) {
|
|
280
|
+
this.skipWs();
|
|
281
|
+
if (this.match("?.")) {
|
|
282
|
+
this.skipWs();
|
|
283
|
+
if (this.peek() === "[") {
|
|
284
|
+
this.i += 1;
|
|
285
|
+
const index = this.parseCond();
|
|
286
|
+
this.eat("]");
|
|
287
|
+
node = { type: "Index", object: node, index, optional: true };
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const property = this.readIdent();
|
|
291
|
+
node = { type: "Member", object: node, property, optional: true };
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (this.peek() === ".") {
|
|
295
|
+
this.i += 1;
|
|
296
|
+
const property = this.readIdent();
|
|
297
|
+
node = { type: "Member", object: node, property, optional: false };
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (this.peek() === "[") {
|
|
301
|
+
this.i += 1;
|
|
302
|
+
const index = this.parseCond();
|
|
303
|
+
this.eat("]");
|
|
304
|
+
node = { type: "Index", object: node, index, optional: false };
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
return node;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** @returns {ExprNode} */
|
|
313
|
+
parsePrimary() {
|
|
314
|
+
this.skipWs();
|
|
315
|
+
const ch = this.peek();
|
|
316
|
+
|
|
317
|
+
if (ch === "(") {
|
|
318
|
+
this.i += 1;
|
|
319
|
+
const node = this.parseCond();
|
|
320
|
+
this.eat(")");
|
|
321
|
+
return node;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (ch === '"' || ch === "'") {
|
|
325
|
+
return this.readString();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (ch === "`") {
|
|
329
|
+
throw this.error("Template literals are not allowed in expressions");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (/[0-9]/.test(ch) || (ch === "." && /[0-9]/.test(this.source[this.i + 1] ?? ""))) {
|
|
333
|
+
return this.readNumber();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (IDENT_START.test(ch)) {
|
|
337
|
+
const name = this.readIdent();
|
|
338
|
+
if (name === "true") return { type: "Literal", value: true, raw: "true" };
|
|
339
|
+
if (name === "false") return { type: "Literal", value: false, raw: "false" };
|
|
340
|
+
if (name === "null") return { type: "Literal", value: null, raw: "null" };
|
|
341
|
+
return { type: "Ident", name };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
throw this.error("Expected expression");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** @returns {string} */
|
|
348
|
+
readIdent() {
|
|
349
|
+
this.skipWs();
|
|
350
|
+
if (!IDENT_START.test(this.peek())) {
|
|
351
|
+
throw this.error("Expected identifier");
|
|
352
|
+
}
|
|
353
|
+
const start = this.i;
|
|
354
|
+
this.i += 1;
|
|
355
|
+
while (this.i < this.end && IDENT_CONT.test(this.source[this.i])) this.i += 1;
|
|
356
|
+
return this.source.slice(start, this.i);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** @returns {LiteralNode} */
|
|
360
|
+
readString() {
|
|
361
|
+
const quote = this.source[this.i];
|
|
362
|
+
this.i += 1;
|
|
363
|
+
let raw = quote;
|
|
364
|
+
let value = "";
|
|
365
|
+
while (this.i < this.end) {
|
|
366
|
+
const ch = this.source[this.i];
|
|
367
|
+
if (ch === "\\") {
|
|
368
|
+
raw += ch + (this.source[this.i + 1] ?? "");
|
|
369
|
+
value += this.source[this.i + 1] ?? "";
|
|
370
|
+
this.i += 2;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (ch === quote) {
|
|
374
|
+
raw += quote;
|
|
375
|
+
this.i += 1;
|
|
376
|
+
return { type: "Literal", value, raw };
|
|
377
|
+
}
|
|
378
|
+
if (ch === "\n") {
|
|
379
|
+
throw this.error("Unterminated string");
|
|
380
|
+
}
|
|
381
|
+
raw += ch;
|
|
382
|
+
value += ch;
|
|
383
|
+
this.i += 1;
|
|
384
|
+
}
|
|
385
|
+
throw this.error("Unterminated string");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** @returns {LiteralNode} */
|
|
389
|
+
readNumber() {
|
|
390
|
+
const start = this.i;
|
|
391
|
+
while (this.i < this.end && /[0-9_]/.test(this.source[this.i])) this.i += 1;
|
|
392
|
+
if (this.source[this.i] === ".") {
|
|
393
|
+
this.i += 1;
|
|
394
|
+
while (this.i < this.end && /[0-9_]/.test(this.source[this.i])) this.i += 1;
|
|
395
|
+
}
|
|
396
|
+
if (this.source[this.i] === "e" || this.source[this.i] === "E") {
|
|
397
|
+
this.i += 1;
|
|
398
|
+
if (this.source[this.i] === "+" || this.source[this.i] === "-") this.i += 1;
|
|
399
|
+
while (this.i < this.end && /[0-9]/.test(this.source[this.i])) this.i += 1;
|
|
400
|
+
}
|
|
401
|
+
const raw = this.source.slice(start, this.i).replace(/_/g, "");
|
|
402
|
+
return { type: "Literal", value: Number(raw), raw };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time `.jsk` derleyici yüzeyi.
|
|
3
|
+
*/
|
|
4
|
+
export { CompileError, indexToLocation } from "./errors.js";
|
|
5
|
+
export { parseExpr, codegenExpr } from "./expr.js";
|
|
6
|
+
export { parseTemplate, isComponentTag } from "./parse.js";
|
|
7
|
+
export { codegen, toPascalCase, normalizeIncludeId } from "./codegen.js";
|
|
8
|
+
export {
|
|
9
|
+
getViewRoots,
|
|
10
|
+
getComponentDirs,
|
|
11
|
+
discoverJskFiles,
|
|
12
|
+
componentNameFromViewId,
|
|
13
|
+
collectKnownComponents,
|
|
14
|
+
} from "./resolve.js";
|
|
15
|
+
export { compileAll, compileSource, ensureTemplatesCompiled } from "./compile-all.js";
|