@vertz/ui-compiler 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +84 -26
  2. package/package.json +4 -6
package/dist/index.js CHANGED
@@ -263,11 +263,12 @@ class JsxAnalyzer {
263
263
  const deps = identifiers.filter((id) => reactiveNames.has(id));
264
264
  const uniqueDeps = [...new Set(deps)];
265
265
  const hasSignalApiAccess = containsSignalApiPropertyAccess(expr, signalApiVars, plainPropVars, fieldSignalPropVars);
266
+ const hasSignalApiRef = containsSignalApiReference(expr, signalApiVars);
266
267
  const hasReactiveSourceAccess = containsReactiveSourceAccess(expr, reactiveSourceVars);
267
268
  results.push({
268
269
  start: expr.getStart(),
269
270
  end: expr.getEnd(),
270
- reactive: uniqueDeps.length > 0 || hasSignalApiAccess || hasReactiveSourceAccess,
271
+ reactive: uniqueDeps.length > 0 || hasSignalApiAccess || hasSignalApiRef || hasReactiveSourceAccess,
271
272
  deps: uniqueDeps
272
273
  });
273
274
  }
@@ -309,6 +310,19 @@ function containsSignalApiPropertyAccess(node, signalApiVars, plainPropVars, fie
309
310
  }
310
311
  return false;
311
312
  }
313
+ function containsSignalApiReference(node, signalApiVars) {
314
+ if (signalApiVars.size === 0)
315
+ return false;
316
+ const callExprs = node.getDescendantsOfKind(SyntaxKind4.CallExpression);
317
+ for (const call of callExprs) {
318
+ for (const arg of call.getArguments()) {
319
+ if (arg.isKind(SyntaxKind4.Identifier) && signalApiVars.has(arg.getText())) {
320
+ return true;
321
+ }
322
+ }
323
+ }
324
+ return false;
325
+ }
312
326
  function containsReactiveSourceAccess(node, reactiveSourceVars) {
313
327
  if (reactiveSourceVars.size === 0)
314
328
  return false;
@@ -527,7 +541,8 @@ class ReactivityAnalyzer {
527
541
  consts.set(syntheticName, {
528
542
  start: decl.getStart(),
529
543
  end: decl.getEnd(),
530
- deps: []
544
+ deps: [],
545
+ propertyAccesses: new Map
531
546
  });
532
547
  }
533
548
  }
