github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/transform/livereloadinject/livereloadinject.go (about) 1 // Copyright 2018 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package livereloadinject 15 16 import ( 17 "bytes" 18 "fmt" 19 "html" 20 "net/url" 21 "strings" 22 23 "github.com/gohugoio/hugo/helpers" 24 "github.com/gohugoio/hugo/transform" 25 ) 26 27 const warnMessage = `"head" or "body" tag is required in html to append livereload script. ` + 28 "As a fallback, Hugo injects it somewhere but it might not work properly." 29 30 var warnScript = fmt.Sprintf(`<script data-no-instant defer>console.warn('%s');</script>`, warnMessage) 31 32 type tag struct { 33 markup []byte 34 appendScript bool 35 warnRequired bool 36 } 37 38 var tags = []tag{ 39 {markup: []byte("<head"), appendScript: true}, 40 {markup: []byte("<HEAD"), appendScript: true}, 41 {markup: []byte("</body>")}, 42 {markup: []byte("</BODY>")}, 43 {markup: []byte("<html"), appendScript: true, warnRequired: true}, 44 {markup: []byte("<HTML"), appendScript: true, warnRequired: true}, 45 } 46 47 // New creates a function that can be used 48 // to inject a script tag for the livereload JavaScript in a HTML document. 49 func New(baseURL url.URL) transform.Transformer { 50 return func(ft transform.FromTo) error { 51 b := ft.From().Bytes() 52 idx := -1 53 var match tag 54 // We used to insert the livereload script right before the closing body. 55 // This does not work when combined with tools such as Turbolinks. 56 // So we try to inject the script as early as possible. 57 for _, t := range tags { 58 idx = bytes.Index(b, t.markup) 59 if idx != -1 { 60 match = t 61 break 62 } 63 } 64 65 path := strings.TrimSuffix(baseURL.Path, "/") 66 67 src := path + "/livereload.js?mindelay=10&v=2" 68 src += "&port=" + baseURL.Port() 69 src += "&path=" + strings.TrimPrefix(path+"/livereload", "/") 70 71 c := make([]byte, len(b)) 72 copy(c, b) 73 74 if idx == -1 { 75 idx = len(b) 76 match = tag{warnRequired: true} 77 } 78 79 script := []byte(fmt.Sprintf(`<script src="%s" data-no-instant defer></script>`, html.EscapeString(src))) 80 81 i := idx 82 if match.appendScript { 83 i += bytes.Index(b[i:], []byte(">")) + 1 84 } 85 86 if match.warnRequired { 87 script = append(script, []byte(warnScript)...) 88 } 89 90 c = append(c[:i], append(script, c[i:]...)...) 91 92 if _, err := ft.To().Write(c); err != nil { 93 helpers.DistinctWarnLog.Println("Failed to inject LiveReload script:", err) 94 } 95 return nil 96 } 97 }