Skip to content

Commit 12e1ddf

Browse files
author
Olivier Bonnaure
committed
feat: live reload
1 parent 2656088 commit 12e1ddf

File tree

5 files changed

+189
-2
lines changed

5 files changed

+189
-2
lines changed

.lua/luaonbeans.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,12 @@ function LoadCronsJobs(path)
521521
dir:close()
522522
end
523523
end
524+
525+
function RefreshPageForDevMode()
526+
if BeansEnv == "development" then
527+
return [[<script src="/live_reload.js"></script>]]
528+
else
529+
return ""
530+
end
531+
end
532+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"actions" : {
3+
"run" : {
4+
"enabled" : true,
5+
"script" : "beans dev"
6+
}
7+
}
8+
}

app/views/layouts/app/index.html.etlua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,6 @@
8080
</div>
8181
<%- Partial("footer") %>
8282
</div>
83+
<%- RefreshPageForDevMode() %>
8384
</body>
8485
</html>

public/app.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/live_reload.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/**
2+
* Live Reload WebSocket Client
3+
* Include this script in your HTML pages to enable live reload functionality
4+
* when using the beans CLI development server.
5+
*/
6+
7+
(function() {
8+
'use strict';
9+
10+
// Configuration
11+
const WS_URL = 'ws://localhost:8081';
12+
const RECONNECT_DELAY = 1000;
13+
14+
Window.lob_ws = Window.lob_ws || null;
15+
let reconnectTimer = null;
16+
17+
// Create connection status indicator
18+
function createStatusIndicator() {
19+
const indicator = document.createElement('div');
20+
indicator.id = 'live-reload-status';
21+
indicator.style.cssText = `
22+
position: fixed;
23+
top: 10px;
24+
right: 10px;
25+
width: 12px;
26+
height: 12px;
27+
border-radius: 50%;
28+
background-color: #dc3545;
29+
z-index: 9999;
30+
transition: background-color 0.3s ease;
31+
`;
32+
document.body.appendChild(indicator);
33+
return indicator;
34+
}
35+
36+
// Update connection status
37+
function updateStatus(connected) {
38+
const indicator = document.getElementById('live-reload-status');
39+
if (indicator) {
40+
indicator.style.backgroundColor = connected ? '#28a745' : '#dc3545';
41+
}
42+
}
43+
44+
// Reload stylesheets to force CSS refresh
45+
function reloadStylesheets() {
46+
document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
47+
const newHref = link.href.replace(/\?.*$/, '') + '?v=' + Date.now();
48+
link.href = newHref;
49+
});
50+
}
51+
52+
// Connect to WebSocket server
53+
function connect() {
54+
if (Window.lob_ws && Window.lob_ws.readyState === WebSocket.OPEN) {
55+
updateStatus(true);
56+
return;
57+
}
58+
59+
try {
60+
Window.lob_ws = Window.lob_ws || new WebSocket(WS_URL);
61+
if (Window.lob_ws.readyState == 3) {
62+
Window.lob_ws = new WebSocket(WS_URL);
63+
}
64+
65+
Window.lob_ws.onopen = function() {
66+
updateStatus(true);
67+
};
68+
69+
Window.lob_ws.onmessage = async function(event) {
70+
try {
71+
const data = JSON.parse(event.data);
72+
if (data.type === 'file_changed') {
73+
// Reload page for relevant file changes
74+
if (shouldReload(data.path)) {
75+
setTimeout(async () => {
76+
const isCSS = data.path.toLowerCase().endsWith('.css');
77+
if (isCSS) {
78+
// For CSS changes, just reload stylesheets without touching body
79+
reloadStylesheets();
80+
} else {
81+
// For other changes, fetch and replace body content
82+
const newPage = await fetch(window.location.href, {
83+
headers: { "X-Requested-With": "fetch-update" }
84+
});
85+
const html = await newPage.text();
86+
87+
// Parse and replace <body> content safely
88+
const parser = new DOMParser();
89+
const doc = parser.parseFromString(html, "text/html");
90+
document.body.innerHTML = doc.body.innerHTML;
91+
92+
// Optionally re-run scripts if needed
93+
const scripts = document.body.querySelectorAll("script");
94+
scripts.forEach(oldScript => {
95+
const newScript = document.createElement("script");
96+
if (oldScript.src) {
97+
newScript.src = oldScript.src;
98+
} else {
99+
newScript.textContent = oldScript.textContent;
100+
}
101+
// Remove the old script node before appending new to avoid duplicates
102+
oldScript.parentNode.removeChild(oldScript);
103+
document.body.appendChild(newScript);
104+
});
105+
}
106+
}, 100);
107+
}
108+
}
109+
} catch (e) {
110+
console.log('Received:', event.data);
111+
}
112+
};
113+
114+
Window.lob_ws.onclose = function() {
115+
updateStatus(false);
116+
scheduleReconnect();
117+
};
118+
119+
Window.lob_ws.onerror = function(error) {
120+
updateStatus(false);
121+
scheduleReconnect();
122+
};
123+
124+
} catch (error) {
125+
scheduleReconnect();
126+
}
127+
}
128+
129+
// Determine if page should reload for this file change
130+
function shouldReload(filePath) {
131+
const reloadExtensions = ['.html', '.css', '.js', '.lua', '.etlua'];
132+
const reloadPaths = ['/'];
133+
134+
// Check file extension
135+
const hasReloadExtension = reloadExtensions.some(ext =>
136+
filePath.toLowerCase().endsWith(ext)
137+
);
138+
139+
// Check if file is in a reload-worthy directory
140+
const isInReloadPath = reloadPaths.some(path =>
141+
filePath.includes(path)
142+
);
143+
144+
return hasReloadExtension || isInReloadPath;
145+
}
146+
147+
// Schedule reconnection attempt
148+
function scheduleReconnect() {
149+
if (reconnectTimer) {
150+
clearTimeout(reconnectTimer);
151+
}
152+
153+
reconnectTimer = setTimeout(() => {
154+
connect();
155+
}, RECONNECT_DELAY);
156+
}
157+
158+
// Initialize live reload
159+
function init() {
160+
// Only run in development mode (when localhost)
161+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
162+
createStatusIndicator();
163+
connect();
164+
}
165+
}
166+
167+
// Start when DOM is ready
168+
init();
169+
})();

0 commit comments

Comments
 (0)