@sprlab/wccompiler 0.5.3 → 0.5.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.
package/lib/codegen.js CHANGED
@@ -473,7 +473,7 @@ function generateItemSetup(lines, forBlock, itemVar, indexVar, propNames, signal
473
473
  lines.push(`${indent} const __sp = { ${propsEntries} };`);
474
474
  lines.push(`${indent} let __h = __slotEl.innerHTML;`);
475
475
  lines.push(`${indent} for (const [k, v] of Object.entries(__sp)) {`);
476
- lines.push(`${indent} __h = __h.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '\\\\s*\\\\}\\\\}', 'g'), v ?? '');`);
476
+ lines.push(`${indent} __h = __h.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '(\\\\(\\\\))?\\\\s*\\\\}\\\\}', 'g'), v ?? '');`);
477
477
  lines.push(`${indent} }`);
478
478
  lines.push(`${indent} __slotEl.innerHTML = __h;`);
479
479
  lines.push(`${indent} }`);
@@ -761,13 +761,14 @@ export function generateComponent(parseResult, options = {}) {
761
761
  lines.push(` this.${b.varName}.textContent = this._c_${b.name}() ?? '';`);
762
762
  lines.push(' }));');
763
763
  } else {
764
- // method type — check if it's a props.x access pattern
764
+ // method/expression type — check if it's a props.x access or a complex expression
765
765
  let ref;
766
766
  if (propsObjectName && b.name.startsWith(propsObjectName + '.')) {
767
767
  const propName = b.name.slice(propsObjectName.length + 1);
768
768
  ref = `this._s_${propName}()`;
769
769
  } else {
770
- ref = `this._${b.name}()`;
770
+ // Use transformExpr for complex expressions (e.g. items().length, ternary)
771
+ ref = transformExpr(b.name, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames);
771
772
  }
772
773
  lines.push(' this.__disposers.push(__effect(() => {');
773
774
  lines.push(` this.${b.varName}.textContent = ${ref} ?? '';`);
@@ -787,7 +788,7 @@ export function generateComponent(parseResult, options = {}) {
787
788
  lines.push(` const __props = { ${propsObj} };`);
788
789
  lines.push(` let __html = this.__slotTpl_${s.name};`);
789
790
  lines.push(" for (const [k, v] of Object.entries(__props)) {");
790
- lines.push(` __html = __html.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '\\\\s*\\\\}\\\\}', 'g'), v ?? '');`);
791
+ lines.push(` __html = __html.replace(new RegExp('\\\\{\\\\{\\\\s*' + k + '(\\\\(\\\\))?\\\\s*\\\\}\\\\}', 'g'), v ?? '');`);
791
792
  lines.push(' }');
792
793
  lines.push(` this.${s.varName}.innerHTML = __html;`);
793
794
  lines.push(' });');
package/lib/sfc-parser.js CHANGED
@@ -57,16 +57,59 @@ function findBlocks(source, blockName) {
57
57
  let m;
58
58
 
59
59
  while ((m = openRe.exec(source)) !== null) {
60
+ const attrs = m[1] || '';
61
+
62
+ // For template blocks: skip <template #name> (slot content, not SFC block)
63
+ if (blockName === 'template' && /#/.test(attrs)) {
64
+ continue;
65
+ }
66
+
60
67
  const openEnd = m.index + m[0].length;
61
- const closeIdx = source.indexOf(closeTag, openEnd);
62
- if (closeIdx === -1) continue; // unclosed tag skip
63
-
64
- matches.push({
65
- content: source.slice(openEnd, closeIdx),
66
- attrs: m[1] || '',
67
- start: m.index,
68
- end: closeIdx + closeTag.length,
69
- });
68
+
69
+ // Use depth counting to find the matching close tag (handles nested <template #name>)
70
+ if (blockName === 'template') {
71
+ let depth = 1;
72
+ let searchPos = openEnd;
73
+ let closeIdx = -1;
74
+ const openTagRe = /<template[\s>]/g;
75
+ const closeTagStr = '</template>';
76
+
77
+ while (depth > 0 && searchPos < source.length) {
78
+ const nextClose = source.indexOf(closeTagStr, searchPos);
79
+ if (nextClose === -1) break;
80
+
81
+ // Check for any opening <template> between searchPos and nextClose
82
+ openTagRe.lastIndex = searchPos;
83
+ let openMatch;
84
+ while ((openMatch = openTagRe.exec(source)) !== null && openMatch.index < nextClose) {
85
+ depth++;
86
+ }
87
+
88
+ depth--; // for the </template> we found
89
+ if (depth === 0) {
90
+ closeIdx = nextClose;
91
+ break;
92
+ }
93
+ searchPos = nextClose + closeTagStr.length;
94
+ }
95
+
96
+ if (closeIdx === -1) continue;
97
+ matches.push({
98
+ content: source.slice(openEnd, closeIdx),
99
+ attrs,
100
+ start: m.index,
101
+ end: closeIdx + closeTag.length,
102
+ });
103
+ } else {
104
+ const closeIdx = source.indexOf(closeTag, openEnd);
105
+ if (closeIdx === -1) continue;
106
+ matches.push({
107
+ content: source.slice(openEnd, closeIdx),
108
+ attrs,
109
+ start: m.index,
110
+ end: closeIdx + closeTag.length,
111
+ });
112
+ }
70
113
  }
71
114
 
72
115
  return matches;
@@ -240,10 +240,10 @@ export function walkTree(rootEl, signalNames, computedNames, propNames = new Set
240
240
  }
241
241
 
242
242
  // --- Text node with interpolations ---
243
- if (node.nodeType === 3 && /\{\{[\w.()]+\}\}/.test(node.textContent)) {
243
+ if (node.nodeType === 3 && /\{\{(?:[^}]|\}(?!\}))+\}\}/.test(node.textContent)) {
244
244
  const text = node.textContent;
245
245
  const trimmed = text.trim();
246
- const soleMatch = trimmed.match(/^\{\{([\w.()]+)\}\}$/);
246
+ const soleMatch = trimmed.match(/^\{\{((?:[^}]|\}(?!\}))+)\}\}$/);
247
247
  const parent = node.parentNode;
248
248
 
249
249
  // Strip trailing () from expression to get the base name for type lookup
@@ -268,7 +268,7 @@ export function walkTree(rootEl, signalNames, computedNames, propNames = new Set
268
268
  // Case 2: Mixed text and interpolations — split into spans
269
269
  const doc = node.ownerDocument;
270
270
  const fragment = doc.createDocumentFragment();
271
- const parts = text.split(/(\{\{[\w.()]+\}\})/);
271
+ const parts = text.split(/(\{\{(?:[^}]|\}(?!\}))+\}\})/);
272
272
  const parentPath = pathParts.slice(0, -1);
273
273
 
274
274
  // Find the index of this text node among its siblings
@@ -280,7 +280,7 @@ export function walkTree(rootEl, signalNames, computedNames, propNames = new Set
280
280
 
281
281
  let offset = 0;
282
282
  for (const part of parts) {
283
- const bm = part.match(/^\{\{([\w.()]+)\}\}$/);
283
+ const bm = part.match(/^\{\{((?:[^}]|\}(?!\}))+)\}\}$/);
284
284
  if (bm) {
285
285
  fragment.appendChild(doc.createElement('span'));
286
286
  const varName = `__b${bindIdx++}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Zero-runtime compiler that transforms .wcc single-file components into native web components with signals-based reactivity",
5
5
  "type": "module",
6
6
  "bin": {