mnfst-run 1.0.1 → 1.0.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/serve.mjs +25 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-run",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Zero-dependency dev server for Manifest projects",
5
5
  "type": "module",
6
6
  "bin": {
package/serve.mjs CHANGED
@@ -13,12 +13,15 @@
13
13
  * SPA vs MPA is auto-detected: if the root index.html contains
14
14
  * <meta name="manifest:prerendered"> the server disables SPA fallback.
15
15
  *
16
- * Live reload: CSS changes hot-swap the stylesheet without a page reload.
17
- * All other file changes trigger a full browser refresh.
16
+ * Live reload:
17
+ * .css → hot-swaps the matching stylesheet href (no reload, no flash)
18
+ * .csv/.json/etc. → dispatches manifest:dev-reload; data plugin re-fetches local
19
+ * sources and updates Alpine store reactively (no reload)
20
+ * other → full page reload
18
21
  */
19
22
  import { createServer } from 'http';
20
23
  import { readFileSync, statSync, watch } from 'fs';
21
- import { join, extname, resolve, basename, relative } from 'path';
24
+ import { join, extname, resolve, basename } from 'path';
22
25
  import { exec } from 'child_process';
23
26
 
24
27
  const MIME = {
@@ -44,8 +47,10 @@ const MIME = {
44
47
  '.map': 'application/json',
45
48
  };
46
49
 
47
- // Injected into every HTML response connects to the SSE stream and handles
48
- // CSS hot-swap or full reload depending on what changed.
50
+ // Built once at startup. Injected only into full HTML documents (not fragments).
51
+ // - CSS changes → hot-swaps the matching <link> href (no reload, no flash)
52
+ // - data file changes → dispatches manifest:dev-reload (data plugin re-fetches)
53
+ // - other changes → full page reload
49
54
  const LIVE_RELOAD_SCRIPT = `<script>
50
55
  (function () {
51
56
  var es = new EventSource('/__mnfst_sse__');
@@ -56,13 +61,15 @@ const LIVE_RELOAD_SCRIPT = `<script>
56
61
  var base = l.href.split('?')[0];
57
62
  if (base.endsWith(d.file)) l.href = base + '?t=' + Date.now();
58
63
  });
64
+ } else if (d.type === 'data') {
65
+ window.dispatchEvent(new CustomEvent('manifest:dev-reload'));
59
66
  } else {
60
67
  location.reload();
61
68
  }
62
69
  };
63
70
  es.onerror = function () { es.close(); };
64
71
  })();
65
- </script>`;
72
+ \x3c/script>`;
66
73
 
67
74
  // --- CLI args ---
68
75
  const args = process.argv.slice(2);
@@ -82,8 +89,7 @@ function detectMPA(rootDir) {
82
89
  return /name=["']manifest:prerendered["']/i.test(readFileSync(join(rootDir, 'index.html'), 'utf8'));
83
90
  } catch { return false; }
84
91
  }
85
- const spa = !detectMPA(root);
86
- const mode = spa ? 'SPA' : 'MPA';
92
+ const spa = !detectMPA(root);
87
93
 
88
94
  // --- SSE clients ---
89
95
  let clients = [];
@@ -104,6 +110,8 @@ try {
104
110
  const ext = extname(filename).toLowerCase();
105
111
  if (ext === '.css') {
106
112
  broadcast({ type: 'css', file: '/' + filename.replace(/\\/g, '/') });
113
+ } else if (['.csv', '.json', '.yaml', '.yml', '.md'].includes(ext)) {
114
+ broadcast({ type: 'data' });
107
115
  } else {
108
116
  broadcast({ type: 'reload' });
109
117
  }
@@ -123,12 +131,15 @@ function serveFile(res, filePath) {
123
131
  const mime = MIME[ext] || 'application/octet-stream';
124
132
  let body = readFileSync(filePath);
125
133
  if (ext === '.html') {
126
- let html = body.toString('utf8');
127
- // Inject before </body>; fall back to appending if tag absent
128
- html = html.includes('</body>')
129
- ? html.replace('</body>', LIVE_RELOAD_SCRIPT + '</body>')
130
- : html + LIVE_RELOAD_SCRIPT;
131
- body = Buffer.from(html, 'utf8');
134
+ const html = body.toString('utf8');
135
+ // Only inject into full HTML documents not component fragments
136
+ const isFullDoc = /<!doctype\s/i.test(html) || /<html[\s>]/i.test(html);
137
+ if (isFullDoc) {
138
+ const injected = html.includes('</body>')
139
+ ? html.replace('</body>', LIVE_RELOAD_SCRIPT + '</body>')
140
+ : html + LIVE_RELOAD_SCRIPT;
141
+ body = Buffer.from(injected, 'utf8');
142
+ }
132
143
  }
133
144
  res.writeHead(200, { 'Content-Type': mime });
134
145
  res.end(body);