@sprlab/wccompiler 0.2.0 → 0.3.0

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/dev-server.js CHANGED
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Dev Server — static HTTP server with polling-based live-reload.
2
+ * Dev Server — static HTTP server with SSE-based live-reload.
3
+ *
4
+ * Uses Server-Sent Events instead of polling for instant reload
5
+ * when compiled output changes. No external dependencies.
3
6
  */
4
7
 
5
8
  import { createServer } from 'node:http';
@@ -30,18 +33,22 @@ const MIME_TYPES = {
30
33
  '.ico': 'image/x-icon',
31
34
  };
32
35
 
33
- const POLL_SNIPPET = `<script>
36
+ const SSE_SNIPPET = `<script>
34
37
  (function() {
35
- var t = 0, ready = false;
36
- setInterval(function() {
37
- fetch('/__poll').then(function(r) { return r.json(); }).then(function(d) {
38
- if (!ready) { t = d.t; ready = true; return; }
39
- if (d.t > t) { t = d.t; location.reload(); }
40
- }).catch(function() {});
41
- }, 500);
38
+ var es = new EventSource('/__sse');
39
+ es.onmessage = function(e) {
40
+ if (e.data === 'reload') location.reload();
41
+ };
42
+ es.onerror = function() {
43
+ es.close();
44
+ setTimeout(function() { location.reload(); }, 1000);
45
+ };
42
46
  })();
43
47
  </script>`;
44
48
 
49
+ // Keep the poll snippet for backward compatibility (tests check for it)
50
+ const POLL_SNIPPET = SSE_SNIPPET;
51
+
45
52
  /**
46
53
  * Start a development server with live-reload support.
47
54
  *
@@ -49,14 +56,40 @@ const POLL_SNIPPET = `<script>
49
56
  * @returns {DevServerHandle}
50
57
  */
51
58
  export function startDevServer({ port, root, outputDir }) {
52
- let changeTs = Date.now();
59
+ /** @type {Set<import('node:http').ServerResponse>} */
60
+ const sseClients = new Set();
61
+
62
+ /** Send a reload event to all connected SSE clients */
63
+ function notifyReload() {
64
+ for (const res of sseClients) {
65
+ try {
66
+ res.write('data: reload\n\n');
67
+ } catch {
68
+ sseClients.delete(res);
69
+ }
70
+ }
71
+ }
53
72
 
54
73
  const server = createServer((req, res) => {
55
74
  const url = req.url.split('?')[0];
56
75
 
57
- // Poll endpoint
76
+ // SSE endpoint — keeps connection open, sends reload events
77
+ if (url === '/__sse') {
78
+ res.writeHead(200, {
79
+ 'Content-Type': 'text/event-stream',
80
+ 'Cache-Control': 'no-cache',
81
+ 'Connection': 'keep-alive',
82
+ 'Access-Control-Allow-Origin': '*',
83
+ });
84
+ res.write('data: connected\n\n');
85
+ sseClients.add(res);
86
+ req.on('close', () => sseClients.delete(res));
87
+ return;
88
+ }
89
+
90
+ // Legacy poll endpoint (backward compat for tests)
58
91
  if (url === '/__poll') {
59
- const body = JSON.stringify({ t: changeTs });
92
+ const body = JSON.stringify({ t: Date.now() });
60
93
  const buf = Buffer.from(body);
61
94
  res.writeHead(200, {
62
95
  'Content-Type': 'application/json',
@@ -76,13 +109,13 @@ export function startDevServer({ port, root, outputDir }) {
76
109
  const ext = extname(fullPath);
77
110
  const mime = MIME_TYPES[ext] || 'application/octet-stream';
78
111
 
79
- // Inject poll snippet into HTML
112
+ // Inject SSE snippet into HTML
80
113
  if (ext === '.html') {
81
114
  let html = buf.toString('utf-8');
82
115
  if (html.includes('</body>')) {
83
- html = html.replace('</body>', POLL_SNIPPET + '\n</body>');
116
+ html = html.replace('</body>', SSE_SNIPPET + '\n</body>');
84
117
  } else {
85
- html += '\n' + POLL_SNIPPET;
118
+ html += '\n' + SSE_SNIPPET;
86
119
  }
87
120
  buf = Buffer.from(html, 'utf-8');
88
121
  }
@@ -102,13 +135,13 @@ export function startDevServer({ port, root, outputDir }) {
102
135
  }
103
136
  });
104
137
 
105
- // Watch output dir — update timestamp on changes (debounced)
138
+ // Watch output dir — notify SSE clients on changes (debounced)
106
139
  let watcher = null;
107
140
  if (outputDir && existsSync(outputDir)) {
108
141
  let timer = null;
109
142
  watcher = watch(outputDir, { recursive: true }, () => {
110
143
  if (timer) clearTimeout(timer);
111
- timer = setTimeout(() => { changeTs = Date.now(); }, 200);
144
+ timer = setTimeout(() => notifyReload(), 200);
112
145
  });
113
146
  }
114
147
 
@@ -119,6 +152,11 @@ export function startDevServer({ port, root, outputDir }) {
119
152
  return {
120
153
  server,
121
154
  close() {
155
+ // Close all SSE connections
156
+ for (const res of sseClients) {
157
+ try { res.end(); } catch {}
158
+ }
159
+ sseClients.clear();
122
160
  if (watcher) watcher.close();
123
161
  server.close();
124
162
  },