@nitronjs/framework 0.3.3 → 0.3.5

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.
@@ -1,219 +1,217 @@
1
- /**
2
- * HMR Client — Browser-side hot module replacement handler.
3
- *
4
- * Connects to the dev server via Socket.IO WebSocket.
5
- * On file change events, fetches a fresh RSC (React Server Components)
6
- * Flight payload and lets React reconcile the DOM — no full page reload needed.
7
- *
8
- * Wire format from /__nitron/rsc:
9
- * "<flightLength>\n<flightPayload><jsonMetadata>"
10
- * - First line: byte length of the Flight payload
11
- * - Then the Flight payload itself (React serialization format)
12
- * - Then a JSON object with { meta, css, translations }
13
- */
14
- (function() {
15
- "use strict";
16
-
17
- var RSC_ENDPOINT = "/__nitron/rsc";
18
- var socket = null;
19
-
20
- // --- Connection ---
21
-
22
- function connect() {
23
- if (socket) return;
24
-
25
- if (typeof io === "undefined") {
26
- setTimeout(connect, 100);
27
-
28
- return;
29
- }
30
-
31
- try {
32
- socket = io({
33
- path: "/__nitron_hmr",
34
- transports: ["websocket"],
35
- reconnection: true,
36
- reconnectionAttempts: Infinity,
37
- reconnectionDelay: 1000,
38
- reconnectionDelayMax: 5000,
39
- timeout: 5000
40
- });
41
- }
42
- catch (e) {
43
- // Socket.IO not available — HMR disabled silently (dev-only feature)
44
- return;
45
- }
46
-
47
- socket.on("connect", function() {
48
- window.__nitron_hmr_connected__ = true;
49
- hideErrorOverlay();
50
- });
51
-
52
- socket.on("disconnect", function() {
53
- window.__nitron_hmr_connected__ = false;
54
- });
55
-
56
- socket.on("connect_error", function() {
57
- window.__nitron_hmr_connected__ = false;
58
- });
59
-
60
- // Unified RSC-based change handler
61
- socket.on("hmr:change", function(data) {
62
- hideErrorOverlay();
63
-
64
- if (data.changeType === "css") {
65
- refreshCss();
66
- return;
67
- }
68
-
69
- // page, layout all use RSC refetch
70
- refetchRSC(data.cssChanged);
71
- });
72
-
73
- socket.on("hmr:reload", function() {
74
- location.reload();
75
- });
76
-
77
- socket.on("hmr:error", function(data) {
78
- showErrorOverlay(data.message || "Unknown error", data.file);
79
- });
80
- }
81
-
82
- // --- RSC Wire Format Parser ---
83
-
84
- // Parses the length-prefixed response from /__nitron/rsc.
85
- // Format: "<length>\n<flight payload><json metadata>"
86
- // Returns { payload, meta, css, translations } or null on malformed input.
87
- function parseLengthPrefixed(text) {
88
- var nl = text.indexOf("\n");
89
- if (nl === -1) return null;
90
-
91
- var len = parseInt(text.substring(0, nl), 10);
92
- if (isNaN(len) || len < 0) return null;
93
-
94
- var flight = text.substring(nl + 1, nl + 1 + len);
95
- var jsonStr = text.substring(nl + 1 + len);
96
- var data;
97
-
98
- try { data = JSON.parse(jsonStr); }
99
- catch (e) { return null; }
100
-
101
- return {
102
- payload: flight,
103
- meta: data.meta || null,
104
- css: data.css || null,
105
- translations: data.translations || null
106
- };
107
- }
108
-
109
- // --- RSC Page Update ---
110
-
111
- // Fetches a fresh Flight payload for the current URL and hands it to React.
112
- // React's reconciliation updates only the changed DOM nodes — no full reload.
113
- function refetchRSC(cssChanged) {
114
- var rsc = window.__NITRON_RSC__;
115
-
116
- if (!rsc || !rsc.root) {
117
- location.reload();
118
- return;
119
- }
120
-
121
- var url = location.pathname + location.search;
122
-
123
- fetch(RSC_ENDPOINT + "?url=" + encodeURIComponent(url), {
124
- headers: { "X-Nitron-SPA": "1" },
125
- credentials: "same-origin"
126
- })
127
- .then(function(r) {
128
- if (!r.ok) throw new Error("HTTP " + r.status);
129
-
130
- return r.text().then(function(text) {
131
- return parseLengthPrefixed(text);
132
- });
133
- })
134
- .then(function(d) {
135
- if (!d || !d.payload) {
136
- location.reload();
137
- return;
138
- }
139
-
140
- // Merge translations BEFORE navigate — persistent components (layouts) keep their keys
141
- if (d.translations) {
142
- var prev = window.__NITRON_TRANSLATIONS__;
143
-
144
- window.__NITRON_TRANSLATIONS__ = prev
145
- ? Object.assign({}, prev, d.translations)
146
- : d.translations;
147
- }
148
-
149
- // React reconciliation — only changed DOM nodes update
150
- rsc.navigate(d.payload);
151
-
152
- if (d.meta && d.meta.title) document.title = d.meta.title;
153
-
154
- if (cssChanged) refreshCss();
155
- })
156
- .catch(function() {
157
- // RSC fetch failed — full reload as last resort
158
- location.reload();
159
- });
160
- }
161
-
162
- // --- CSS Hot Reload ---
163
-
164
- // Cache-busts all stylesheets by appending ?t=<timestamp>
165
- function refreshCss() {
166
- var links = document.querySelectorAll('link[rel="stylesheet"]');
167
- var timestamp = Date.now();
168
-
169
- for (var i = 0; i < links.length; i++) {
170
- var href = (links[i].href || "").split("?")[0];
171
- links[i].href = href + "?t=" + timestamp;
172
- }
173
- }
174
-
175
- // --- Build Error Overlay ---
176
-
177
- function showErrorOverlay(message, file) {
178
- hideErrorOverlay();
179
-
180
- var overlay = document.createElement("div");
181
- overlay.id = "__nitron_error__";
182
- overlay.innerHTML =
183
- '<div style="position:fixed;inset:0;background:rgba(0,0,0,.95);color:#ff4444;padding:32px;font-family:monospace;z-index:999999;overflow:auto">' +
184
- '<div style="font-size:24px;font-weight:bold;margin-bottom:16px">Build Error</div>' +
185
- '<div style="color:#888;margin-bottom:16px">' + escapeHtml(file || "") + '</div>' +
186
- '<pre style="white-space:pre-wrap;background:#1a1a2e;padding:16px;border-radius:8px">' + escapeHtml(message) + '</pre>' +
187
- '<button onclick="this.parentNode.parentNode.remove()" style="position:absolute;top:16px;right:16px;background:#333;color:#fff;border:none;padding:8px 16px;cursor:pointer;border-radius:4px">Close</button>' +
188
- '</div>';
189
-
190
- document.body.appendChild(overlay);
191
- }
192
-
193
- function hideErrorOverlay() {
194
- var el = document.getElementById("__nitron_error__");
195
-
196
- if (el) {
197
- el.remove();
198
- }
199
- }
200
-
201
- // --- Utilities ---
202
-
203
- function escapeHtml(str) {
204
- return String(str || "")
205
- .replace(/&/g, "&amp;")
206
- .replace(/</g, "&lt;")
207
- .replace(/>/g, "&gt;");
208
- }
209
-
210
- // --- Initialize ---
211
-
212
- if (document.readyState === "loading") {
213
- document.addEventListener("DOMContentLoaded", connect);
214
- }
215
- else {
216
- connect();
217
- }
218
-
219
- })();
1
+ (function() {
2
+ "use strict";
3
+
4
+ var RSC_ENDPOINT = "/__nitron/rsc";
5
+ var ws = null;
6
+ var reconnectTimer = null;
7
+
8
+ function connect() {
9
+ if (ws) return;
10
+
11
+ var protocol = location.protocol === "https:" ? "wss:" : "ws:";
12
+ var url = protocol + "//" + location.host + "/__nitron_hmr";
13
+
14
+ try {
15
+ ws = new WebSocket(url);
16
+ }
17
+ catch (e) {
18
+ return;
19
+ }
20
+
21
+ ws.onopen = function() {
22
+ window.__nitron_hmr_connected__ = true;
23
+ hideErrorOverlay();
24
+ };
25
+
26
+ ws.onclose = function() {
27
+ window.__nitron_hmr_connected__ = false;
28
+ ws = null;
29
+
30
+ clearTimeout(reconnectTimer);
31
+ reconnectTimer = setTimeout(connect, 1000);
32
+ };
33
+
34
+ ws.onerror = function() {
35
+ window.__nitron_hmr_connected__ = false;
36
+ };
37
+
38
+ ws.onmessage = function(event) {
39
+ var msg;
40
+
41
+ try {
42
+ msg = JSON.parse(event.data);
43
+ }
44
+ catch (e) {
45
+ return;
46
+ }
47
+
48
+ if (msg.type === "change") {
49
+ hideErrorOverlay();
50
+
51
+ if (msg.changeType === "css") {
52
+ refreshCss();
53
+ return;
54
+ }
55
+
56
+ refetchRSC(msg.cssChanged);
57
+ }
58
+
59
+ if (msg.type === "fast-refresh") {
60
+ hideErrorOverlay();
61
+ handleFastRefresh(msg);
62
+ }
63
+
64
+ if (msg.type === "reload") {
65
+ location.reload();
66
+ }
67
+
68
+ if (msg.type === "error") {
69
+ showErrorOverlay(msg.message || "Unknown error", msg.file);
70
+ }
71
+ };
72
+ }
73
+
74
+ function parseLengthPrefixed(text) {
75
+ var nl = text.indexOf("\n");
76
+ if (nl === -1) return null;
77
+
78
+ var len = parseInt(text.substring(0, nl), 10);
79
+ if (isNaN(len) || len < 0) return null;
80
+
81
+ var flight = text.substring(nl + 1, nl + 1 + len);
82
+ var jsonStr = text.substring(nl + 1 + len);
83
+ var data;
84
+
85
+ try { data = JSON.parse(jsonStr); }
86
+ catch (e) { return null; }
87
+
88
+ return {
89
+ payload: flight,
90
+ meta: data.meta || null,
91
+ css: data.css || null,
92
+ translations: data.translations || null
93
+ };
94
+ }
95
+
96
+ function refetchRSC(cssChanged) {
97
+ var rsc = window.__NITRON_RSC__;
98
+
99
+ if (!rsc || !rsc.root) {
100
+ location.reload();
101
+ return;
102
+ }
103
+
104
+ var url = location.pathname + location.search;
105
+
106
+ fetch(RSC_ENDPOINT + "?url=" + encodeURIComponent(url), {
107
+ headers: { "X-Nitron-SPA": "1" },
108
+ credentials: "same-origin"
109
+ })
110
+ .then(function(r) {
111
+ if (!r.ok) throw new Error("HTTP " + r.status);
112
+
113
+ return r.text().then(function(text) {
114
+ return parseLengthPrefixed(text);
115
+ });
116
+ })
117
+ .then(function(d) {
118
+ if (!d || !d.payload) {
119
+ location.reload();
120
+ return;
121
+ }
122
+
123
+ if (d.translations) {
124
+ var prev = window.__NITRON_TRANSLATIONS__;
125
+
126
+ window.__NITRON_TRANSLATIONS__ = prev
127
+ ? Object.assign({}, prev, d.translations)
128
+ : d.translations;
129
+ }
130
+
131
+ rsc.navigate(d.payload);
132
+
133
+ if (d.meta && d.meta.title) document.title = d.meta.title;
134
+
135
+ if (cssChanged) refreshCss();
136
+ })
137
+ .catch(function() {
138
+ location.reload();
139
+ });
140
+ }
141
+
142
+ function refreshCss() {
143
+ var links = document.querySelectorAll('link[rel="stylesheet"]');
144
+ var timestamp = Date.now();
145
+
146
+ for (var i = 0; i < links.length; i++) {
147
+ var href = (links[i].href || "").split("?")[0];
148
+ links[i].href = href + "?t=" + timestamp;
149
+ }
150
+ }
151
+
152
+ function showErrorOverlay(message, file) {
153
+ hideErrorOverlay();
154
+
155
+ var overlay = document.createElement("div");
156
+ overlay.id = "__nitron_error__";
157
+ overlay.innerHTML =
158
+ '<div style="position:fixed;inset:0;background:rgba(0,0,0,.95);color:#ff4444;padding:32px;font-family:monospace;z-index:999999;overflow:auto">' +
159
+ '<div style="font-size:24px;font-weight:bold;margin-bottom:16px">Build Error</div>' +
160
+ '<div style="color:#888;margin-bottom:16px">' + escapeHtml(file || "") + '</div>' +
161
+ '<pre style="white-space:pre-wrap;background:#1a1a2e;padding:16px;border-radius:8px">' + escapeHtml(message) + '</pre>' +
162
+ '<button onclick="this.parentNode.parentNode.remove()" style="position:absolute;top:16px;right:16px;background:#333;color:#fff;border:none;padding:8px 16px;cursor:pointer;border-radius:4px">Close</button>' +
163
+ '</div>';
164
+
165
+ document.body.appendChild(overlay);
166
+ }
167
+
168
+ function hideErrorOverlay() {
169
+ var el = document.getElementById("__nitron_error__");
170
+
171
+ if (el) {
172
+ el.remove();
173
+ }
174
+ }
175
+
176
+ function handleFastRefresh(msg) {
177
+ const chunks = msg.chunks || [];
178
+ const timestamp = msg.timestamp || Date.now();
179
+ const cssChanged = msg.cssChanged || false;
180
+
181
+ const promises = chunks.map(function(chunk) {
182
+ if (chunk.includes("..") || chunk.includes("://")) return Promise.resolve();
183
+
184
+ return import("/storage/" + chunk + "?t=" + timestamp);
185
+ });
186
+
187
+ Promise.all(promises)
188
+ .then(function() {
189
+ if (window.$RefreshRuntime$) {
190
+ window.$RefreshRuntime$.performReactRefresh();
191
+ }
192
+
193
+ if (cssChanged) {
194
+ refreshCss();
195
+ }
196
+ })
197
+ .catch(function(err) {
198
+ console.warn("[HMR] Fast Refresh failed, full reload:", err.message);
199
+ location.reload();
200
+ });
201
+ }
202
+
203
+ function escapeHtml(str) {
204
+ return String(str || "")
205
+ .replace(/&/g, "&amp;")
206
+ .replace(/</g, "&lt;")
207
+ .replace(/>/g, "&gt;");
208
+ }
209
+
210
+ if (document.readyState === "loading") {
211
+ document.addEventListener("DOMContentLoaded", connect);
212
+ }
213
+ else {
214
+ connect();
215
+ }
216
+
217
+ })();
@@ -0,0 +1 @@
1
+ module.exports = require("react-refresh/runtime");
package/lib/View/View.js CHANGED
@@ -735,10 +735,15 @@ class View {
735
735
  runtimeScript += `<script${nonceAttr}>window.__NITRON_FLIGHT__=${JSON.stringify(escapedPayload)};</script>`;
736
736
  }
