transform/livereloadinject: Inject livereload script right after head if possible

We used to insert the livereload script right before the closing body.

This dord  not work when combined with tools such as Turbolinks.

This commit changes it So we try to inject the script as early as possible.

Fixes #6821
This commit is contained in:
Bjørn Erik Pedersen 2020-01-29 12:46:18 +01:00
parent 281abb18ee
commit 8f08cdd0ac
2 changed files with 71 additions and 24 deletions

View file

@ -21,25 +21,54 @@ import (
"github.com/gohugoio/hugo/transform" "github.com/gohugoio/hugo/transform"
) )
type tag struct {
markup []byte
appendScript bool
}
var tags = []tag{
tag{markup: []byte("<head>"), appendScript: true},
tag{markup: []byte("<HEAD>"), appendScript: true},
tag{markup: []byte("</body>")},
tag{markup: []byte("</BODY>")},
}
// New creates a function that can be used // New creates a function that can be used
// to inject a script tag for the livereload JavaScript in a HTML document. // to inject a script tag for the livereload JavaScript in a HTML document.
func New(port int) transform.Transformer { func New(port int) transform.Transformer {
return func(ft transform.FromTo) error { return func(ft transform.FromTo) error {
b := ft.From().Bytes() b := ft.From().Bytes()
endBodyTag := "</body>" var idx = -1
match := []byte(endBodyTag) var match tag
replaceTemplate := `<script data-no-instant>document.write('<script src="/livereload.js?port=%d&mindelay=10&v=2"></' + 'script>')</script>%s` // We used to insert the livereload script right before the closing body.
replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) // This does not work when combined with tools such as Turbolinks.
// So we try to inject the script as early as possible.
newcontent := bytes.Replace(b, match, replace, 1) for _, t := range tags {
if len(newcontent) == len(b) { idx = bytes.Index(b, t.markup)
endBodyTag = "</BODY>" if idx != -1 {
replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) match = t
match := []byte(endBodyTag) break
newcontent = bytes.Replace(b, match, replace, 1) }
} }
if _, err := ft.To().Write(newcontent); err != nil { c := make([]byte, len(b))
copy(c, b)
if idx == -1 {
_, err := ft.To().Write(c)
return err
}
script := []byte(fmt.Sprintf(`<script data-no-instant>document.write('<script src="/livereload.js?port=%d&mindelay=10&v=2"></' + 'script>')</script>`, port))
i := idx
if match.appendScript {
i += len(match.markup)
}
c = append(c[:i], append(script, c[i:]...)...)
if _, err := ft.To().Write(c); err != nil {
helpers.DistinctWarnLog.Println("Failed to inject LiveReload script:", err) helpers.DistinctWarnLog.Println("Failed to inject LiveReload script:", err)
} }
return nil return nil

View file

@ -15,27 +15,45 @@ package livereloadinject
import ( import (
"bytes" "bytes"
"fmt"
"strings" "strings"
"testing" "testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/transform" "github.com/gohugoio/hugo/transform"
) )
func TestLiveReloadInject(t *testing.T) { func TestLiveReloadInject(t *testing.T) {
doTestLiveReloadInject(t, "</body>") c := qt.New(t)
doTestLiveReloadInject(t, "</BODY>")
}
func doTestLiveReloadInject(t *testing.T, bodyEndTag string) { expectBase := `<script data-no-instant>document.write('<script src="/livereload.js?port=1313&mindelay=10&v=2"></' + 'script>')</script>`
out := new(bytes.Buffer) apply := func(s string) string {
in := strings.NewReader(bodyEndTag) out := new(bytes.Buffer)
in := strings.NewReader(s)
tr := transform.New(New(1313)) tr := transform.New(New(1313))
tr.Apply(out, in) tr.Apply(out, in)
expected := fmt.Sprintf(`<script data-no-instant>document.write('<script src="/livereload.js?port=1313&mindelay=10&v=2"></' + 'script>')</script>%s`, bodyEndTag) return out.String()
if out.String() != expected {
t.Errorf("Expected %s got %s", expected, out.String())
} }
c.Run("Head lower", func(c *qt.C) {
c.Assert(apply("<html><head>foo"), qt.Equals, "<html><head>"+expectBase+"foo")
})
c.Run("Head upper", func(c *qt.C) {
c.Assert(apply("<html><HEAD>foo"), qt.Equals, "<html><HEAD>"+expectBase+"foo")
})
c.Run("Body lower", func(c *qt.C) {
c.Assert(apply("foo</body>"), qt.Equals, "foo"+expectBase+"</body>")
})
c.Run("Body upper", func(c *qt.C) {
c.Assert(apply("foo</BODY>"), qt.Equals, "foo"+expectBase+"</BODY>")
})
c.Run("No match", func(c *qt.C) {
c.Assert(apply("<h1>No match</h1>"), qt.Equals, "<h1>No match</h1>")
})
} }