@@ -539,12 +554,21 @@ class ReactivityAnalyzer {
539
554
  if (signalApiConfig && syntheticName) {
540
555
  const isSignalProp = signalApiConfig.signalProperties.has(propName);
541
556
  const deps2 = isSignalProp ? [syntheticName] : [];
542
- const entry2 = { start: decl.getStart(), end: decl.getEnd(), deps: deps2 };
557
+ const propAccesses = new Map;
558
+ if (isSignalProp) {
559
+ propAccesses.set(syntheticName, new Set([propName]));
560
+ }
561
+ const entry2 = {
562
+ start: decl.getStart(),
563
+ end: decl.getEnd(),
564
+ deps: deps2,
565
+ propertyAccesses: propAccesses
566
+ };
543
567
  consts.set(bindingName, entry2);
544
568
  destructuredFromMap.set(bindingName, syntheticName);
545
569
  } else {
546
- const deps2 = init ? collectIdentifierRefs(init) : [];
547
- const entry2 = { start: decl.getStart(), end: decl.getEnd(), deps: deps2 };
570
+ const { refs: deps2, propertyAccesses: propertyAccesses2 } = init ? collectDeps(init) : { refs: [], propertyAccesses: new Map };
571
+ const entry2 = { start: decl.getStart(), end: decl.getEnd(), deps: deps2, propertyAccesses: propertyAccesses2 };
548
572
  if (isLet) {
549
573
  lets.set(bindingName, entry2);
550
574
  } else if (isConst) {
@@ -555,8 +579,8 @@ class ReactivityAnalyzer {
555
579
  continue;
556
580
  }
557
581
  const name = decl.getName();
558
- const deps = init ? collectIdentifierRefs(init) : [];
559
- const entry = { start: decl.getStart(), end: decl.getEnd(), deps };
582
+ const { refs: deps, propertyAccesses } = init ? collectDeps(init) : { refs: [], propertyAccesses: new Map };
583
+ const entry = { start: decl.getStart(), end: decl.getEnd(), deps, propertyAccesses };
560
584
  let callInit = init;
561
585
  if (callInit?.isKind(SyntaxKind6.NonNullExpression)) {
562
586
  callInit = callInit.getExpression();
@@ -614,7 +638,20 @@ class ReactivityAnalyzer {
614
638
  for (const [name, info] of consts) {
615
639
  if (computeds.has(name))
616
640
  continue;
617
- const dependsOnReactive = info.deps.some((dep) => signals.has(dep) || computeds.has(dep) || signalApiVars.has(dep) || reactiveSourceVars.has(dep));
641
+ if (signalApiVars.has(name))
642
+ continue;
643
+ const dependsOnReactive = info.deps.some((dep) => {
644
+ if (signals.has(dep) || computeds.has(dep) || reactiveSourceVars.has(dep))
645
+ return true;
646
+ const apiConfig = signalApiVars.get(dep);
647
+ if (apiConfig) {
648
+ const accessed = info.propertyAccesses.get(dep);
649
+ if (!accessed || accessed.size === 0)
650
+ return false;
651
+ return [...accessed].some((prop) => apiConfig.signalProperties.has(prop));
652
+ }
653
+ return false;
654
+ });
618
655
  if (dependsOnReactive) {
619
656
  computeds.add(name);
620
657
  changed = true;
@@ -678,9 +715,27 @@ function addIdentifiers(node, refs) {
678
715
  addIdentifiers(child, refs);
679
716
  }
680
717
  }
681
- function collectIdentifierRefs(node) {
718
+ function collectDeps(node) {
682
719
  const refs = [];
720
+ const propertyAccesses = new Map;
683
721
  const walk = (n) => {
722
+ if (n.isKind(SyntaxKind6.PropertyAccessExpression)) {
723
+ const expr = n.getExpression();
724
+ const propName = n.getName();
725
+ if (expr.isKind(SyntaxKind6.Identifier)) {
726
+ const varName = expr.getText();
727
+ refs.push(varName);
728
+ let props = propertyAccesses.get(varName);
729
+ if (!props) {
730
+ props = new Set;
731
+ propertyAccesses.set(varName, props);
732
+ }
733
+ props.add(propName);
734
+ } else {
735
+ walk(expr);
736
+ }
737
+ return;
738
+ }
684
739
  if (n.isKind(SyntaxKind6.Identifier)) {
685
740
  refs.push(n.getText());
686
741
  }
@@ -689,7 +744,7 @@ function collectIdentifierRefs(node) {
689
744
  }
690
745
  };
691
746
  walk(node);
692
- return refs;
747
+ return { refs, propertyAccesses };
693
748
  }
694
749
  function buildImportAliasMap(sourceFile) {
695
750
  const signalApiAliases = new Map;
@@ -1110,6 +1165,9 @@ function transformComputedReads(source, bodyNode, computeds) {
1110
1165
 
1111
1166
  // src/transformers/jsx-transformer.ts
1112
1167
  import { SyntaxKind as SyntaxKind10 } from "ts-morph";
1168
+ function isLiteralExpression(node) {
1169
+ return node.isKind(SyntaxKind10.StringLiteral) || node.isKind(SyntaxKind10.NumericLiteral) || node.isKind(SyntaxKind10.TrueKeyword) || node.isKind(SyntaxKind10.FalseKeyword) || node.isKind(SyntaxKind10.NullKeyword) || node.isKind(SyntaxKind10.NoSubstitutionTemplateLiteral);
1170
+ }
1113
1171
  function cleanJsxText(raw) {
1114
1172
  if (!raw.includes(`
1115
1173
  `) && !raw.includes("\r")) {
@@ -1220,7 +1278,7 @@ function transformJsxElement(node, reactiveNames, jsxMap, source, formVarNames =
1220
1278
  for (const attr of attrs) {
1221
1279
  if (!attr.isKind(SyntaxKind10.JsxAttribute))
1222
1280
  continue;
1223
- const attrStmt = processAttribute(attr, elVar, jsxMap, source);
1281
+ const attrStmt = processAttribute(attr, elVar, source);
1224
1282
  if (attrStmt)
1225
1283
  statements.push(attrStmt);
1226
1284
  }
@@ -1266,7 +1324,7 @@ function transformSelfClosingElement(node, reactiveNames, jsxMap, source, formVa
1266
1324
  for (const attr of attrs) {
1267
1325
  if (!attr.isKind(SyntaxKind10.JsxAttribute))
1268
1326
  continue;
1269
- const attrStmt = processAttribute(attr, elVar, jsxMap, source);
1327
+ const attrStmt = processAttribute(attr, elVar, source);
1270
1328
  if (attrStmt)
1271
1329
  statements.push(attrStmt);
1272
1330
  }
@@ -1295,7 +1353,7 @@ ${statements.map((s) => ` ${s};`).join(`
1295
1353
  return ${fragVar};
1296
1354
  })()`;
1297
1355
  }
1298
- function processAttribute(attr, elVar, jsxMap, source) {
1356
+ function processAttribute(attr, elVar, source) {
1299
1357
  if (!attr.isKind(SyntaxKind10.JsxAttribute))
1300
1358
  return null;
1301
1359
  const attrName = attr.getNameNode().getText();
@@ -1315,10 +1373,9 @@ function processAttribute(attr, elVar, jsxMap, source) {
1315
1373
  return `${elVar}.setAttribute(${JSON.stringify(attrName)}, ${init.getText()})`;
1316
1374
  }
1317
1375
  if (init.isKind(SyntaxKind10.JsxExpression)) {
1318
- const exprInfo = jsxMap.get(init.getStart());
1319
1376
  const exprNode = init.getExpression();
1320
1377
  const exprText = exprNode ? source.slice(exprNode.getStart(), exprNode.getEnd()) : "";
1321
- if (exprInfo?.reactive) {
1378
+ if (exprNode && !isLiteralExpression(exprNode)) {
1322
1379
  return `__attr(${elVar}, ${JSON.stringify(attrName)}, () => ${exprText})`;
1323
1380
  }
1324
1381
  return `{ const __v = ${exprText}; if (__v != null && __v !== false) ${elVar}.setAttribute(${JSON.stringify(attrName)}, __v === true ? "" : __v); }`;
@@ -1337,18 +1394,20 @@ function transformChild(child, reactiveNames, jsxMap, parentVar, source, formVar
1337
1394
  const exprNode = child.getExpression();
1338
1395
  if (!exprNode)
1339
1396
  return null;
1340
- if (exprInfo?.reactive) {
1397
+ if (!isLiteralExpression(exprNode)) {
1341
1398
  const conditionalCode = tryTransformConditional(exprNode, reactiveNames, jsxMap, source);
1342
1399
  if (conditionalCode) {
1343
1400
  return `__append(${parentVar}, ${conditionalCode})`;
1344
1401
  }
1402
+ }
1403
+ if (exprInfo?.reactive) {
1345
1404
  const listCode = tryTransformList(exprNode, reactiveNames, jsxMap, parentVar, source);
1346
1405
  if (listCode) {
1347
1406
  return listCode;
1348
1407
  }
1349
1408
  }
1350
1409
  const exprText = sliceWithTransformedJsx(exprNode, reactiveNames, jsxMap, source, formVarNames);
1351
- if (exprInfo?.reactive) {
1410
+ if (!isLiteralExpression(exprNode)) {
1352
1411
  return `__append(${parentVar}, __child(() => ${exprText}))`;
1353
1412
  }
1354
1413
  return `__insert(${parentVar}, ${exprText})`;
@@ -1367,18 +1426,17 @@ function transformChildAsValue(child, reactiveNames, jsxMap, source, formVarName
1367
1426
  return `__staticText(${JSON.stringify(text)})`;
1368
1427
  }
1369
1428
  if (child.isKind(SyntaxKind10.JsxExpression)) {
1370
- const exprInfo = jsxMap.get(child.getStart());
1371
1429
  const exprNode = child.getExpression();
1372
1430
  if (!exprNode)
1373
1431
  return null;
1374
- if (exprInfo?.reactive) {
1432
+ if (!isLiteralExpression(exprNode)) {
1375
1433
  const conditionalCode = tryTransformConditional(exprNode, reactiveNames, jsxMap, source);
1376
1434
  if (conditionalCode) {
1377
1435
  return conditionalCode;
1378
1436
  }
1379
1437
  }
1380
1438
  const exprText = sliceWithTransformedJsx(exprNode, reactiveNames, jsxMap, source, formVarNames);
1381
- if (exprInfo?.reactive) {
1439
+ if (!isLiteralExpression(exprNode)) {
1382
1440
  return `__child(() => ${exprText})`;
1383
1441
  }
1384
1442
  return exprText;
@@ -1571,10 +1629,9 @@ function buildPropsObject(element, jsxMap, source, reactiveNames, formVarNames,
1571
1629
  continue;
1572
1630
  }
1573
1631
  if (init.isKind(SyntaxKind10.JsxExpression)) {
1574
- const exprInfo = jsxMap.get(init.getStart());
1575
1632
  const exprNode = init.getExpression();
1576
1633
  const exprText = exprNode ? sliceWithTransformedJsx(exprNode, reactiveNames, jsxMap, source, formVarNames) : "";
1577
- if (exprInfo?.reactive) {
1634
+ if (exprNode && !isLiteralExpression(exprNode)) {
1578
1635
  props.push(`get ${name}() { return ${exprText}; }`);
1579
1636
  } else {
1580
1637
  props.push(`${name}: ${exprText}`);
@@ -1724,6 +1781,7 @@ class SignalTransformer {
1724
1781
  }
1725
1782
  }
1726
1783
  function transformDeclarations(source, bodyNode, signals) {
1784
+ const seenNames = new Map;
1727
1785
  for (const stmt of bodyNode.getChildSyntaxList()?.getChildren() ?? []) {
1728
1786
  if (!stmt.isKind(SyntaxKind11.VariableStatement))
1729
1787
  continue;
@@ -1741,8 +1799,11 @@ function transformDeclarations(source, bodyNode, signals) {
1741
1799
  if (letKeyword) {
1742
1800
  source.overwrite(letKeyword.getStart(), letKeyword.getEnd(), "const");
1743
1801
  }
1802
+ const count = seenNames.get(name) ?? 0;
1803
+ seenNames.set(name, count + 1);
1804
+ const hmrKey = count === 0 ? name : `${name}$${count}`;
1744
1805
  source.appendLeft(init.getStart(), "signal(");
1745
- source.appendRight(init.getEnd(), ")");
1806
+ source.appendRight(init.getEnd(), `, '${hmrKey}')`);
1746
1807
  }
1747
1808
  }
1748
1809
  }
@@ -1891,9 +1952,6 @@ function compile(source, optionsOrFilename) {
1891
1952
  if (jsxExpressions.length > 0) {
1892
1953
  usedFeatures.add("__element");
1893
1954
  usedFeatures.add("__child");
1894
- }
1895
- if (jsxExpressions.some((e) => e.reactive)) {
1896
- usedFeatures.add("__text");
1897
1955
  usedFeatures.add("__attr");
1898
1956
  }
1899
1957
  const hasEvents = sourceFile.getDescendantsOfKind(SyntaxKind12.JsxAttribute).some((attr) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/ui-compiler",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Vertz UI compiler — SSR and build-time optimizations",
@@ -23,12 +23,12 @@
23
23
  "scripts": {
24
24
  "build": "bunup",
25
25
  "test": "bun test",
26
- "test:watch": "vitest",
26
+ "test:watch": "bun test --watch",
27
27
  "typecheck": "tsc --noEmit -p tsconfig.typecheck.json"
28
28
  },
29
29
  "dependencies": {
30
30
  "@ampproject/remapping": "^2.3.0",
31
- "@vertz/ui": "^0.2.2",
31
+ "@vertz/ui": "^0.2.11",
32
32
  "magic-string": "^0.30.0",
33
33
  "ts-morph": "^27.0.2"
34
34
  },
@@ -36,10 +36,8 @@
36
36
  "@jridgewell/trace-mapping": "^0.3.31",
37
37
  "@types/node": "^25.3.1",
38
38
  "bun-types": "^1.3.10",
39
- "@vitest/coverage-v8": "^4.0.18",
40
39
  "bunup": "^0.16.31",
41
- "typescript": "^5.7.0",
42
- "vitest": "^4.0.18"
40
+ "typescript": "^5.7.0"
43
41
  },
44
42
  "engines": {
45
43
  "node": ">=22"