static.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. //go:build !wasm
  2. // +build !wasm
  3. package app
  4. import (
  5. "io/ioutil"
  6. "net/http"
  7. "net/http/httptest"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "github.com/maxence-charriere/go-app/v9/pkg/errors"
  12. )
  13. // GenerateStaticWebsite generates the files to run a PWA built with go-app as a
  14. // static website in the specified directory. Static websites can be used with
  15. // hosts such as Github Pages.
  16. //
  17. // Note that app.wasm must still be built separately and put into the web
  18. // directory.
  19. func GenerateStaticWebsite(dir string, h *Handler, pages ...string) error {
  20. if dir == "" {
  21. dir = "."
  22. }
  23. resources := map[string]struct{}{
  24. "/": {},
  25. "/wasm_exec.js": {},
  26. "/app.js": {},
  27. "/app-worker.js": {},
  28. "/manifest.webmanifest": {},
  29. "/app.css": {},
  30. "/web": {},
  31. }
  32. for path := range routes.routes {
  33. resources[path] = struct{}{}
  34. }
  35. for _, p := range pages {
  36. if p == "" {
  37. continue
  38. }
  39. if !strings.HasPrefix(p, "/") {
  40. p = "/" + p
  41. }
  42. resources[p] = struct{}{}
  43. }
  44. server := httptest.NewServer(h)
  45. defer server.Close()
  46. for path := range resources {
  47. switch path {
  48. case "/web":
  49. if err := createStaticDir(filepath.Join(dir, path), ""); err != nil {
  50. return errors.New("creating web directory failed").Wrap(err)
  51. }
  52. default:
  53. filename := path
  54. if filename == "/" {
  55. filename = "/index.html"
  56. }
  57. f, err := createStaticFile(dir, filename)
  58. if err != nil {
  59. return errors.New("creating file failed").
  60. Tag("path", path).
  61. Tag("filename", filename).
  62. Wrap(err)
  63. }
  64. defer f.Close()
  65. page, err := createStaticPage(server.URL + path)
  66. if err != nil {
  67. return errors.New("creating page failed").
  68. Tag("path", path).
  69. Tag("filename", filename).
  70. Wrap(err)
  71. }
  72. if n, err := f.Write(page); err != nil {
  73. return errors.New("writing page failed").
  74. Tag("path", path).
  75. Tag("filename", filename).
  76. Tag("bytes-written", n).
  77. Wrap(err)
  78. }
  79. }
  80. }
  81. return nil
  82. }
  83. func createStaticDir(dir, path string) error {
  84. dir = filepath.Join(dir, filepath.Dir(path))
  85. if _, err := os.Stat(dir); !os.IsNotExist(err) {
  86. return nil
  87. }
  88. return os.MkdirAll(filepath.Join(dir), 0755)
  89. }
  90. func createStaticFile(dir, path string) (*os.File, error) {
  91. if err := createStaticDir(dir, path); err != nil {
  92. return nil, errors.New("creating file directory failed").Wrap(err)
  93. }
  94. filename := filepath.Join(dir, path)
  95. if filepath.Ext(filename) == "" {
  96. filename += ".html"
  97. }
  98. return os.Create(filename)
  99. }
  100. func createStaticPage(path string) ([]byte, error) {
  101. req, err := http.NewRequest(http.MethodGet, path, nil)
  102. if err != nil {
  103. return nil, errors.New("creating http request failed").
  104. Tag("path", path).
  105. Wrap(err)
  106. }
  107. res, err := http.DefaultClient.Do(req)
  108. if err != nil {
  109. return nil, errors.New("http request failed").
  110. Tag("path", path).
  111. Wrap(err)
  112. }
  113. defer res.Body.Close()
  114. body, err := ioutil.ReadAll(res.Body)
  115. if err != nil {
  116. return nil, errors.New("reading request body failed").
  117. Tag("path", path).
  118. Wrap(err)
  119. }
  120. return body, nil
  121. }