@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 +3 -0
- package/lib/codegen.js +24 -5
- package/lib/config.js +6 -1
- package/lib/reactive-runtime.js +40 -2
- package/lib/tree-walker.js +2 -2
- package/package.json +1 -1
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 (
|
|
907
|
-
if (
|
|
908
|
-
if (
|
|
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
|
-
|
|
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
|
}
|
package/lib/reactive-runtime.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
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
|
+
}
|
package/lib/tree-walker.js
CHANGED
|
@@ -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