static.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package static // import "miniflux.app/v2/internal/ui/static"
  4. import (
  5. "bytes"
  6. "embed"
  7. "fmt"
  8. "miniflux.app/v2/internal/crypto"
  9. "github.com/tdewolff/minify/v2"
  10. "github.com/tdewolff/minify/v2/css"
  11. "github.com/tdewolff/minify/v2/js"
  12. )
  13. // Static assets.
  14. var (
  15. StylesheetBundleChecksums map[string]string
  16. StylesheetBundles map[string][]byte
  17. JavascriptBundleChecksums map[string]string
  18. JavascriptBundles map[string][]byte
  19. )
  20. //go:embed bin/*
  21. var binaryFiles embed.FS
  22. //go:embed css/*.css
  23. var stylesheetFiles embed.FS
  24. //go:embed js/*.js
  25. var javascriptFiles embed.FS
  26. var binaryFileChecksums map[string]string
  27. // CalculateBinaryFileChecksums generates hash of embed binary files.
  28. func CalculateBinaryFileChecksums() error {
  29. binaryFileChecksums = make(map[string]string)
  30. dirEntries, err := binaryFiles.ReadDir("bin")
  31. if err != nil {
  32. return err
  33. }
  34. for _, dirEntry := range dirEntries {
  35. data, err := LoadBinaryFile(dirEntry.Name())
  36. if err != nil {
  37. return err
  38. }
  39. binaryFileChecksums[dirEntry.Name()] = crypto.HashFromBytes(data)
  40. }
  41. return nil
  42. }
  43. // LoadBinaryFile loads an embed binary file.
  44. func LoadBinaryFile(filename string) ([]byte, error) {
  45. return binaryFiles.ReadFile(fmt.Sprintf(`bin/%s`, filename))
  46. }
  47. // GetBinaryFileChecksum returns a binary file checksum.
  48. func GetBinaryFileChecksum(filename string) (string, error) {
  49. if _, found := binaryFileChecksums[filename]; !found {
  50. return "", fmt.Errorf(`static: unable to find checksum for %q`, filename)
  51. }
  52. return binaryFileChecksums[filename], nil
  53. }
  54. // GenerateStylesheetsBundles creates CSS bundles.
  55. func GenerateStylesheetsBundles() error {
  56. var bundles = map[string][]string{
  57. "light_serif": {"css/light.css", "css/serif.css", "css/common.css"},
  58. "light_sans_serif": {"css/light.css", "css/sans_serif.css", "css/common.css"},
  59. "dark_serif": {"css/dark.css", "css/serif.css", "css/common.css"},
  60. "dark_sans_serif": {"css/dark.css", "css/sans_serif.css", "css/common.css"},
  61. "system_serif": {"css/system.css", "css/serif.css", "css/common.css"},
  62. "system_sans_serif": {"css/system.css", "css/sans_serif.css", "css/common.css"},
  63. }
  64. StylesheetBundles = make(map[string][]byte)
  65. StylesheetBundleChecksums = make(map[string]string)
  66. minifier := minify.New()
  67. minifier.AddFunc("text/css", css.Minify)
  68. for bundle, srcFiles := range bundles {
  69. var buffer bytes.Buffer
  70. for _, srcFile := range srcFiles {
  71. fileData, err := stylesheetFiles.ReadFile(srcFile)
  72. if err != nil {
  73. return err
  74. }
  75. buffer.Write(fileData)
  76. }
  77. minifiedData, err := minifier.Bytes("text/css", buffer.Bytes())
  78. if err != nil {
  79. return err
  80. }
  81. StylesheetBundles[bundle] = minifiedData
  82. StylesheetBundleChecksums[bundle] = crypto.HashFromBytes(minifiedData)
  83. }
  84. return nil
  85. }
  86. // GenerateJavascriptBundles creates JS bundles.
  87. func GenerateJavascriptBundles() error {
  88. var bundles = map[string][]string{
  89. "app": {
  90. "js/tt.js", // has to be first
  91. "js/touch_handler.js",
  92. "js/keyboard_handler.js",
  93. "js/request_builder.js",
  94. "js/modal_handler.js",
  95. "js/app.js",
  96. "js/webauthn_handler.js",
  97. "js/bootstrap.js",
  98. },
  99. "service-worker": {
  100. "js/service_worker.js",
  101. },
  102. }
  103. var prefixes = map[string]string{
  104. "app": "(function(){'use strict';",
  105. }
  106. var suffixes = map[string]string{
  107. "app": "})();",
  108. }
  109. JavascriptBundles = make(map[string][]byte)
  110. JavascriptBundleChecksums = make(map[string]string)
  111. jsMinifier := js.Minifier{Version: 2020}
  112. minifier := minify.New()
  113. minifier.AddFunc("text/javascript", jsMinifier.Minify)
  114. for bundle, srcFiles := range bundles {
  115. var buffer bytes.Buffer
  116. if prefix, found := prefixes[bundle]; found {
  117. buffer.WriteString(prefix)
  118. }
  119. for _, srcFile := range srcFiles {
  120. fileData, err := javascriptFiles.ReadFile(srcFile)
  121. if err != nil {
  122. return err
  123. }
  124. buffer.Write(fileData)
  125. }
  126. if suffix, found := suffixes[bundle]; found {
  127. buffer.WriteString(suffix)
  128. }
  129. minifiedData, err := minifier.Bytes("text/javascript", buffer.Bytes())
  130. if err != nil {
  131. return err
  132. }
  133. JavascriptBundles[bundle] = minifiedData
  134. JavascriptBundleChecksums[bundle] = crypto.HashFromBytes(minifiedData)
  135. }
  136. return nil
  137. }