737
737
 
738
+ const refreshScript = this.#isDev
739
+ ? `<script src="/storage/js/react-refresh-runtime.js"${nonceAttr}></script>`
740
+ + `<script${nonceAttr}>$RefreshRuntime$.injectIntoGlobalHook(window);window.$RefreshReg$=function(t,i){$RefreshRuntime$.register(t,"__mod__ "+i)};window.$RefreshSig$=function(){return $RefreshRuntime$.createSignatureFunctionForTransform()};</script>`
741
+ : "";
742
+
738
743
  const vendorScript = `<script src="/storage/js/vendor.js"${nonceAttr}></script>`;
739
744
 
740
745
  const hmrScript = this.#isDev
741
- ? `<script src="/__nitron_client/socket.io.js"${nonceAttr}></script><script src="/storage/js/hmr.js"${nonceAttr}></script>`
746
+ ? `<script src="/storage/js/hmr.js"${nonceAttr}></script>`
742
747
  : "";
743
748
 
744
749
  const consumerScript = hasFlightPayload
@@ -757,7 +762,7 @@ ${this.#generateHead(meta, css)}
757
762
  <body>
758
763
  <div id="app">${html}</div>
759
764
  ${runtimeScript}
760
- ${vendorScript}${hmrScript}${consumerScript}${spaScript}${devIndicator}
765
+ ${refreshScript}${vendorScript}${hmrScript}${consumerScript}${spaScript}${devIndicator}
761
766
  </body>
762
767
  </html>`;
763
768
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitronjs/framework",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "NitronJS is a modern and extensible Node.js MVC framework built on Fastify. It focuses on clean architecture, modular structure, and developer productivity, offering built-in routing, middleware, configuration management, CLI tooling, and native React integration for scalable full-stack applications.",
5
5
  "bin": {
6
6
  "njs": "./cli/njs.js"
@@ -21,11 +21,11 @@
21
21
  "@fastify/helmet": "^13.0.2",
22
22
  "@fastify/multipart": "^9.3.0",
23
23
  "@fastify/static": "^8.3.0",
24
- "@tailwindcss/postcss": "^4.1.18",
24
+ "@parcel/watcher": "^2.5.6",
25
+ "@tailwindcss/node": "^4.2.2",
25
26
  "@types/react": "^19.2.7",
26
27
  "@types/react-dom": "^19.2.3",
27
28
  "bcrypt": "^5.1.1",
28
- "chokidar": "^5.0.0",
29
29
  "dotenv": "^17.2.3",
30
30
  "es-module-lexer": "^2.0.0",
31
31
  "esbuild": "^0.27.2",
@@ -33,15 +33,17 @@
33
33
  "mysql2": "^3.16.0",
34
34
  "nodemailer": "^7.0.11",
35
35
  "otpauth": "^9.5.0",
36
- "postcss": "^8.5.6",
36
+ "oxc-parser": "^0.121.0",
37
+ "oxc-transform": "^0.121.0",
37
38
  "qrcode": "^1.5.4",
38
39
  "react": "^19.2.3",
39
40
  "react-dom": "^19.2.3",
41
+ "react-refresh": "^0.18.0",
40
42
  "react-server-dom-webpack": "^19.2.4",
41
43
  "redis": "^5.6.0",
42
- "socket.io": "^4.8.1",
43
44
  "tailwindcss": "^4.1.18",
44
- "typescript": "^5.9.3"
45
+ "typescript": "^5.9.3",
46
+ "ws": "^8.18.3"
45
47
  },
46
48
  "keywords": [
47
49
  "nitronjs",