Implement automatic rebuild and browser reload during development:
- File watcher monitors .go files for changes with configurable extensions
- Builder compiles Go source to WASM on file changes
- LiveReload WebSocket server notifies connected browsers to reload
- DevServer combines all components for easy development setup
- HTML injection adds reload script automatically
Usage:
dev := host.NewDevServer("public", "index.html", ".", "public/app.wasm")
dev.ListenAndServe(":8080")
Closes #9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
81 lines
2.3 KiB
Go
81 lines
2.3 KiB
Go
package host
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
)
|
|
|
|
// ReloadScript is the JavaScript code injected into HTML pages for hot reload.
|
|
const ReloadScript = `<script>
|
|
(function() {
|
|
var wsUrl = 'ws://' + window.location.host + '/__livereload';
|
|
var ws;
|
|
var reconnectAttempts = 0;
|
|
var maxReconnectDelay = 5000;
|
|
|
|
function connect() {
|
|
ws = new WebSocket(wsUrl);
|
|
|
|
ws.onopen = function() {
|
|
console.log('[iris] Hot reload connected');
|
|
reconnectAttempts = 0;
|
|
};
|
|
|
|
ws.onmessage = function(event) {
|
|
if (event.data === 'reload') {
|
|
console.log('[iris] Reloading...');
|
|
window.location.reload();
|
|
}
|
|
};
|
|
|
|
ws.onclose = function() {
|
|
var delay = Math.min(1000 * Math.pow(2, reconnectAttempts), maxReconnectDelay);
|
|
reconnectAttempts++;
|
|
console.log('[iris] Connection lost. Reconnecting in ' + delay + 'ms...');
|
|
setTimeout(connect, delay);
|
|
};
|
|
|
|
ws.onerror = function() {
|
|
ws.close();
|
|
};
|
|
}
|
|
|
|
connect();
|
|
})();
|
|
</script>`
|
|
|
|
// InjectReloadScript injects the live reload script into HTML content.
|
|
// The script is inserted just before the closing </body> tag.
|
|
func InjectReloadScript(html []byte) []byte {
|
|
// Try to inject before </body>
|
|
bodyEnd := bytes.LastIndex(html, []byte("</body>"))
|
|
if bodyEnd != -1 {
|
|
result := make([]byte, 0, len(html)+len(ReloadScript))
|
|
result = append(result, html[:bodyEnd]...)
|
|
result = append(result, []byte(ReloadScript)...)
|
|
result = append(result, html[bodyEnd:]...)
|
|
return result
|
|
}
|
|
|
|
// Try to inject before </html>
|
|
htmlEnd := bytes.LastIndex(html, []byte("</html>"))
|
|
if htmlEnd != -1 {
|
|
result := make([]byte, 0, len(html)+len(ReloadScript))
|
|
result = append(result, html[:htmlEnd]...)
|
|
result = append(result, []byte(ReloadScript)...)
|
|
result = append(result, html[htmlEnd:]...)
|
|
return result
|
|
}
|
|
|
|
// Append at the end as fallback
|
|
result := make([]byte, 0, len(html)+len(ReloadScript))
|
|
result = append(result, html...)
|
|
result = append(result, []byte(ReloadScript)...)
|
|
return result
|
|
}
|
|
|
|
// IsHTMLContent checks if the content type indicates HTML.
|
|
func IsHTMLContent(contentType string) bool {
|
|
return strings.Contains(contentType, "text/html")
|
|
}
|