@sprlab/wccompiler 0.5.0 → 0.5.1
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 +38 -16
- package/lib/compiler.js +2 -1
- package/package.json +1 -1
package/lib/codegen.js
CHANGED
|
@@ -530,9 +530,21 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
530
530
|
|
|
531
531
|
const lines = [];
|
|
532
532
|
|
|
533
|
+
// ── 0. Source comment ──
|
|
534
|
+
if (options.sourceFile) {
|
|
535
|
+
lines.push(`// Generated from: ${options.sourceFile} (wcCompiler)`);
|
|
536
|
+
}
|
|
537
|
+
|
|
533
538
|
// ── 1. Reactive runtime (shared import or inline) ──
|
|
534
539
|
if (options.runtimeImportPath) {
|
|
535
|
-
|
|
540
|
+
// Tree-shake: only import what this component actually uses
|
|
541
|
+
const usedRuntime = new Set(['__signal']); // always need __signal
|
|
542
|
+
if (computeds.length > 0) usedRuntime.add('__computed');
|
|
543
|
+
if (effects.length > 0 || bindings.length > 0 || showBindings.length > 0 || modelBindings.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');
|
|
544
|
+
// __batch is available but only needed if user code calls it explicitly — always include for safety
|
|
545
|
+
usedRuntime.add('__batch');
|
|
546
|
+
const imports = [...usedRuntime].join(', ');
|
|
547
|
+
lines.push(`import { ${imports} } from '${options.runtimeImportPath}';`);
|
|
536
548
|
} else {
|
|
537
549
|
lines.push(reactiveRuntime.trim());
|
|
538
550
|
}
|
|
@@ -546,12 +558,16 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
546
558
|
lines.push('');
|
|
547
559
|
}
|
|
548
560
|
|
|
549
|
-
// ── 2. CSS injection (scoped
|
|
561
|
+
// ── 2. CSS injection (scoped, deduplicated via id guard) ──
|
|
550
562
|
if (style) {
|
|
551
563
|
const scoped = scopeCSS(style, tagName);
|
|
552
|
-
|
|
553
|
-
lines.push(`
|
|
554
|
-
lines.push(`document.
|
|
564
|
+
const cssId = `__css_${className}`;
|
|
565
|
+
lines.push(`if (!document.getElementById('${cssId}')) {`);
|
|
566
|
+
lines.push(` const ${cssId} = document.createElement('style');`);
|
|
567
|
+
lines.push(` ${cssId}.id = '${cssId}';`);
|
|
568
|
+
lines.push(` ${cssId}.textContent = \`${scoped}\`;`);
|
|
569
|
+
lines.push(` document.head.appendChild(${cssId});`);
|
|
570
|
+
lines.push('}');
|
|
555
571
|
lines.push('');
|
|
556
572
|
}
|
|
557
573
|
|
|
@@ -724,8 +740,12 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
724
740
|
lines.push(' }');
|
|
725
741
|
lines.push('');
|
|
726
742
|
|
|
727
|
-
// connectedCallback
|
|
743
|
+
// connectedCallback (idempotent — safe for re-mount)
|
|
728
744
|
lines.push(' connectedCallback() {');
|
|
745
|
+
lines.push(' if (this.__connected) return;');
|
|
746
|
+
lines.push(' this.__connected = true;');
|
|
747
|
+
lines.push(' this.__ac = new AbortController();');
|
|
748
|
+
lines.push('');
|
|
729
749
|
|
|
730
750
|
// Binding effects — one __effect per binding
|
|
731
751
|
for (const b of bindings) {
|
|
@@ -846,10 +866,10 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
846
866
|
}
|
|
847
867
|
}
|
|
848
868
|
|
|
849
|
-
// Event listeners
|
|
869
|
+
// Event listeners (with AbortController signal for cleanup)
|
|
850
870
|
for (const e of events) {
|
|
851
871
|
const handlerExpr = generateEventHandler(e.handler, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, constantNames);
|
|
852
|
-
lines.push(` this.${e.varName}.addEventListener('${e.event}', ${handlerExpr});`);
|
|
872
|
+
lines.push(` this.${e.varName}.addEventListener('${e.event}', ${handlerExpr}, { signal: this.__ac.signal });`);
|
|
853
873
|
}
|
|
854
874
|
|
|
855
875
|
// Show effects — one __effect per ShowBinding
|
|
@@ -880,17 +900,17 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
880
900
|
}
|
|
881
901
|
}
|
|
882
902
|
|
|
883
|
-
// Model event listeners — DOM → signal (
|
|
903
|
+
// Model event listeners — DOM → signal (with AbortController signal)
|
|
884
904
|
for (const mb of modelBindings) {
|
|
885
905
|
if (mb.prop === 'checked' && mb.radioValue === null) {
|
|
886
906
|
// Checkbox: read e.target.checked
|
|
887
|
-
lines.push(` this.${mb.varName}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.checked); });`);
|
|
907
|
+
lines.push(` this.${mb.varName}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.checked); }, { signal: this.__ac.signal });`);
|
|
888
908
|
} else if (mb.coerce) {
|
|
889
909
|
// Number input: wrap in Number()
|
|
890
|
-
lines.push(` this.${mb.varName}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(Number(e.target.value)); });`);
|
|
910
|
+
lines.push(` this.${mb.varName}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(Number(e.target.value)); }, { signal: this.__ac.signal });`);
|
|
891
911
|
} else {
|
|
892
912
|
// All others: read e.target.value
|
|
893
|
-
lines.push(` this.${mb.varName}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.value); });`);
|
|
913
|
+
lines.push(` this.${mb.varName}.addEventListener('${mb.event}', (e) => { this._${mb.signal}(e.target.value); }, { signal: this.__ac.signal });`);
|
|
894
914
|
}
|
|
895
915
|
}
|
|
896
916
|
|
|
@@ -1078,9 +1098,11 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1078
1098
|
lines.push(' }');
|
|
1079
1099
|
lines.push('');
|
|
1080
1100
|
|
|
1081
|
-
// disconnectedCallback (
|
|
1101
|
+
// disconnectedCallback (cleanup: abort listeners + user hooks)
|
|
1102
|
+
lines.push(' disconnectedCallback() {');
|
|
1103
|
+
lines.push(' this.__connected = false;');
|
|
1104
|
+
lines.push(' this.__ac.abort();');
|
|
1082
1105
|
if (onDestroyHooks.length > 0) {
|
|
1083
|
-
lines.push(' disconnectedCallback() {');
|
|
1084
1106
|
for (const hook of onDestroyHooks) {
|
|
1085
1107
|
const body = transformMethodBody(hook.body, signalNames, computedNames, propsObjectName, propNames, emitsObjectName, refVarNames, constantNames);
|
|
1086
1108
|
if (hook.async) {
|
|
@@ -1097,9 +1119,9 @@ export function generateComponent(parseResult, options = {}) {
|
|
|
1097
1119
|
}
|
|
1098
1120
|
}
|
|
1099
1121
|
}
|
|
1100
|
-
lines.push(' }');
|
|
1101
|
-
lines.push('');
|
|
1102
1122
|
}
|
|
1123
|
+
lines.push(' }');
|
|
1124
|
+
lines.push('');
|
|
1103
1125
|
|
|
1104
1126
|
// attributeChangedCallback (if props exist)
|
|
1105
1127
|
if (propDefs.length > 0) {
|
package/lib/compiler.js
CHANGED
|
@@ -323,7 +323,8 @@ async function compileSFC(filePath, config) {
|
|
|
323
323
|
parseResult.processedTemplate = rootEl.innerHTML;
|
|
324
324
|
|
|
325
325
|
// 20. Generate component
|
|
326
|
-
|
|
326
|
+
const genOptions = { ...config, sourceFile: fileName };
|
|
327
|
+
return generateComponent(parseResult, genOptions);
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
/**
|
package/package.json
CHANGED