@sprlab/wccompiler 0.11.5 → 0.11.8
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 +20 -5
- package/lib/compiler.js +3 -2
- 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,22 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
900
901
|
}
|
|
901
902
|
|
|
902
903
|
// ── 1. Reactive runtime (shared import or inline) ──
|
|
904
|
+
// Determine which runtime functions this component needs
|
|
905
|
+
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);
|
|
906
|
+
const needsComputed = computeds.length > 0;
|
|
907
|
+
const needsUntrack = watchers.length > 0;
|
|
908
|
+
|
|
903
909
|
if (options.runtimeImportPath) {
|
|
904
910
|
// Tree-shake: only import what this component actually uses
|
|
905
911
|
const usedRuntime = new Set(['__signal']); // always need __signal
|
|
906
|
-
if (
|
|
907
|
-
if (
|
|
908
|
-
if (
|
|
912
|
+
if (needsComputed) usedRuntime.add('__computed');
|
|
913
|
+
if (needsEffect) usedRuntime.add('__effect');
|
|
914
|
+
if (needsUntrack) usedRuntime.add('__untrack');
|
|
909
915
|
const imports = [...usedRuntime].join(', ');
|
|
910
916
|
lines.push(`import { ${imports} } from '${options.runtimeImportPath}';`);
|
|
911
917
|
} else {
|
|
912
|
-
|
|
918
|
+
// Standalone: inline only the runtime functions this component needs
|
|
919
|
+
lines.push(buildInlineRuntime({ needsComputed, needsEffect, needsBatch: false, needsUntrack }).trim());
|
|
913
920
|
}
|
|
914
921
|
lines.push('');
|
|
915
922
|
|
|
@@ -998,6 +1005,7 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
998
1005
|
|
|
999
1006
|
// Signal initialization
|
|
1000
1007
|
for (const s of signals) {
|
|
1008
|
+
if (s === signals[0]) comment('Signals');
|
|
1001
1009
|
lines.push(` this._${s.name} = __signal(${s.value});`);
|
|
1002
1010
|
}
|
|
1003
1011
|
|
|
@@ -1013,6 +1021,7 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1013
1021
|
|
|
1014
1022
|
// Computed initialization
|
|
1015
1023
|
for (const c of computeds) {
|
|
1024
|
+
if (c === computeds[0]) comment('Computed');
|
|
1016
1025
|
const body = transformExpr(c.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames, modelVarMap);
|
|
1017
1026
|
lines.push(` this._c_${c.name} = __computed(() => ${body});`);
|
|
1018
1027
|
}
|
|
@@ -1260,6 +1269,7 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1260
1269
|
lines.push('');
|
|
1261
1270
|
|
|
1262
1271
|
// Binding effects — one __effect per binding
|
|
1272
|
+
if (bindings.length > 0) comment('Text bindings');
|
|
1263
1273
|
for (const b of bindings) {
|
|
1264
1274
|
if (b.type === 'prop') {
|
|
1265
1275
|
lines.push(' this.__disposers.push(__effect(() => {');
|
|
@@ -1409,12 +1419,14 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1409
1419
|
}
|
|
1410
1420
|
|
|
1411
1421
|
// Event listeners (with AbortController signal for cleanup)
|
|
1422
|
+
if (events.length > 0) comment('Event listeners');
|
|
1412
1423
|
for (const e of events) {
|
|
1413
1424
|
const handlerExpr = generateEventHandler(e.handler, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, modelVarMap);
|
|
1414
1425
|
lines.push(` this.${e.varName}.addEventListener('${e.event}', ${handlerExpr}, { signal: this.__ac.signal });`);
|
|
1415
1426
|
}
|
|
1416
1427
|
|
|
1417
1428
|
// Show effects — one __effect per ShowBinding
|
|
1429
|
+
if (showBindings.length > 0) comment('Show directives');
|
|
1418
1430
|
for (const sb of showBindings) {
|
|
1419
1431
|
const expr = transformExpr(sb.expression, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames, methodNames, modelVarMap);
|
|
1420
1432
|
lines.push(' this.__disposers.push(__effect(() => {');
|
|
@@ -1423,6 +1435,7 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1423
1435
|
}
|
|
1424
1436
|
|
|
1425
1437
|
// Model effects — signal → DOM (one __effect per ModelBinding)
|
|
1438
|
+
if (modelBindings.length > 0) comment('Model bindings (signal → DOM)');
|
|
1426
1439
|
for (const mb of modelBindings) {
|
|
1427
1440
|
if (mb.prop === 'checked' && mb.radioValue !== null) {
|
|
1428
1441
|
// Radio: compare signal value to radioValue
|
|
@@ -1849,6 +1862,8 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1849
1862
|
}
|
|
1850
1863
|
|
|
1851
1864
|
// User methods (prefixed with _)
|
|
1865
|
+
if (methods.length > 0 && options.comments) lines.push('');
|
|
1866
|
+
if (methods.length > 0 && options.comments) lines.push(' // --- Methods ---');
|
|
1852
1867
|
for (const m of methods) {
|
|
1853
1868
|
const body = transformMethodBody(m.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames, modelVarMap);
|
|
1854
1869
|
lines.push(` _${m.name}(${m.params}) {`);
|
package/lib/compiler.js
CHANGED
|
@@ -118,11 +118,11 @@ async function compileSFC(filePath, config) {
|
|
|
118
118
|
const style = descriptor.style;
|
|
119
119
|
|
|
120
120
|
// 7. Extract lifecycle hooks (before other extractions)
|
|
121
|
-
const { onMountHooks, onDestroyHooks } = extractLifecycleHooks(source);
|
|
121
|
+
const { onMountHooks, onDestroyHooks, onAdoptHooks } = extractLifecycleHooks(source);
|
|
122
122
|
|
|
123
123
|
// 7b. Strip lifecycle/watcher blocks from source for extraction
|
|
124
124
|
let sourceForExtraction = source;
|
|
125
|
-
const hookLinePattern = /\bonMount\s*\(|\bonDestroy\s*\(|\bwatch\s*\(/;
|
|
125
|
+
const hookLinePattern = /\bonMount\s*\(|\bonDestroy\s*\(|\bonAdopt\s*\(|\bwatch\s*\(/;
|
|
126
126
|
const sourceLines = sourceForExtraction.split('\n');
|
|
127
127
|
const filteredLines = [];
|
|
128
128
|
let skipDepth = 0;
|
|
@@ -276,6 +276,7 @@ async function compileSFC(filePath, config) {
|
|
|
276
276
|
forBlocks: [],
|
|
277
277
|
onMountHooks,
|
|
278
278
|
onDestroyHooks,
|
|
279
|
+
onAdoptHooks,
|
|
279
280
|
modelBindings: [],
|
|
280
281
|
modelPropBindings: [],
|
|
281
282
|
attrBindings: [],
|
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