Repository: gohugoio/hugo (language: go)

Issue:
Print a warning when LiveReload fails to inject its code 
There are more than a few posts on the Hugo Discourse over the years through recent times of users having LiveReload not work in `hugo server`. The cause seems to be their templates missing either the `<head>` or the `<body>` tags. As @davidsneighbour has pointed out, HTML5 doesn't require these tags (or the `<html>` tag).

I feel it would make LiveReload more foolproof if the function would inject those html tags in order to inject the LiveReload script if they are missing, instead of failing silently without the user knowing why it isn't working. Or at least a WARNING in the console saying that those tags were not found and so LiveReload was not enabled. Maybe both. The first covers the fact that it'll just work for any user regardless of theme or experience and result in a good user experience (not having anything silently not working as expected) and the second teaches users that they are missing typical tags

I experienced this very thing today and was about to make a post on Discourse about it but was able to parse through the posts and find the issue. The problem is if you "Inspect Page" with DevTools, those missing html tags are injected by DevTools and so it looks like they are there, but you have to go to "View Source" to see that in fact they don't exist in the html.


Discussion / hints:

Fix patch:
diff --git a/transform/livereloadinject/livereloadinject.go b/transform/livereloadinject/livereloadinject.go
index 32ed55f63ae..d57ba4d244d 100644
--- a/transform/livereloadinject/livereloadinject.go
+++ b/transform/livereloadinject/livereloadinject.go
@@ -24,16 +24,24 @@ import (
 	"github.com/gohugoio/hugo/transform"
 )
 
+const warnMessage = `"head" or "body" tag is required in html to append livereload script. ` +
+	"As a fallback, Hugo injects it somewhere but it might not work properly."
+
+var warnScript = fmt.Sprintf(`<script data-no-instant defer>console.warn('%s');</script>`, warnMessage)
+
 type tag struct {
 	markup       []byte
 	appendScript bool
+	warnRequired bool
 }
 
 var tags = []tag{
-	{markup: []byte("<head>"), appendScript: true},
-	{markup: []byte("<HEAD>"), appendScript: true},
+	{markup: []byte("<head"), appendScript: true},
+	{markup: []byte("<HEAD"), appendScript: true},
 	{markup: []byte("</body>")},
 	{markup: []byte("</BODY>")},
+	{markup: []byte("<html"), appendScript: true, warnRequired: true},
+	{markup: []byte("<HTML"), appendScript: true, warnRequired: true},
 }
 
 // New creates a function that can be used
@@ -64,15 +72,19 @@ func New(baseURL url.URL) transform.Transformer {
 		copy(c, b)
 
 		if idx == -1 {
-			_, err := ft.To().Write(c)
-			return err
+			idx = len(b)
+			match = tag{warnRequired: true}
 		}
 
 		script := []byte(fmt.Sprintf(`<script src="%s" data-no-instant defer></script>`, html.EscapeString(src)))
 
 		i := idx
 		if match.appendScript {
-			i += len(match.markup)
+			i += bytes.Index(b[i:], []byte(">")) + 1
+		}
+
+		if match.warnRequired {
+			script = append(script, []byte(warnScript)...)
 		}
 
 		c = append(c[:i], append(script, c[i:]...)...)


Test patch:
diff --git a/transform/livereloadinject/livereloadinject_test.go b/transform/livereloadinject/livereloadinject_test.go
index b2ec4483ab1..d5cee79f8c4 100644
--- a/transform/livereloadinject/livereloadinject_test.go
+++ b/transform/livereloadinject/livereloadinject_test.go
@@ -58,7 +58,15 @@ func TestLiveReloadInject(t *testing.T) {
 		c.Assert(apply("foo</BODY>"), qt.Equals, "foo"+expectBase+"</BODY>")
 	})
 
+	c.Run("Html upper", func(c *qt.C) {
+		c.Assert(apply("<html>foo"), qt.Equals, "<html>"+expectBase+warnScript+"foo")
+	})
+
+	c.Run("Html upper with attr", func(c *qt.C) {
+		c.Assert(apply(`<html lang="en">foo`), qt.Equals, `<html lang="en">`+expectBase+warnScript+"foo")
+	})
+
 	c.Run("No match", func(c *qt.C) {
-		c.Assert(apply("<h1>No match</h1>"), qt.Equals, "<h1>No match</h1>")
+		c.Assert(apply("<h1>No match</h1>"), qt.Equals, "<h1>No match</h1>"+expectBase+warnScript)
 	})
 }