static.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. "log/slog"
  8. "slices"
  9. "strings"
  10. "miniflux.app/v2/internal/crypto"
  11. "github.com/tdewolff/minify/v2"
  12. "github.com/tdewolff/minify/v2/css"
  13. "github.com/tdewolff/minify/v2/js"
  14. "github.com/tdewolff/minify/v2/svg"
  15. )
  16. type asset struct {
  17. Data []byte
  18. Checksum string
  19. }
  20. // Static assets.
  21. var (
  22. StylesheetBundles map[string]asset
  23. JavascriptBundles map[string]asset
  24. BinaryBundles map[string]asset
  25. )
  26. //go:embed bin/*
  27. var binaryFiles embed.FS
  28. //go:embed css/*.css
  29. var stylesheetFiles embed.FS
  30. //go:embed js/*.js
  31. var javascriptFiles embed.FS
  32. func GenerateBinaryBundles() error {
  33. dirEntries, err := binaryFiles.ReadDir("bin")
  34. if err != nil {
  35. return err
  36. }
  37. BinaryBundles = make(map[string]asset, len(dirEntries))
  38. minifier := minify.New()
  39. minifier.Add("image/svg+xml", &svg.Minifier{
  40. KeepComments: true, // needed to keep the license
  41. })
  42. for _, dirEntry := range dirEntries {
  43. name := dirEntry.Name()
  44. data, err := binaryFiles.ReadFile("bin/" + name)
  45. if err != nil {
  46. return err
  47. }
  48. if strings.HasSuffix(name, ".svg") {
  49. // minifier.Bytes returns the data unchanged in case of error.
  50. data, err = minifier.Bytes("image/svg+xml", data)
  51. if err != nil {
  52. slog.Error("Unable to minimize the svg file", slog.String("filename", name), slog.Any("error", err))
  53. }
  54. }
  55. BinaryBundles[name] = asset{
  56. Data: data,
  57. Checksum: crypto.HashFromBytes(data),
  58. }
  59. }
  60. return nil
  61. }
  62. // GenerateStylesheetsBundles creates CSS bundles.
  63. func GenerateStylesheetsBundles() error {
  64. var bundles = map[string][]string{
  65. "light_serif": {"css/light.css", "css/serif.css", "css/common.css"},
  66. "light_sans_serif": {"css/light.css", "css/sans_serif.css", "css/common.css"},
  67. "dark_serif": {"css/dark.css", "css/serif.css", "css/common.css"},
  68. "dark_sans_serif": {"css/dark.css", "css/sans_serif.css", "css/common.css"},
  69. "system_serif": {"css/system.css", "css/serif.css", "css/common.css"},
  70. "system_sans_serif": {"css/system.css", "css/sans_serif.css", "css/common.css"},
  71. }
  72. StylesheetBundles = make(map[string]asset, len(bundles))
  73. minifier := minify.New()
  74. minifier.AddFunc("text/css", css.Minify)
  75. for bundle, srcFiles := range bundles {
  76. var buffer bytes.Buffer
  77. for _, srcFile := range srcFiles {
  78. fileData, err := stylesheetFiles.ReadFile(srcFile)
  79. if err != nil {
  80. return err
  81. }
  82. buffer.Write(fileData)
  83. }
  84. minifiedData, err := minifier.Bytes("text/css", buffer.Bytes())
  85. if err != nil {
  86. return err
  87. }
  88. StylesheetBundles[bundle] = asset{
  89. Data: minifiedData,
  90. Checksum: crypto.HashFromBytes(minifiedData),
  91. }
  92. }
  93. return nil
  94. }
  95. // GenerateJavascriptBundles creates JS bundles.
  96. func GenerateJavascriptBundles(webauthnEnabled bool) error {
  97. var bundles = map[string][]string{
  98. "app": {
  99. "js/touch_handler.js",
  100. "js/keyboard_handler.js",
  101. "js/app.js",
  102. },
  103. "service-worker": {
  104. "js/service_worker.js",
  105. },
  106. }
  107. if webauthnEnabled {
  108. bundles["app"] = slices.Insert(bundles["app"], 1, "js/webauthn_handler.js")
  109. }
  110. JavascriptBundles = make(map[string]asset, len(bundles))
  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. for _, srcFile := range srcFiles {
  117. fileData, err := javascriptFiles.ReadFile(srcFile)
  118. if err != nil {
  119. return err
  120. }
  121. buffer.Write(fileData)
  122. }
  123. minifiedData, err := minifier.Bytes("text/javascript", buffer.Bytes())
  124. if err != nil {
  125. return err
  126. }
  127. JavascriptBundles[bundle] = asset{
  128. Data: minifiedData,
  129. Checksum: crypto.HashFromBytes(minifiedData),
  130. }
  131. }
  132. return nil
  133. }