static.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. type asset struct {
  14. Data []byte
  15. Checksum string
  16. }
  17. // Static assets.
  18. var (
  19. StylesheetBundles map[string]asset
  20. JavascriptBundles map[string]asset
  21. binaryFileChecksums map[string]string
  22. )
  23. //go:embed bin/*
  24. var binaryFiles embed.FS
  25. //go:embed css/*.css
  26. var stylesheetFiles embed.FS
  27. //go:embed js/*.js
  28. var javascriptFiles embed.FS
  29. // CalculateBinaryFileChecksums generates hash of embed binary files.
  30. func CalculateBinaryFileChecksums() error {
  31. dirEntries, err := binaryFiles.ReadDir("bin")
  32. if err != nil {
  33. return err
  34. }
  35. binaryFileChecksums = make(map[string]string, len(dirEntries))
  36. for _, dirEntry := range dirEntries {
  37. data, err := LoadBinaryFile(dirEntry.Name())
  38. if err != nil {
  39. return err
  40. }
  41. binaryFileChecksums[dirEntry.Name()] = crypto.HashFromBytes(data)
  42. }
  43. return nil
  44. }
  45. // LoadBinaryFile loads an embed binary file.
  46. func LoadBinaryFile(filename string) ([]byte, error) {
  47. return binaryFiles.ReadFile("bin/" + filename)
  48. }
  49. // GetBinaryFileChecksum returns a binary file checksum.
  50. func GetBinaryFileChecksum(filename string) (string, error) {
  51. data, found := binaryFileChecksums[filename]
  52. if !found {
  53. return "", fmt.Errorf(`static: unable to find checksum for %q`, filename)
  54. }
  55. return data, nil
  56. }
  57. // GenerateStylesheetsBundles creates CSS bundles.
  58. func GenerateStylesheetsBundles() error {
  59. var bundles = map[string][]string{
  60. "light_serif": {"css/light.css", "css/serif.css", "css/common.css"},
  61. "light_sans_serif": {"css/light.css", "css/sans_serif.css", "css/common.css"},
  62. "dark_serif": {"css/dark.css", "css/serif.css", "css/common.css"},
  63. "dark_sans_serif": {"css/dark.css", "css/sans_serif.css", "css/common.css"},
  64. "system_serif": {"css/system.css", "css/serif.css", "css/common.css"},
  65. "system_sans_serif": {"css/system.css", "css/sans_serif.css", "css/common.css"},
  66. }
  67. StylesheetBundles = make(map[string]asset, len(bundles))
  68. minifier := minify.New()
  69. minifier.AddFunc("text/css", css.Minify)
  70. for bundle, srcFiles := range bundles {
  71. var buffer bytes.Buffer
  72. for _, srcFile := range srcFiles {
  73. fileData, err := stylesheetFiles.ReadFile(srcFile)
  74. if err != nil {
  75. return err
  76. }
  77. buffer.Write(fileData)
  78. }
  79. minifiedData, err := minifier.Bytes("text/css", buffer.Bytes())
  80. if err != nil {
  81. return err
  82. }
  83. StylesheetBundles[bundle] = asset{
  84. Data: minifiedData,
  85. Checksum: crypto.HashFromBytes(minifiedData),
  86. }
  87. }
  88. return nil
  89. }
  90. // GenerateJavascriptBundles creates JS bundles.
  91. func GenerateJavascriptBundles() error {
  92. var bundles = map[string][]string{
  93. "app": {
  94. "js/tt.js", // has to be first
  95. "js/touch_handler.js",
  96. "js/keyboard_handler.js",
  97. "js/modal_handler.js",
  98. "js/webauthn_handler.js",
  99. "js/app.js",
  100. },
  101. "service-worker": {
  102. "js/service_worker.js",
  103. },
  104. }
  105. JavascriptBundles = make(map[string]asset, len(bundles))
  106. jsMinifier := js.Minifier{Version: 2020}
  107. minifier := minify.New()
  108. minifier.AddFunc("text/javascript", jsMinifier.Minify)
  109. for bundle, srcFiles := range bundles {
  110. var buffer bytes.Buffer
  111. for _, srcFile := range srcFiles {
  112. fileData, err := javascriptFiles.ReadFile(srcFile)
  113. if err != nil {
  114. return err
  115. }
  116. buffer.Write(fileData)
  117. }
  118. minifiedData, err := minifier.Bytes("text/javascript", buffer.Bytes())
  119. if err != nil {
  120. return err
  121. }
  122. JavascriptBundles[bundle] = asset{
  123. Data: minifiedData,
  124. Checksum: crypto.HashFromBytes(minifiedData),
  125. }
  126. }
  127. return nil
  128. }