@sprlab/wccompiler 0.4.0 → 0.4.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/bin/wcc.js CHANGED
@@ -67,7 +67,7 @@ async function main() {
67
67
  } else if (command === 'dev') {
68
68
  await build(config, cwd);
69
69
  const outputDir = resolve(cwd, config.output);
70
- startDevServer({ port: config.port, root: cwd, outputDir });
70
+ const devServer = startDevServer({ port: config.port, root: cwd, outputDir });
71
71
  const inputDir = resolve(cwd, config.input);
72
72
  console.log(`Watching ${inputDir} for changes...`);
73
73
  watch(inputDir, { recursive: true }, async (eventType, filename) => {
@@ -85,6 +85,7 @@ async function main() {
85
85
  console.log(`Compiled: ${filename}`);
86
86
  } catch (err) {
87
87
  console.error(`Error compiling ${filename}: ${err.message}`);
88
+ devServer.notifyError(`${filename}\n\n${err.message}`);
88
89
  }
89
90
  });
90
91
  } else {
package/lib/codegen.js CHANGED
@@ -517,6 +517,7 @@ export function generateComponent(parseResult) {
517
517
  refBindings = [],
518
518
  childComponents = [],
519
519
  childImports = [],
520
+ exposeNames = [],
520
521
  } = parseResult;
521
522
 
522
523
  const signalNames = signals.map(s => s.name);
@@ -614,9 +615,11 @@ export function generateComponent(parseResult) {
614
615
  lines.push(` this.${s.varName} = ${pathExpr(s.path, '__root')};`);
615
616
  }
616
617
 
617
- // Assign DOM refs for child component instances
618
+ // Assign DOM refs for child component instances (only if they have prop bindings)
618
619
  for (const cc of childComponents) {
619
- lines.push(` this.${cc.varName} = ${pathExpr(cc.path, '__root')};`);
620
+ if (cc.propBindings.length > 0) {
621
+ lines.push(` this.${cc.varName} = ${pathExpr(cc.path, '__root')};`);
622
+ }
620
623
  }
621
624
 
622
625
  // Assign DOM refs for attr bindings (reuse ref when same path)
@@ -1157,6 +1160,20 @@ export function generateComponent(parseResult) {
1157
1160
  }
1158
1161
  }
1159
1162
 
1163
+ // ── defineExpose: public getters/methods ──
1164
+ for (const name of exposeNames) {
1165
+ if (computedNames.includes(name)) {
1166
+ lines.push(` get ${name}() { return this._c_${name}(); }`);
1167
+ } else if (signalNames.includes(name)) {
1168
+ lines.push(` get ${name}() { return this._${name}(); }`);
1169
+ } else if (methodNames.includes(name)) {
1170
+ lines.push(` ${name}(...args) { return this._${name}(...args); }`);
1171
+ } else if (constantNames.includes(name)) {
1172
+ lines.push(` get ${name}() { return this._const_${name}; }`);
1173
+ }
1174
+ }
1175
+ if (exposeNames.length > 0) lines.push('');
1176
+
1160
1177
  // ── if setup methods ──
