@xaendar/compiler 0.6.4 → 0.6.5

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.
@@ -55,6 +55,10 @@ var LexerState = /* @__PURE__ */ function(LexerState) {
55
55
  */
56
56
  LexerState["FLOW_CONTROL_BLOCK"] = "flow-control-block";
57
57
  /**
58
+ * Consuming an attribute literal value
59
+ */
60
+ LexerState["ATTRIBUTE_VALUE"] = "attribute-value";
61
+ /**
58
62
  * Dispatching between an expression or literal interpolation after `{`.
59
63
  */
60
64
  LexerState["INTERPOLATION"] = "interpolation";
@@ -99,72 +103,100 @@ var TokenType = /* @__PURE__ */ function(TokenType) {
99
103
  */
100
104
  TokenType[TokenType["TAG_CLOSE_NAME"] = 4] = "TAG_CLOSE_NAME";
101
105
  /**
102
- * An HTML attribute and its optional value.
106
+ * An HTML attribute
103
107
  */
104
108
  TokenType[TokenType["ATTRIBUTE"] = 5] = "ATTRIBUTE";
105
109
  /**
110
+ * An HTML attribute literal value
111
+ */
112
+ TokenType[TokenType["ATTRIBUTE_VALUE"] = 6] = "ATTRIBUTE_VALUE";
113
+ /**
106
114
  * A DOM event binding declared with `@eventName=handler`.
107
115
  */
108
- TokenType[TokenType["EVENT"] = 6] = "EVENT";
116
+ TokenType[TokenType["EVENT"] = 7] = "EVENT";
109
117
  /**
110
118
  * A template-literal interpolation string enclosed in `` {`...`} ``.
111
119
  */
112
- TokenType[TokenType["INTERPOLATION_LITERAL"] = 7] = "INTERPOLATION_LITERAL";
120
+ TokenType[TokenType["INTERPOLATION_LITERAL"] = 8] = "INTERPOLATION_LITERAL";
113
121
  /**
114
122
  * A JavaScript expression interpolation enclosed in `{ }`.
115
123
  */
116
- TokenType[TokenType["INTERPOLATION_EXPRESSION"] = 8] = "INTERPOLATION_EXPRESSION";
124
+ TokenType[TokenType["INTERPOLATION_EXPRESSION"] = 9] = "INTERPOLATION_EXPRESSION";
117
125
  /**
118
126
  * A `@const name = expression;` template-level constant declaration.
119
127
  */
120
- TokenType[TokenType["CONST_DECLARATION"] = 9] = "CONST_DECLARATION";
128
+ TokenType[TokenType["CONST_DECLARATION"] = 10] = "CONST_DECLARATION";
121
129
  /**
122
130
  * Opening keyword of an `@if` directive.
123
131
  */
124
- TokenType[TokenType["IF"] = 10] = "IF";
132
+ TokenType[TokenType["IF"] = 11] = "IF";
125
133
  /**
126
134
  * Opening keyword of a `@for` directive.
127
135
  */
128
- TokenType[TokenType["FOR"] = 11] = "FOR";
136
+ TokenType[TokenType["FOR"] = 12] = "FOR";
129
137
  /**
130
138
  * Opening keyword of an `@else` branch.
131
139
  */
132
- TokenType[TokenType["ELSE"] = 12] = "ELSE";
140
+ TokenType[TokenType["ELSE"] = 13] = "ELSE";
133
141
  /**
134
142
  * Opening keyword of an `@else if` branch.
135
143
  */
136
- TokenType[TokenType["ELSE_IF"] = 13] = "ELSE_IF";
144
+ TokenType[TokenType["ELSE_IF"] = 14] = "ELSE_IF";
137
145
  /**
138
146
  * Opening keyword of a `@switch` directive.
139
147
  */
140
- TokenType[TokenType["SWITCH"] = 14] = "SWITCH";
148
+ TokenType[TokenType["SWITCH"] = 15] = "SWITCH";
141
149
  /**
142
150
  * Opening keyword of a `@case` branch.
143
151
  */
144
- TokenType[TokenType["CASE"] = 15] = "CASE";
152
+ TokenType[TokenType["CASE"] = 16] = "CASE";
145
153
  /**
146
154
  * Opening keyword of a `@default` branch.
147
155
  */
148
- TokenType[TokenType["DEFAULT"] = 16] = "DEFAULT";
156
+ TokenType[TokenType["DEFAULT"] = 17] = "DEFAULT";
149
157
  /**
150
158
  * The condition expression `(...)` associated with a flow-control directive.
151
159
  */
152
- TokenType[TokenType["CONDITION"] = 17] = "CONDITION";
160
+ TokenType[TokenType["CONDITION"] = 18] = "CONDITION";
153
161
  /**
154
162
  * The opening `{` of a flow-control block body.
155
163
  */
156
- TokenType[TokenType["BLOCK_OPEN"] = 18] = "BLOCK_OPEN";
164
+ TokenType[TokenType["BLOCK_OPEN"] = 19] = "BLOCK_OPEN";
157
165
  /**
158
166
  * The closing `}` of a flow-control block body.
159
167
  */
160
- TokenType[TokenType["BLOCK_CLOSE"] = 19] = "BLOCK_CLOSE";
168
+ TokenType[TokenType["BLOCK_CLOSE"] = 20] = "BLOCK_CLOSE";
161
169
  /**
162
170
  * Sentinel token emitted when the end of the input is reached.
163
171
  */
164
- TokenType[TokenType["EOF"] = 20] = "EOF";
172
+ TokenType[TokenType["EOF"] = 21] = "EOF";
165
173
  return TokenType;
166
174
  }({});
167
175
  //#endregion
176
+ //#region ../packages/compiler/src/lexer/states/attribute-value.state.ts
177
+ function consumeAttributeValue(cursor, _context) {
178
+ let read = true;
179
+ let value = "";
180
+ let retVal;
181
+ while (read) switch (cursor.peek()) {
182
+ case 34:
183
+ cursor.advance();
184
+ read = false;
185
+ retVal = {
186
+ state: LexerState.TAG_BODY,
187
+ tokens: [{
188
+ type: TokenType.ATTRIBUTE_VALUE,
189
+ parts: [value]
190
+ }]
191
+ };
192
+ break;
193
+ default:
194
+ cursor.advance();
195
+ value = `${value}${cursor.currentChar.value}`;
196
+ }
197
+ return retVal;
198
+ }
199
+ //#endregion
168
200
  //#region ../packages/compiler/src/lexer/states/attribute.state.ts
169
201
  /**
170
202
  * Consumes an attribute name and optional value from the current position,
@@ -181,8 +213,8 @@ function consumeAttribute(cursor, _context) {
181
213
  let retVal;
182
214
  while (read) switch (cursor.peek()) {
183
215
  case 32:
184
- case 47:
185
- case 62:
216
+ cursor.advance();
217
+ read = false;
186
218
  retVal = {
187
219
  state: LexerState.TAG_BODY,
188
220
  tokens: [{
@@ -190,18 +222,23 @@ function consumeAttribute(cursor, _context) {
190
222
  parts: [attribute]
191
223
  }]
192
224
  };
193
- read = false;
194
225
  break;
195
- case 123:
226
+ case 61:
227
+ cursor.advance();
228
+ if (cursor.peek() !== 34) {
229
+ const { row, column } = cursor.position;
230
+ throw new Error(`Attribute value must start with double quotes '"'.Row ${row} Col ${column}`);
231
+ }
232
+ cursor.advance();
233
+ read = false;
196
234
  retVal = {
197
- state: LexerState.INTERPOLATION,
235
+ state: cursor.peek() === 123 ? LexerState.INTERPOLATION : LexerState.ATTRIBUTE_VALUE,
236
+ pushState: true,
198
237
  tokens: [{
199
238
  type: TokenType.ATTRIBUTE,
200
239
  parts: [attribute]
201
- }],
202
- pushState: true
240
+ }]
203
241
  };
204
- read = false;
205
242
  break;
206
243
  default:
207
244
  cursor.advance();
@@ -319,6 +356,27 @@ function consumeConstDeclaration(cursor, _context) {
319
356
  return retVal;
320
357
  }
321
358
  //#endregion
359
+ //#region ../packages/compiler/src/lexer/states/default-flow-control-condition.state.ts
360
+ /**
361
+ * Consumes the condition expression `(...)` of a flow-control directive,
362
+ * handling nested parentheses correctly. Emits a CONDITION token with the
363
+ * raw expression string and transitions to FLOW_CONTROL_BLOCK.
364
+ *
365
+ * @param cursor The lexer cursor positioned at the opening `(`.
366
+ * @param _context Unused lexer context.
367
+ * @returns Transition result with the CONDITION token and the FLOW_CONTROL_BLOCK state.
368
+ */
369
+ function consumeDefaultFlowControlCondition(cursor, _context) {
370
+ return {
371
+ state: LexerState.FLOW_CONTROL_BLOCK,
372
+ tokens: [{
373
+ type: TokenType.CONDITION,
374
+ parts: [consumeFlowControlCondition(cursor, _context)]
375
+ }],
376
+ popState: true
377
+ };
378
+ }
379
+ //#endregion
322
380
  //#region ../packages/compiler/src/lexer/states/event.state.ts
323
381
  /**
324
382
  * Consumes a DOM event binding starting with `@` and reads until a delimiter
@@ -337,7 +395,6 @@ function consumeEvent(cursor, _context) {
337
395
  case 32:
338
396
  case 47:
339
397
  case 62:
340
- if (!event.includes("=")) event = `${event}=on${event[0].toUpperCase()}${event.slice(1)}($event)`;
341
398
  retVal = {
342
399
  state: LexerState.TAG_BODY,
343
400
  tokens: [{
@@ -354,50 +411,6 @@ function consumeEvent(cursor, _context) {
354
411
  return retVal;
355
412
  }
356
413
  //#endregion
357
- //#region ../packages/compiler/src/lexer/states/flow-control-block.state.ts
358
- /**
359
- * Consumes the opening `{` of a flow control block body,
360
- * skipping any leading whitespace before it.
361
- *
362
- * Emits a BLOCK_OPEN token and transitions to TEXT,
363
- * pushing FLOW_CONTROL_BLOCK onto the state stack.
364
- * This allows consumeText to later recognise the matching `}` as a BLOCK_CLOSE
365
- * rather than as an interpolation boundary.
366
- *
367
- * Used by: @if, @for, @switch, @case, @else, @default
368
- */
369
- function consumeFlowControlBlock(cursor, _context) {
370
- cursor.skipSpaces();
371
- if (cursor.peek() !== 123) throw new Error(`Expected '{' but got '${String.fromCharCode(cursor.peek())}' at row ${cursor.position.row}, col ${cursor.position.column}`);
372
- cursor.advance();
373
- return {
374
- state: LexerState.TEXT,
375
- tokens: [{ type: TokenType.BLOCK_OPEN }],
376
- pushState: true
377
- };
378
- }
379
- //#endregion
380
- //#region ../packages/compiler/src/lexer/states/default-flow-control-condition.state.ts
381
- /**
382
- * Consumes the condition expression `(...)` of a flow-control directive,
383
- * handling nested parentheses correctly. Emits a CONDITION token with the
384
- * raw expression string and transitions to FLOW_CONTROL_BLOCK.
385
- *
386
- * @param cursor The lexer cursor positioned at the opening `(`.
387
- * @param _context Unused lexer context.
388
- * @returns Transition result with the CONDITION token and the FLOW_CONTROL_BLOCK state.
389
- */
390
- function consumeDefaultFlowControlCondition(cursor, _context) {
391
- return {
392
- state: LexerState.FLOW_CONTROL_BLOCK,
393
- tokens: [{
394
- type: TokenType.CONDITION,
395
- parts: [consumeFlowControlCondition(cursor, _context)]
396
- }],
397
- popState: true
398
- };
399
- }
400
- //#endregion
401
414
  //#region ../packages/compiler/src/lexer/states/flow-control.ts
402
415
  /**
403
416
  * Dispatches on a `@keyword` to determine which flow-control directive begins here.
@@ -465,6 +478,29 @@ function consumeFlowControl(cursor, _context) {
465
478
  return retVal;
466
479
  }
467
480
  //#endregion
481
+ //#region ../packages/compiler/src/lexer/states/flow-control-block.state.ts
482
+ /**
483
+ * Consumes the opening `{` of a flow control block body,
484
+ * skipping any leading whitespace before it.
485
+ *
486
+ * Emits a BLOCK_OPEN token and transitions to TEXT,
487
+ * pushing FLOW_CONTROL_BLOCK onto the state stack.
488
+ * This allows consumeText to later recognise the matching `}` as a BLOCK_CLOSE
489
+ * rather than as an interpolation boundary.
490
+ *
491
+ * Used by: @if, @for, @switch, @case, @else, @default
492
+ */
493
+ function consumeFlowControlBlock(cursor, _context) {
494
+ cursor.skipSpaces();
495
+ if (cursor.peek() !== 123) throw new Error(`Expected '{' but got '${String.fromCharCode(cursor.peek())}' at row ${cursor.position.row}, col ${cursor.position.column}`);
496
+ cursor.advance();
497
+ return {
498
+ state: LexerState.TEXT,
499
+ tokens: [{ type: TokenType.BLOCK_OPEN }],
500
+ pushState: true
501
+ };
502
+ }
503
+ //#endregion
468
504
  //#region ../packages/compiler/src/lexer/states/interpolation-expression.state.ts
469
505
  /**
470
506
  * Consumes a JavaScript expression interpolation `{ expression }`, tracking nested
@@ -494,8 +530,11 @@ function consumeInterpolationExpression(cursor, context) {
494
530
  let state;
495
531
  switch (previousState) {
496
532
  case LexerState.ATTRIBUTE:
497
- const lastToken = context.tokens[context.tokens.length - 1];
498
- if (lastToken?.type === TokenType.ATTRIBUTE && !lastToken.parts[0]) lastToken.parts[0] = `${interpolation}=`;
533
+ if (cursor.peek() !== 34) {
534
+ const { row, column } = cursor.position;
535
+ throw new Error(`Interpolation must be end with double quotes '"' Found ${String.fromCharCode(cursor.peek())}. \nRow ${row} Col ${column}`);
536
+ }
537
+ cursor.advance();
499
538
  state = LexerState.TAG_BODY;
500
539
  break;
501
540
  case LexerState.TEXT: state = LexerState.TEXT;
@@ -1014,6 +1053,7 @@ var Lexer = class {
1014
1053
  [LexerState.TAG_OPEN_END]: consumeTagOpenEnd,
1015
1054
  [LexerState.TAG_CLOSE]: consumeTagClose,
1016
1055
  [LexerState.ATTRIBUTE]: consumeAttribute,
1056
+ [LexerState.ATTRIBUTE_VALUE]: consumeAttributeValue,
1017
1057
  [LexerState.FLOW_CONTROL]: consumeFlowControl,
1018
1058
  [LexerState.FLOW_CONTROL_CONDITION]: consumeDefaultFlowControlCondition,
1019
1059
  [LexerState.CASE_FLOW_CONTROL_CONDITION]: consumeCaseFlowControlCondition,
@@ -1415,22 +1455,21 @@ function parseInterpolation(cursor, _parseNode, token) {
1415
1455
  */
1416
1456
  function parseAttribute(cursor, parseNode, token) {
1417
1457
  cursor.advance();
1418
- const raw = token.parts[0];
1419
- if (!raw.includes("=")) return {
1420
- name: raw,
1458
+ const name = token.parts[0];
1459
+ const nextToken = cursor.peek();
1460
+ if (nextToken.type === TokenType.ATTRIBUTE || nextToken.type === TokenType.TAG_CLOSE_NAME || nextToken.type === TokenType.EVENT) return {
1461
+ name,
1421
1462
  value: "true"
1422
1463
  };
1423
- const [name, value] = raw.split("=");
1424
- if (!name) throw new Error(`[Parser] Attribute name missing in: ${raw}`);
1425
- const nextToken = cursor.peek();
1426
1464
  if (nextToken.type === TokenType.INTERPOLATION_EXPRESSION || nextToken.type === TokenType.INTERPOLATION_LITERAL) return {
1427
1465
  name,
1428
1466
  value: parseInterpolation(cursor, parseNode, nextToken)
1429
1467
  };
1430
- if (!value) throw new Error(`[Parser] Attribute value missing for ${name} in: ${raw}`);
1468
+ if (nextToken.type !== TokenType.ATTRIBUTE_VALUE) throw new Error(`[Parser] Attribute value missing for ${name} in: ${name}`);
1469
+ cursor.advance();
1431
1470
  return {
1432
1471
  name,
1433
- value: value.replace(/^['']|['']$/g, "")
1472
+ value: nextToken.parts[0]
1434
1473
  };
1435
1474
  }
1436
1475
  //#endregion
@@ -2189,6 +2228,9 @@ function getElementIdentifier(node, parentNode, index) {
2189
2228
  function getTextIdentifier(parentNode, index, prefix = "text") {
2190
2229
  return (parentNode !== "this._root" ? `${parentNode}_${prefix}${index}` : `${prefix}${index}`).replace(/-/g, "_");
2191
2230
  }
2231
+ function getBlockIdentifier(parentNode, index, prefix) {
2232
+ return (parentNode !== "this._root" ? `${parentNode}_${prefix}${index}` : `${prefix}${index}`).replace(/-/g, "_");
2233
+ }
2192
2234
  //#endregion
2193
2235
  //#region ../packages/compiler/src/render-generator/states/process-const-declaration.state.ts
2194
2236
  /**
@@ -2242,10 +2284,19 @@ function processElement(node, nodeName, parentNode, context) {
2242
2284
  * @returns Array of generated code lines, one per attribute.
2243
2285
  */
2244
2286
  function mapAttributes(attributes, nodeName, context) {
2245
- return attributes?.map((attr) => {
2246
- const value = attr.value;
2247
- return typeof value === "string" ? `${nodeName}.setAttribute('${attr.name}', ${value});` : `unwatchFns.push(effect(() => ${nodeName}.setAttribute('${attr.name}', ${resolveExpression(value.expression, context)})));`;
2248
- }) ?? [];
2287
+ const literalsAttributes = new Array();
2288
+ const bindingsAttributes = new Array();
2289
+ attributes?.forEach(({ name, value }) => typeof value === "string" ? literalsAttributes.push({
2290
+ name,
2291
+ value
2292
+ }) : bindingsAttributes.push({
2293
+ name,
2294
+ value
2295
+ }));
2296
+ const mappedAttributes = literalsAttributes.map((attr) => `${nodeName}.setAttribute("${attr.name}", "${attr.value}");`);
2297
+ const mappedBindingAttributes = bindingsAttributes.map((attr) => `effect(() => ${nodeName}.setAttribute("${attr.name}", ${resolveExpression(attr.value.expression, context)})),`);
2298
+ if (mappedBindingAttributes.length) mappedAttributes.push(`unwatchFns.push(`, ...indent(mappedBindingAttributes), `);`);
2299
+ return mappedAttributes;
2249
2300
  }
2250
2301
  /**
2251
2302
  * Generates code that attaches event listeners to a DOM element.
@@ -2313,19 +2364,12 @@ function processFor(node, nodeName, parentNode, parentContext) {
2313
2364
  evenName,
2314
2365
  oddName
2315
2366
  ], parentContext);
2316
- functionsToProcess.set(`for_${nodeName}`, {
2317
- code: [
2318
- `const ${node.itemAlias} = ${itemsName}[${counterName}];`,
2319
- `const ${indexName} = ${counterName};`,
2320
- `const ${firstName} = ${counterName} === 0;`,
2321
- `const ${lastName} = ${counterName} === ${itemsName}.length - 1;`,
2322
- `const ${evenName} = ${counterName} % 2 === 0;`,
2323
- `const ${oddName} = !${evenName};`,
2324
- ...node.children.flatMap((child, i) => processNode(child, `${nodeName}_${i}`, parentNode, forContext))
2325
- ],
2367
+ const forKey = getBlockIdentifier(parentNode, nodeName, "for");
2368
+ functionsToProcess.set(forKey, {
2369
+ code: [`const { ${node.itemAlias}, ${indexName}, ${firstName}, ${lastName}, ${evenName}, ${oddName} } = _iterationVariables(${itemsName}, ${counterName});`, ...node.children.flatMap((child, i) => processNode(child, `${nodeName}_${i}`, parentNode, forContext))],
2326
2370
  args: [itemsName, counterName]
2327
2371
  });
2328
- mainBlock.push("(() => {", ...indent("let localUnwatchFns = [];", "const unwatch = () => {", ...indent("unwatchFns = unwatchFns.filter(fn => !localUnwatchFns.includes(fn));", "localUnwatchFns?.forEach(fn => fn());", "localUnwatchFns = [];"), "};", "unwatchFns.push(", ...indent("effect(() => {", ...indent("unwatch();", `const ${itemsName} = ${iterableExpr};`, "Signal.subtle.untrack(() => {", ...indent(`for (let ${counterName} = 0; ${counterName} < ${itemsName}.length; ${counterName}++) {`, ...indent(`localUnwatchFns.push(...this.for_${nodeName}(${itemsName}, ${counterName}));`, "unwatchFns.push(...localUnwatchFns);"), "}"), "});"), "})"), ");"), "})();");
2372
+ mainBlock.push(`_for(unwatchFns, () => ${iterableExpr}, this.${forKey}.bind(this));`);
2329
2373
  return {
2330
2374
  mainBlock,
2331
2375
  fns: functionsToProcess
@@ -2362,31 +2406,42 @@ function processIf(node, nodeName, parentNode, context) {
2362
2406
  const ifContext = new Context([], context);
2363
2407
  const functionsToProcess = /* @__PURE__ */ new Map();
2364
2408
  const mainBlock = new Array();
2365
- const ifKey = `if_${nodeName}`;
2366
- mainBlock.push(`if (${resolveExpression(node.conditionNode, context)}) {`, ...indent(`checkAndUpdateState(0, this.${ifKey}.bind(this));`), "}");
2409
+ const ifKey = getBlockIdentifier(parentNode, nodeName, "if");
2410
+ mainBlock.push({
2411
+ condition: resolveExpression(node.conditionNode, context),
2412
+ block: `this.${ifKey}.bind(this)`
2413
+ });
2367
2414
  functionsToProcess.set(ifKey, processConsequent(node, nodeName, parentNode, ifContext));
2368
2415
  let alt = node.alternate;
2369
2416
  let index = 0;
2370
2417
  while (alt?.type === ASTNodeType.ElseIf) {
2371
2418
  const elseIfContext = new Context([], context);
2372
- const keyElseIf = `elseIf_${nodeName}_${index}`;
2373
- mainBlock[mainBlock.length - 1] += ` else if (${resolveExpression(alt.conditionNode, context)}) {`;
2374
- mainBlock.push(...indent(`checkAndUpdateState(${++index}, this.${keyElseIf}.bind(this));`), "}");
2419
+ const keyElseIf = getBlockIdentifier(parentNode, `${nodeName}_${index}`, "elseIf");
2420
+ const conditionNode = alt.conditionNode;
2421
+ mainBlock.push({
2422
+ condition: resolveExpression(conditionNode, context),
2423
+ block: `this.${keyElseIf}.bind(this)`
2424
+ });
2375
2425
  functionsToProcess.set(keyElseIf, processConsequent(alt, nodeName, parentNode, elseIfContext));
2376
2426
  alt = alt.alternate;
2377
2427
  }
2378
2428
  if (alt) {
2379
2429
  const elseContext = new Context([], context);
2380
- const keyElse = `else_${nodeName}`;
2381
- mainBlock[mainBlock.length - 1] += " else {";
2382
- mainBlock.push(...indent(`checkAndUpdateState(${++index}, this.${keyElse}.bind(this));`), "}");
2430
+ const keyElse = getBlockIdentifier(parentNode, nodeName, "else");
2431
+ mainBlock.push({ block: `this.${keyElse}.bind(this)` });
2383
2432
  functionsToProcess.set(keyElse, processConsequent(alt, nodeName, parentNode, elseContext));
2384
2433
  }
2385
2434
  return {
2386
2435
  mainBlock: [
2387
- "(() => {",
2388
- ...indent("let state;", "let localUnwatchFns = []", "const checkAndUpdateState = (newState, fn) => {", ...indent("if (state === newState) {", ...indent("return;"), "}", "state = newState;", "unwatch();", "localUnwatchFns = Signal.subtle.untrack(fn);", "unwatchFns.push(...localUnwatchFns);"), "};", "const unwatch = () => {", ...indent("unwatchFns = unwatchFns.filter(fn => !localUnwatchFns.includes(fn));", "localUnwatchFns?.forEach(fn => fn());", "localUnwatchFns = [];"), "}", "unwatchFns.push(", ...indent("effect(() => {", ...indent(...mainBlock), "})"), ");"),
2389
- "})();"
2436
+ "_if(unwatchFns, [",
2437
+ ...indent(mainBlock.map(({ condition, block }) => {
2438
+ return [
2439
+ "{",
2440
+ ...indent(condition ? [`condition: ${condition.toString()},`, `block: ${block.toString()}`] : [`block: ${block.toString()}`]),
2441
+ "},"
2442
+ ];
2443
+ }).flat()),
2444
+ "])"
2390
2445
  ],
2391
2446
  fns: functionsToProcess
2392
2447
  };
@@ -2398,31 +2453,38 @@ function processConsequent(node, nodeName, parentNode, context) {
2398
2453
  //#region ../packages/compiler/src/render-generator/states/process-switch.state.ts
2399
2454
  /**
2400
2455
  * Generates code for a `@switch` node.
2401
- * Emits a `switch (expression) { case ...: { ... break; } ... }` block.
2456
+ * Emits a `_switch(unwatchFns, () => expression, [...])` call that delegates
2457
+ * to the reactive `_switch` runtime utility.
2402
2458
  *
2403
2459
  * @param node The `SwitchNode` to process.
2404
2460
  * @param nodeName Base variable name prefix for child nodes.
2405
2461
  * @param parentNode Variable name of the parent DOM node.
2406
2462
  * @param context Current render scope context.
2407
- * @param processNode Recursive node processor function.
2408
2463
  * @returns Array of generated code lines.
2409
2464
  */
2410
2465
  function processSwitch(node, nodeName, parentNode, context) {
2411
- const mainBlock = new Array();
2412
2466
  const functionsToProcess = /* @__PURE__ */ new Map();
2413
- mainBlock.push(`switch (${resolveExpression(node.expression, context)}) {`);
2467
+ const blocks = new Array();
2414
2468
  node.cases.forEach((caseNode, i) => {
2415
2469
  const caseContext = new Context([], context);
2416
- const caseName = caseNode.condition ? `case_${nodeName}_${i}` : `default_${nodeName}`;
2470
+ const caseName = caseNode.condition ? getBlockIdentifier(parentNode, `${nodeName}_${i}`, "case") : getBlockIdentifier(parentNode, nodeName, "default");
2417
2471
  functionsToProcess.set(caseName, caseNode.children.map((child, i) => processNode(child, `${nodeName}_${i}_${i}`, parentNode, caseContext)).flat());
2418
- mainBlock.push(...indent(...!caseNode.condition ? ["default: {"] : caseNode.condition.map((cond, i, arr) => `case ${cond}:${i === arr.length - 1 ? " {" : ""}`), ...indent(`localUnwatchFns = Signal.subtle.untrack(this.${caseName}.bind(this));`, "unwatchFns.push(...localUnwatchFns);", "break;"), "}"));
2472
+ blocks.push({
2473
+ condition: caseNode.condition,
2474
+ block: `this.${caseName}.bind(this)`
2475
+ });
2419
2476
  });
2420
- mainBlock.push("}");
2421
2477
  return {
2422
2478
  mainBlock: [
2423
- "(() => {",
2424
- ...indent("let localUnwatchFns = []", "const unwatch = () => {", ...indent("unwatchFns = unwatchFns.filter(fn => !localUnwatchFns.includes(fn));", "localUnwatchFns?.forEach(fn => fn());", "localUnwatchFns = [];"), "}", "unwatchFns.push(", ...indent("effect(() => {", ...indent("unwatch();", ...mainBlock), "})"), ");"),
2425
- "})();"
2479
+ `_switch(unwatchFns, () => ${resolveExpression(node.expression, context)}, [`,
2480
+ ...indent(blocks.map(({ condition, block }) => {
2481
+ return [
2482
+ "{",
2483
+ ...indent(condition ? [`condition: [${condition.join(", ")}],`, `block: ${block}`] : [`condition: null,`, `block: ${block}`]),
2484
+ "},"
2485
+ ];
2486
+ }).flat()),
2487
+ "])"
2426
2488
  ],
2427
2489
  fns: functionsToProcess
2428
2490
  };
@@ -2462,11 +2524,23 @@ function generateRenderFunction(ast, cssVariableName) {
2462
2524
  const context = new Context();
2463
2525
  const renderFunctions = ["_render() {"];
2464
2526
  if (cssVariableName) renderFunctions.push(...indent(`this._root.adoptedStyleSheets = [${cssVariableName}];`));
2465
- renderFunctions.push(...indent("let unwatchFns = [];", ...indent(...ast.map((node, i) => [...processNode(node, i.toString(), ROOT_NODE, context)]).flat()), "return unwatchFns;"), "}");
2527
+ renderFunctions.push(...indent([
2528
+ "let unwatchFns = [];",
2529
+ ...ast.map((node, i) => [...processNode(node, i.toString(), ROOT_NODE, context)]).flat(),
2530
+ "return unwatchFns;"
2531
+ ]), "}");
2466
2532
  while (nodeToProcess.size > 0) {
2467
2533
  const [key, fnData] = nodeToProcess.entries().next().value;
2468
- if ("args" in fnData) renderFunctions.push(`${key}(${fnData.args.join(", ")}) {`, ...indent("let unwatchFns = [];", ...indent(...fnData.fn(...fnData.args)), "return unwatchFns;"), "}");
2469
- else renderFunctions.push(`${key}() {`, ...indent("let unwatchFns = [];", ...indent(...fnData.fn()), "return unwatchFns;", "}"));
2534
+ if ("args" in fnData) renderFunctions.push(`${key}(${fnData.args.join(", ")}) {`, ...indent([
2535
+ "let unwatchFns = [];",
2536
+ ...fnData.fn(...fnData.args),
2537
+ "return unwatchFns;"
2538
+ ]), "}");
2539
+ else renderFunctions.push(`${key}() {`, ...indent([
2540
+ "let unwatchFns = [];",
2541
+ ...fnData.fn(),
2542
+ "return unwatchFns;"
2543
+ ]), "}");
2470
2544
  nodeToProcess.delete(key);
2471
2545
  }
2472
2546
  return renderFunctions.join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaendar/compiler",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "A library for transpiling Xaendar Templates into JavaScript code",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -16,8 +16,8 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "@xaendar/common": "0.6.4",
20
- "@xaendar/types": "0.6.4",
19
+ "@xaendar/common": "0.6.5",
20
+ "@xaendar/types": "0.6.5",
21
21
  "typescript": "^6.0.3"
22
22
  }
23
23
  }