@sprlab/wccompiler 0.11.6 → 0.11.9

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/bin/wcc.js CHANGED
@@ -36,6 +36,7 @@ async function build(config, cwd) {
36
36
  const { code, usesSharedRuntime } = await compile(file, {
37
37
  standalone: config.standalone,
38
38
  minify: config.minify,
39
+ comments: config.comments,
39
40
  runtimeImportPath,
40
41
  });
41
42
 
@@ -212,6 +213,7 @@ async function main() {
212
213
 
213
214
  // CLI flags override config
214
215
  if (process.argv.includes('--minify')) config.minify = true;
216
+ if (process.argv.includes('--comments')) config.comments = true;
215
217
 
216
218
  if (command === 'build') {
217
219
  const errors = await build(config, cwd);
@@ -241,6 +243,7 @@ async function main() {
241
243
  const { code, usesSharedRuntime } = await compile(filePath, {
242
244
  standalone: config.standalone,
243
245
  minify: config.minify,
246
+ comments: config.comments,
244
247
  runtimeImportPath,
245
248
  });
246
249
 
package/lib/codegen.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * No props, emits, slots, if, for, model, show, attr, refs, or lifecycle hooks.
14
14
  */
15
15
 
16
- import { reactiveRuntime } from './reactive-runtime.js';
16
+ import { reactiveRuntime, buildInlineRuntime } from './reactive-runtime.js';
17
17
  import { scopeCSS } from './css-scoper.js';
18
18
  import { camelToKebab } from './parser-extractors.js';
19
19
 
@@ -893,6 +893,7 @@ export function generateComponent(parseResult, options = {}) {
893
893
  }
894
894
 
895
895
  const lines = [];
896
+ const comment = options.comments ? (text) => lines.push(` // --- ${text} ---`) : () => {};
896
897
 
897
898
  // ── 0. Source comment ──
898
899
  if (options.sourceFile) {
@@ -900,16 +901,23 @@ export function generateComponent(parseResult, options = {}) {
900
901
  }
901
902
 
902
903
  // ── 1. Reactive runtime (shared import or inline) ──
904
+ if (options.comments) lines.push('// ── Runtime ──────────────────────────────────────────');
905
+ // Determine which runtime functions this component needs
906
+ const needsEffect = effects.length > 0 || bindings.length > 0 || showBindings.length > 0 || modelBindings.length > 0 || modelPropBindings.length > 0 || attrBindings.length > 0 || ifBlocks.length > 0 || forBlocks.length > 0 || watchers.length > 0 || childComponents.length > 0 || slots.some(s => s.slotProps.length > 0);
907
+ const needsComputed = computeds.length > 0;
908
+ const needsUntrack = watchers.length > 0;
909
+
903
910
  if (options.runtimeImportPath) {
904
911
  // Tree-shake: only import what this component actually uses
905
912
  const usedRuntime = new Set(['__signal']); // always need __signal
906
- if (computeds.length > 0) usedRuntime.add('__computed');
907
- if (effects.length > 0 || bindings.length > 0 || showBindings.length > 0 || modelBindings.length > 0 || modelPropBindings.length > 0 || attrBindings.length > 0 || ifBlocks.length > 0 || forBlocks.length > 0 || watchers.length > 0 || childComponents.length > 0 || slots.some(s => s.slotProps.length > 0)) usedRuntime.add('__effect');
908
- if (watchers.length > 0) usedRuntime.add('__untrack');
913
+ if (needsComputed) usedRuntime.add('__computed');
914
+ if (needsEffect) usedRuntime.add('__effect');
915
+ if (needsUntrack) usedRuntime.add('__untrack');
909
916
  const imports = [...usedRuntime].join(', ');
910
917
  lines.push(`import { ${imports} } from '${options.runtimeImportPath}';`);
911
918
  } else {
912
- lines.push(reactiveRuntime.trim());
919
+ // Standalone: inline only the runtime functions this component needs
920
+ lines.push(buildInlineRuntime({ needsComputed, needsEffect, needsBatch: false, needsUntrack }).trim());
913
921
  }
914
922
  lines.push('');
915
923
 
@@ -923,6 +931,7 @@ export function generateComponent(parseResult, options = {}) {
923
931
 
924
932
  // ── 2. CSS injection (scoped, deduplicated via id guard) ──
925
933
  if (style) {
934
+ if (options.comments) lines.push('// ── Styles ───────────────────────────────────────────');
926
935
  const scoped = scopeCSS(style, tagName);
927
936
  const cssId = `__css_${className}`;
928
937
  lines.push(`if (!document.getElementById('${cssId}')) {`);
@@ -935,11 +944,13 @@ export function generateComponent(parseResult, options = {}) {
935
944
  }
936
945
 
937
946
  // ── 3. Template element ──
947
+ if (options.comments) lines.push('// ── Template ─────────────────────────────────────────');
938
948
  lines.push(`const __t_${className} = document.createElement('template');`);
939
949
  lines.push(`__t_${className}.innerHTML = \`${processedTemplate || ''}\`;`);
940
950
  lines.push('');
941
951
 
942
952
  // ── 4. HTMLElement class ──
953
+ if (options.comments) lines.push('// ── Component ────────────────────────────────────────');
943
954
  lines.push(`class ${className} extends HTMLElement {`);
944
955
 
945
956
  // Static observedAttributes (if props or model props exist)
@@ -998,6 +1009,7 @@ export function generateComponent(parseResult, options = {}) {
998
1009
 
999
1010
  // Signal initialization
1000
1011
  for (const s of signals) {
1012
+ if (s === signals[0]) comment('Signals');
1001
1013
  lines.push(` this._${s.name} = __signal(${s.value});`);
1002
1014
  }
1003
1015
 
@@ -1013,6 +1025,7 @@ export function generateComponent(parseResult, options = {}) {
1013
1025
 
1014
1026
  // Computed initialization
1015
1027
  for (const c of computeds) {
1028
+ if (c === computeds[0]) comment('Computed');
1016
1029
  const body = transformExpr(c.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames, modelVarMap);
1017
1030
  lines.push(` this._c_${c.name} = __computed(() => ${body});`);
1018
1031
  }
@@ -1260,6 +1273,7 @@ export function generateComponent(parseResult, options = {}) {
1260
1273
  lines.push('');
1261
1274
 
1262
1275
  // Binding effects — one __effect per binding
1276
+ if (bindings.length > 0) comment('Text bindings');
1263
1277
  for (const b of bindings) {
1264
1278
  if (b.type === 'prop') {
1265
1279
  lines.push(' this.__disposers.push(__effect(() => {');
@@ -1409,12 +1423,14 @@ export function generateComponent(parseResult, options = {}) {
1409
1423
  }
1410
1424
 
1411
1425
  // Event listeners (with AbortController signal for cleanup)
1426
+ if (events.length > 0) comment('Event listeners');
1412
1427
  for (const e of events) {
1413
1428
  const handlerExpr = generateEventHandler(e.handler, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, modelVarMap);
1414
1429
  lines.push(` this.${e.varName}.addEventListener('${e.event}', ${handlerExpr}, { signal: this.__ac.signal });`);
1415
1430
  }
1416
1431
 
1417
1432
  // Show effects — one __effect per ShowBinding
1433
+ if (showBindings.length > 0) comment('Show directives');
1418
1434
  for (const sb of showBindings) {
1419
1435
  const expr = transformExpr(sb.expression, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames, modelVarMap);
1420
1436
  lines.push(' this.__disposers.push(__effect(() => {');
@@ -1423,6 +1439,7 @@ export function generateComponent(parseResult, options = {}) {
1423
1439
  }
1424
1440
 
1425
1441
  // Model effects — signal → DOM (one __effect per ModelBinding)
1442
+ if (modelBindings.length > 0) comment('Model bindings (signal → DOM)');
1426
1443
  for (const mb of modelBindings) {
1427
1444
  if (mb.prop === 'checked' && mb.radioValue !== null) {
1428
1445
  // Radio: compare signal value to radioValue
@@ -1849,6 +1866,8 @@ export function generateComponent(parseResult, options = {}) {
1849
1866
  }
1850
1867
 
1851
1868
  // User methods (prefixed with _)
1869
+ if (methods.length > 0 && options.comments) lines.push('');
1870
+ if (methods.length > 0 && options.comments) lines.push(' // --- Methods ---');
1852
1871
  for (const m of methods) {
1853
1872
  const body = transformMethodBody(m.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames, modelVarMap);
1854
1873
  lines.push(` _${m.name}(${m.params}) {`);
package/lib/config.js CHANGED
@@ -19,7 +19,7 @@ import { pathToFileURL } from 'node:url';
19
19
  * @returns {Promise<WccConfig>}
20
20
  */
21
21
  export async function loadConfig(projectRoot) {
22
- const defaults = { port: 4100, input: 'src', output: 'dist', standalone: false, minify: false };
22
+ const defaults = { port: 4100, input: 'src', output: 'dist', standalone: false, minify: false, comments: false };
23
23
  const configPath = resolve(projectRoot, 'wcc.config.js');
24
24
 
25
25
  if (!existsSync(configPath)) return defaults;
@@ -61,6 +61,11 @@ export async function loadConfig(projectRoot) {
61
61
  error.code = 'INVALID_CONFIG';
62
62
  throw error;
63
63
  }
64
+ if (typeof config.comments !== 'boolean') {
65
+ const error = new Error(`Error en wcc.config.js: comments debe ser un booleano`);
66
+ error.code = 'INVALID_CONFIG';
67
+ throw error;
68
+ }
64
69
 
65
70
  return config;
66
71
  }
@@ -8,16 +8,21 @@
8
8
  * - __computed(fn): cached derived value that auto-invalidates
9
9
  * - __effect(fn): runs fn immediately and re-runs when dependencies change
10
10
  * - __batch(fn): batch multiple signal writes, flush effects once at the end
11
+ * - __untrack(fn): run fn without tracking dependencies
11
12
  *
12
13
  * Dependency tracking uses a global stack (__currentEffect).
13
14
  * Batching uses a depth counter — nested batches are supported.
14
15
  */
15
- /** @type {string} */
16
- export const reactiveRuntime = `
16
+
17
+ /** Shared globals (always included) */
18
+ const runtimeGlobals = `
17
19
  let __currentEffect = null;
18
20
  let __batchDepth = 0;
19
21
  const __pendingEffects = new Set();
22
+ `;
20
23
 
24
+ /** __signal — always included */
25
+ const runtimeSignal = `
21
26
  function __signal(initial) {
22
27
  let _value = initial;
23
28
  const _subs = new Set();
@@ -37,7 +42,10 @@ function __signal(initial) {
37
42
  }
38
43
  };
39
44
  }
45
+ `;
40
46
 
47
+ /** __computed — only if component uses computed() */
48
+ const runtimeComputed = `
41
49
  function __computed(fn) {
42
50
  let _cached, _dirty = true;
43
51
  const _subs = new Set();
@@ -61,7 +69,10 @@ function __computed(fn) {
61
69
  return _cached;
62
70
  };
63
71
  }
72
+ `;
64
73
 
74
+ /** __effect — only if component uses effects/bindings/show/model/attr/if/for/watchers/slots */
75
+ const runtimeEffect = `
65
76
  function __effect(fn) {
66
77
  let _cleanup = null;
67
78
  let _active = true;
@@ -81,7 +92,10 @@ function __effect(fn) {
81
92
  run();
82
93
  return () => { _active = false; if (typeof _cleanup === 'function') _cleanup(); };
83
94
  }
95
+ `;
84
96
 
97
+ /** __batch — only if component uses batch() */
98
+ const runtimeBatch = `
85
99
  function __batch(fn) {
86
100
  __batchDepth++;
87
101
  try {
@@ -95,7 +109,10 @@ function __batch(fn) {
95
109
  }
96
110
  }
97
111
  }
112
+ `;
98
113
 
114
+ /** __untrack — only if component uses watchers */
115
+ const runtimeUntrack = `
99
116
  function __untrack(fn) {
100
117
  const prev = __currentEffect;
101
118
  __currentEffect = null;
@@ -103,3 +120,24 @@ function __untrack(fn) {
103
120
  finally { __currentEffect = prev; }
104
121
  }
105
122
  `;
123
+
124
+ /**
125
+ * Full runtime (for backward compatibility and shared mode export).
126
+ * @type {string}
127
+ */
128
+ export const reactiveRuntime = runtimeGlobals + runtimeSignal + runtimeComputed + runtimeEffect + runtimeBatch + runtimeUntrack;
129
+
130
+ /**
131
+ * Build a tree-shaken inline runtime containing only the functions this component needs.
132
+ *
133
+ * @param {{ needsComputed: boolean, needsEffect: boolean, needsBatch: boolean, needsUntrack: boolean }} usage
134
+ * @returns {string}
135
+ */
136
+ export function buildInlineRuntime(usage) {
137
+ let code = runtimeGlobals + runtimeSignal;
138
+ if (usage.needsComputed) code += runtimeComputed;
139
+ if (usage.needsEffect) code += runtimeEffect;
140
+ if (usage.needsBatch) code += runtimeBatch;
141
+ if (usage.needsUntrack) code += runtimeUntrack;
142
+ return code;
143
+ }
@@ -159,7 +159,7 @@ export function walkTree(rootEl, signalNames, computedNames, propNames = new Set
159
159
  if (attr.name.startsWith('@')) {
160
160
  const eventName = attr.name.slice(1);
161
161
  const handlerName = attr.value.replace(/[^a-zA-Z0-9_]/g, '_').slice(0, 20);
162
- const varName = `__evt_${eventName.replace(/-/g, '_')}_${handlerName}`;
162
+ const varName = `__evt_${eventName.replace(/-/g, '_')}_${handlerName}_${eventIdx}`;
163
163
  eventIdx++;
164
164
  events.push({
165
165
  varName,
@@ -298,7 +298,7 @@ export function walkTree(rootEl, signalNames, computedNames, propNames = new Set
298
298
  if (soleMatch && parent.childNodes.length === 1) {
299
299
  const name = baseName(soleMatch[1]);
300
300
  const safeName = name.replace(/[^a-zA-Z0-9_]/g, '_').slice(0, 30);
301
- const varName = `__text_${safeName}`;
301
+ const varName = `__text_${safeName}_${bindIdx}`;
302
302
  bindIdx++;
303
303
  bindings.push({
304
304
  varName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.11.6",
3
+ "version": "0.11.9",
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
  "exports": {