github.com/pbberlin/go-pwa@v0.0.0-20220328105622-7c26e0ca1ab8/pkg/static/template-executors.go (about) 1 package static 2 3 import ( 4 "bytes" 5 "fmt" 6 "html/template" 7 htmltpl "html/template" 8 "io" 9 "log" 10 "net/http" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 texttpl "text/template" 16 17 "github.com/pbberlin/go-pwa/pkg/cfg" 18 ) 19 20 // listForPreCaching compiles the list of files with a version 21 // to be pre-cached by service worker 22 func (dirs dirsT) listForPreCaching(w http.ResponseWriter) []string { 23 24 var seed = []string{ 25 "/index.html", 26 "/offline.html", 27 // ... dynamic css, js, webp, json 28 29 // example for external url res 30 // "https://fonts.google.com/icon?family=Material+Icons", 31 } 32 33 res := make([]string, len(seed), 16) 34 copy(res, seed) 35 36 for _, dir := range dirs { 37 38 if dir.isSingleFile { 39 if !dir.swpc.cache { 40 continue 41 } 42 // these files are requested without 43 // version prefix in the path, 44 // thus we dont need the version prefix here 45 res = append(res, dir.urlPath) 46 continue 47 } 48 49 files, err := filesOfDir(dir.src) 50 if err != nil { 51 fmt.Fprint(w, err) 52 return res 53 } 54 55 for _, file := range files { 56 57 if file.IsDir() { 58 continue 59 } 60 61 if len(dir.swpc.includeExtensions) > 0 { 62 for _, ext := range dir.swpc.includeExtensions { 63 if strings.HasSuffix(file.Name(), ext) { 64 continue 65 } 66 } 67 } 68 69 // fmt.Fprintf(w, "\t %v\n", file.Name()) 70 pth := path.Join(dir.src, cfg.Get().TS, file.Name()) 71 pth = strings.Replace(pth, "app-bucket/", "./", 1) 72 res = append(res, pth) 73 } 74 } 75 76 return res 77 } 78 79 /* 80 This is unused and leads nowhere. 81 82 There is no idiomatic way to fill a template of Javascript 83 with snippets of Javascript :-( 84 85 A clean way woudl be to create a template file 86 from a JavaScript file fill its 87 JavaScript fields {{ .MyJavaScriptSnippet }} 88 89 We would want the template be of type html.JS or html.JSStr 90 91 If we use HTML types, we get escapings 92 93 "<" being escaped to < in the template 94 95 "'" in .MyJavaScriptSnippet being escaped to &23232; 96 97 98 */ 99 func javascriptTemplate(srcPth string, w io.Writer) { 100 101 _ = htmltpl.HTMLEscapeString("force-import") 102 _ = texttpl.HTMLEscapeString("force-import") // avoiding escaping hassle by using text templates => fail 103 104 btsSW, err := os.ReadFile(srcPth) 105 if err != nil { 106 fmt.Fprintf(w, "could not read service worker template %v\n", err) 107 return 108 } 109 strSW := string(btsSW) // file content bytes to string 110 strSWAsJS := htmltpl.JS(strSW) // string into type Javascript code 111 _ = strSWAsJS 112 113 // 114 115 tpl := htmltpl.New("JS body") 116 tpl.Parse("let x=1; {{js .JS}}") // we want to put in strSWAsJS, but the argument must be string 117 118 } 119 120 // execServiceWorker executes the service worker template 121 // and writes the result into the file system; 122 // see javascriptTemplate for a discussion of alternatives 123 func execServiceWorker(dirs dirsT, dir dirT, w http.ResponseWriter) { 124 125 t, err := htmltpl.ParseFiles(dir.srcTpl) 126 if err != nil { 127 fmt.Fprintf(w, "could not parse template %v: %v\n", dir.srcTpl, err) 128 return 129 } 130 131 data := struct { 132 Version string 133 ListOfFiles htmltpl.HTML // neither .JS nor .JSStr do work 134 }{ 135 Version: cfg.Get().TS, 136 ListOfFiles: htmltpl.HTML("'" + strings.Join(dirs.listForPreCaching(w), "',\n '") + "'"), 137 } 138 139 bts := &bytes.Buffer{} 140 err = t.Execute(bts, data) 141 if err != nil { 142 fmt.Fprintf(w, "could not execute template %v: %v\n", dir.srcTpl, err) 143 return 144 } 145 146 dstPth := path.Join(dir.src, dir.fn) 147 os.WriteFile(dstPth, bts.Bytes(), 0644) 148 } 149 150 func manifestIconList(w http.ResponseWriter) string { 151 152 /* template string for each file 153 { 154 "src": "/img/icon-072.webp", 155 "sizes": "72x72", 156 "type": "image/png", 157 "purpose": "maskable" 158 }, 159 */ 160 ts := ` { 161 "src": "/img/{{.FN}}", 162 "sizes": "{{.Size}}", 163 "type": "image/png", 164 "purpose": "{{.MaskableOrAny}}" 165 }` 166 167 t := template.New("list-entry") 168 t, err := t.Parse(ts) 169 if err != nil { 170 log.Fatalf("error parsing icon template: %v", err) 171 } 172 173 // globbing for .../img/icon-072.webp, ... /img/icon-128.webp ... 174 icons, err := filepath.Glob("./app-bucket/img/icon-???.webp") 175 if err != nil { 176 log.Fatalf("error reading icons: %v", err) 177 } 178 179 sb := &strings.Builder{} 180 181 for idx, icon := range icons { 182 icon = filepath.Base(icon) 183 // log.Printf("\ticon %v", icon) 184 185 fnp := icon // file name part containing the icon size, i.e. 072 186 fnp = strings.TrimSuffix(fnp, filepath.Ext(icon)) // remove trailing .webp 187 fnp = strings.TrimPrefix(fnp, "icon-") // leading icon- 188 if strings.HasPrefix(fnp, "0") { 189 fnp = strings.TrimPrefix(fnp, "0") 190 } 191 // size := "072x072" 192 size := fmt.Sprintf("%vx%v", fnp, fnp) 193 194 maskableOrAny := "maskable" 195 if len(fnp) > 2 && fnp > "128" { 196 maskableOrAny = "any" 197 } 198 199 data := struct { 200 FN string 201 Size string 202 MaskableOrAny string 203 }{ 204 FN: icon, 205 Size: size, 206 MaskableOrAny: maskableOrAny, 207 } 208 t.Execute(sb, data) 209 if idx < len(icons)-1 { 210 fmt.Fprint(sb, ",\n") // trailing comma - except for last iteration 211 } 212 } 213 214 return sb.String() 215 } 216 217 func execManifest(dirs dirsT, dir dirT, w http.ResponseWriter) { 218 219 t, err := htmltpl.ParseFiles(dir.srcTpl) 220 if err != nil { 221 fmt.Fprintf(w, "could not parse template %v: %v\n", dir.srcTpl, err) 222 return 223 } 224 225 data := struct { 226 Title string 227 TitleShort string 228 Description string 229 IconList htmltpl.HTML 230 }{ 231 Title: cfg.Get().Title, 232 TitleShort: cfg.Get().TitleShort, 233 Description: cfg.Get().Description, 234 IconList: htmltpl.HTML(manifestIconList(w)), 235 } 236 237 bts := &bytes.Buffer{} 238 err = t.Execute(bts, data) 239 if err != nil { 240 fmt.Fprintf(w, "could not execute template %v: %v\n", dir.srcTpl, err) 241 return 242 } 243 244 dstPth := path.Join(dir.src, dir.fn) 245 os.WriteFile(dstPth, bts.Bytes(), 0644) 246 } 247 248 func execDB(dirs dirsT, dir dirT, w http.ResponseWriter) { 249 250 t, err := htmltpl.ParseFiles(dir.srcTpl) 251 if err != nil { 252 fmt.Fprintf(w, "could not parse template %v: %v\n", dir.srcTpl, err) 253 return 254 } 255 256 data := struct { 257 SchemaVersion int 258 }{ 259 SchemaVersion: cfg.Get().SchemaVersion, 260 } 261 262 bts := &bytes.Buffer{} 263 err = t.Execute(bts, data) 264 if err != nil { 265 fmt.Fprintf(w, "could not execute template %v: %v\n", dir.srcTpl, err) 266 return 267 } 268 269 dstPth := path.Join(dir.src, dir.fn) 270 os.WriteFile(dstPth, bts.Bytes(), 0644) 271 }