1161
1178
  for (const ifBlock of ifBlocks) {
1162
1179
  const vn = ifBlock.varName;
package/lib/compiler.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  extractLifecycleHooks,
33
33
  extractRefs,
34
34
  extractConstants,
35
+ extractExpose,
35
36
  validatePropsAssignment,
36
37
  validateDuplicateProps,
37
38
  validatePropsConflicts,
@@ -147,6 +148,7 @@ async function compileSFC(filePath, config) {
147
148
  const methods = extractFunctions(sourceForExtraction);
148
149
  const refs = extractRefs(sourceForExtraction);
149
150
  const constantVars = extractConstants(sourceForExtraction);
151
+ const exposeNames = extractExpose(source);
150
152
 
151
153
  // 9. Extract props (array form — after type strip, if generic didn't find any)
152
154
  const propsFromArray = propsFromGeneric.length > 0 ? [] : extractPropsArray(source);
@@ -218,6 +220,7 @@ async function compileSFC(filePath, config) {
218
220
  refBindings: [],
219
221
  childComponents: [],
220
222
  childImports: [],
223
+ exposeNames,
221
224
  };
222
225
 
223
226
  // 16. Process template through jsdom → tree-walker → codegen (same as compile())
package/lib/dev-server.js CHANGED
@@ -36,8 +36,25 @@ const MIME_TYPES = {
36
36
  const SSE_SNIPPET = `<script>
37
37
  (function() {
38
38
  var es = new EventSource('/__sse');
39
+ var overlay = null;
40
+ function showError(msg) {
41
+ hideError();
42
+ overlay = document.createElement('div');
43
+ overlay.id = '__wcc_error_overlay';
44
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.85);color:#fff;font-family:monospace;font-size:14px;padding:32px;overflow:auto;display:flex;align-items:flex-start;justify-content:center;';
45
+ var box = document.createElement('div');
46
+ box.style.cssText = 'background:#1e1e1e;border:2px solid #f44;border-radius:8px;padding:24px;max-width:700px;width:100%;white-space:pre-wrap;word-break:break-word;';
47
+ box.innerHTML = '<div style="color:#f44;font-size:16px;font-weight:bold;margin-bottom:12px;">\\u274C Compilation Error</div>' + msg.replace(/</g,'&lt;').replace(/>/g,'&gt;');
48
+ overlay.appendChild(box);
49
+ overlay.addEventListener('click', hideError);
50
+ document.body.appendChild(overlay);
51
+ }
52
+ function hideError() {
53
+ if (overlay) { overlay.remove(); overlay = null; }
54
+ }
39
55
  es.onmessage = function(e) {
40
- if (e.data === 'reload') location.reload();
56
+ if (e.data === 'reload') { hideError(); location.reload(); }
57
+ else if (e.data.startsWith('error:')) { showError(e.data.slice(6).replace(/\\\\n/g,'\\n')); }
41
58
  };
42
59
  es.onerror = function() {
43
60
  es.close();
@@ -70,6 +87,17 @@ export function startDevServer({ port, root, outputDir }) {
70
87
  }
71
88
  }
72
89
 
90
+ /** Send an error event to all connected SSE clients */
91
+ function notifyError(message) {
92
+ for (const res of sseClients) {
93
+ try {
94
+ res.write(`data: error:${message.replace(/\n/g, '\\n')}\n\n`);
95
+ } catch {
96
+ sseClients.delete(res);
97
+ }
98
+ }
99
+ }
100
+
73
101
  const server = createServer((req, res) => {
74
102
  const url = req.url.split('?')[0];
75
103
 
@@ -151,6 +179,7 @@ export function startDevServer({ port, root, outputDir }) {
151
179
 
152
180
  return {
153
181
  server,
182
+ notifyError,
154
183
  close() {
155
184
  // Close all SSE connections
156
185
  for (const res of sseClients) {
@@ -1027,3 +1027,26 @@ export function extractRefs(source) {
1027
1027
  }
1028
1028
  return refs;
1029
1029
  }
1030
+
1031
+ // ── defineExpose extraction ─────────────────────────────────────────
1032
+
1033
+ /**
1034
+ * Extract property names from defineExpose({ key1, key2, ... }).
1035
+ * Supports shorthand properties: defineExpose({ doubled, handleUpdate })
1036
+ *
1037
+ * @param {string} source — Source code (after type stripping)
1038
+ * @returns {string[]} Array of exposed property names
1039
+ */
1040
+ export function extractExpose(source) {
1041
+ const m = source.match(/defineExpose\(\s*\{([^}]*)\}\s*\)/);
1042
+ if (!m) return [];
1043
+
1044
+ const body = m[1];
1045
+ const names = [];
1046
+ const re = /\b(\w+)\b/g;
1047
+ let match;
1048
+ while ((match = re.exec(body)) !== null) {
1049
+ names.push(match[1]);
1050
+ }
1051
+ return names;
1052
+ }
@@ -131,14 +131,13 @@ export function walkTree(rootEl, signalNames, computedNames, propNames = new Set
131
131
  }
132
132
  }
133
133
 
134
- if (propBindings.length > 0) {
135
- childComponents.push({
136
- tag: tagLower,
137
- varName: `__child${childIdx++}`,
138
- path: [...pathParts],
139
- propBindings,
140
- });
141
- }
134
+ // Always register child component for auto-import (even without prop bindings)
135
+ childComponents.push({
136
+ tag: tagLower,
137
+ varName: `__child${childIdx++}`,
138
+ path: [...pathParts],
139
+ propBindings,
140
+ });
142
141
  }
143
142
 
144
143
  // Check for @event attributes
package/lib/types.js CHANGED
@@ -99,6 +99,7 @@
99
99
  * @property {RefBinding[]} refBindings — ref attribute bindings from template (empty array if none)
100
100
  * @property {ChildComponentBinding[]} childComponents — Child component bindings (empty array if none)
101
101
  * @property {ChildComponentImport[]} childImports — Resolved child component imports (empty array if none)
102
+ * @property {string[]} exposeNames — Property names from defineExpose (empty array if none)
102
103
  */
103
104
 
104
105
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprlab/wccompiler",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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": {
package/types/wcc.d.ts CHANGED
@@ -19,7 +19,7 @@ declare module 'wcc' {
19
19
  export function defineEmits<T>(): T;
20
20
  export function defineEmits(names: string[]): (name: string, detail?: any) => void;
21
21
 
22
- export function templateRef(name: string): { value: HTMLElement | null };
22
+ export function templateRef<T = HTMLElement>(name: string): { value: (T & HTMLElement) | null };
23
23
 
24
24
  export function onMount(fn: () => void | Promise<void>): void;
25
25
  export function onDestroy(fn: () => void | Promise<void>): void;