├── .github
├── README.md
└── workflows
│ ├── release.yml
│ └── run-tests.yml
├── .gitignore
├── .goreleaser.yaml
├── LICENSE
├── config.go
├── css.go
├── css_test.go
├── engine.go
├── engine_test.go
├── examples
├── echo
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── main_test.go
│ └── models
│ │ └── props.go
├── fiber
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── main_test.go
│ └── models
│ │ └── props.go
├── frontend-mui
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── go.png
│ │ └── react.png
│ ├── src
│ │ ├── Home.css
│ │ ├── Home.tsx
│ │ ├── Layout.tsx
│ │ ├── components
│ │ │ └── Counter.tsx
│ │ ├── generated.d.ts
│ │ └── index.d.ts
│ └── tsconfig.json
├── frontend-tailwind
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── go.png
│ │ └── react.png
│ ├── src
│ │ ├── Home.tsx
│ │ ├── Main.css
│ │ ├── components
│ │ │ └── Counter.tsx
│ │ ├── generated.d.ts
│ │ └── index.d.ts
│ ├── tailwind.config.js
│ └── tsconfig.json
├── frontend
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── go.png
│ │ └── react.png
│ ├── src
│ │ ├── Home.css
│ │ ├── Home.tsx
│ │ ├── Layout.tsx
│ │ ├── components
│ │ │ ├── Counter.module.css
│ │ │ └── Counter.tsx
│ │ ├── generated.d.ts
│ │ └── index.d.ts
│ └── tsconfig.json
└── gin
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── docker-compose.yml
│ ├── go.mod
│ ├── go.sum
│ ├── main.go
│ ├── main_test.go
│ └── models
│ └── props.go
├── go.mod
├── go.sum
├── go.work
├── go.work.sum
├── gossr-cli
├── cmd
│ ├── create
│ │ ├── bootstrapper.go
│ │ ├── create.go
│ │ ├── prompts.go
│ │ └── temp_dir.go
│ ├── root.go
│ └── update
│ │ ├── check_update.go
│ │ ├── latest_version.go
│ │ ├── update.go
│ │ └── version_file.go
├── go.mod
├── go.sum
├── logger
│ └── logger.go
├── main.go
└── utils
│ ├── error.go
│ └── path.go
├── hotreload.go
├── internal
├── cache
│ └── manager.go
├── html
│ ├── base_template.go
│ ├── error_template.go
│ └── render_html.go
├── reactbuilder
│ ├── build.go
│ └── contents.go
├── typeconverter
│ ├── create_temp_file.go
│ ├── get_structs.go
│ ├── models.go
│ └── start.go
└── utils
│ ├── cachedir.go
│ └── filepath.go
├── render.go
└── rendertask.go
/.github/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ---
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Go-SSR is a drop in plugin to **any** existing Go web framework to allow **server rendering** [React](https://react.dev/). It's powered by [esbuild](https://esbuild.github.io/) and allows for passing props from Go to React with **type safety**.
15 |
16 |
18 |
19 | Go-SSR was developed due to a lack of an existing product in the Go ecosystem that made it easy to build full-stack React apps. At the time, most Go web app projects were either built with a static React frontend with lots of client-side logic or html templates. I envisioned creating a new solution that would allow you to create full-stack Go apps with React but with logic being moved to the server and being able to pass that logic down with type-safe props. This project was inspired by [Remix](https://remix.run/) and [Next.JS](https://nextjs.org/), but aims to be a plugin and not a framework.
20 |
21 | # 📜 Features
22 |
23 | - Lightning fast compiling
24 | - Auto generated Typescript structs for props
25 | - Hot reloading
26 | - Simple error reporting
27 | - Production optimized
28 | - Drop in to any existing Go web server
29 |
30 |
31 |
32 | # 🛠️ Getting Started
33 |
34 | Go-SSR was designed with the idea of being dead simple to install. Below are 2 easy ways of setting it up:
35 |
36 | ## ⚡️ Using the CLI tool
37 |
38 |
39 |
40 | The easiest way to get a project up and running is by using the command line tool. Install it with the following command
41 |
42 | ```console
43 | $ go install github.com/natewong1313/go-react-ssr/gossr-cli@latest
44 | ```
45 |
46 | Then you can call the following command to create a project
47 |
48 | ```console
49 | $ gossr-cli create
50 | ```
51 |
52 | You'll be prompted the path to place the project, what web framework you want to use, and whether or not you want to use Tailwind
53 |
54 | ## 📝 Add to existing web server
55 |
56 | To add Go-SSR to an existing Go web server, take a look at the [examples](/examples) folder to get an idea of what a project looks like. In general, you'll want to follow these commands:
57 |
58 | ```console
59 | $ go get -u github.com/natewong1313/go-react-ssr
60 | ```
61 |
62 | Then, add imports into your main file
63 |
64 | ```go
65 | import (
66 | ...
67 | gossr "github.com/natewong1313/go-react-ssr"
68 | )
69 | ```
70 |
71 | In your main function, initialize the plugin. Create a folder for your structs that hold your props to go, which is called `models` in the below example. You'll also want to create a folder for your React code (called `frontend` in this example) inside your project and specifiy the paths in the config. You may want to clone the [example folder](/examples/frontend/) and use that.
72 |
73 | ```go
74 | engine, err := gossr.New(gossr.Config{
75 | AssetRoute: "/assets",
76 | FrontendDir: "./frontend/src",
77 | GeneratedTypesPath: "./frontend/src/generated.d.ts",
78 | PropsStructsPath: "./models/props.go",
79 | })
80 | ```
81 |
82 | Once the plugin has been initialized, you can call the `engine.RenderRoute` function to compile your React file to a string
83 |
84 | ```go
85 | g.GET("/", func(c *gin.Context) {
86 | renderedResponse := engine.RenderRoute(gossr.RenderConfig{
87 | File: "Home.tsx",
88 | Title: "Example app",
89 | MetaTags: map[string]string{
90 | "og:title": "Example app",
91 | "description": "Hello world!",
92 | },
93 | Props: &models.IndexRouteProps{
94 | InitialCount: rand.Intn(100),
95 | },
96 | })
97 | c.Writer.Write(renderedResponse)
98 | })
99 | ```
100 |
101 | # 🚀 Deploying to production
102 |
103 | All of the examples come with a Dockerfile that you can use to deploy to production. You can also use the [gossr-cli](#-using-the-cli-tool) to create a project with a Dockerfile.
104 | Below is an example Dockerfile
105 |
106 | ```Dockerfile
107 | FROM golang:1.21-alpine as build-backend
108 | RUN apk add git
109 | ADD .. /build
110 | WORKDIR /build
111 |
112 | RUN go mod download
113 | RUN go get -u github.com/natewong1313/go-react-ssr
114 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w -X main.APP_ENV=production" -a -o main
115 |
116 |
117 | FROM node:16-alpine as build-frontend
118 |
119 | ADD ./frontend /frontend
120 | WORKDIR /frontend
121 |
122 | RUN npm install
123 |
124 | # if tailwind is enabled, use "FROM node:16-alpine" instead
125 | FROM alpine:latest
126 | COPY --from=build-backend /build/main ./app/main
127 | COPY --from=build-frontend /frontend ./app/frontend
128 |
129 | WORKDIR /app
130 | RUN chmod +x ./main
131 | EXPOSE 8080
132 | CMD ["./main"]
133 | ```
134 |
135 | Go SSR has been tested and deployed on the following platforms:
136 |
137 | - [Fly.io](https://fly.io/) - [example app](https://sparkling-smoke-7627.fly.dev/)
138 | - [Render](https://render.com/) - [example app](https://my-gossr-test.onrender.com/)
139 | - [Hop.io](https://hop.io/) - [example app](https://my-gossr-test.hop.sh/)
140 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Go project
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*" # triggers only if push new tag version, like `0.8.4` or else
7 | jobs:
8 | build:
9 | name: GoReleaser build
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Check out code into the Go module directory
14 | uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0 # See: https://goreleaser.com/ci/actions/
17 |
18 | - name: Set up Go 1.21
19 | uses: actions/setup-go@v2
20 | with:
21 | go-version: 1.21
22 | id: go
23 |
24 | # - name: Run GoReleaser
25 | # uses: goreleaser/goreleaser-action@master
26 | # with:
27 | # version: latest
28 | # args: release --rm-dist
29 | # env:
30 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
32 | - name: Update godoc.org
33 | run: |
34 | OPERATOR_VERSION=$(git describe --tags)
35 | curl https://proxy.golang.org/github.com/natewong1313/go-react-ssr/@v/${OPERATOR_VERSION}.info
36 | curl -X POST https://pkg.go.dev/fetch/github.com/natewong1313/go-react-ssr@${OPERATOR_VERSION}
37 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Run test suite
2 | on:
3 | push:
4 | branches:
5 | - "*"
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Setup Go
14 | uses: actions/setup-go@v4
15 | with:
16 | go-version: "1.21"
17 | - name: Install dependencies
18 | run: go get .
19 | - name: Build
20 | run: go build -v ./...
21 | - name: Test with the Go CLI
22 | run: go test -v ./...
23 | - name: Setup Node
24 | uses: actions/setup-node@v3
25 | - name: Install npm dependencies
26 | working-directory: ./examples/frontend
27 | run: npm install
28 | - name: Test Fiber example
29 | run: go test -v ./examples/fiber
30 | - name: Setup Node for tailwind example
31 | uses: actions/setup-node@v3
32 | - name: Install npm dependencies
33 | working-directory: ./examples/frontend-tailwind
34 | run: npm install
35 | - name: Test Gin example
36 | run: go test -v ./examples/gin
37 | - name: Setup Node for material ui example
38 | uses: actions/setup-node@v3
39 | - name: Install npm dependencies
40 | working-directory: ./examples/frontend-mui
41 | run: npm install
42 | - name: Test Echo example
43 | run: go test -v ./examples/echo
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .tmp
3 | tmp
4 | .DS_Store
5 |
6 | .idea
7 |
8 | generated.d.ts
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | builds:
2 | - skip: true
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Nate Wong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/config.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "fmt"
5 | "github.com/natewong1313/go-react-ssr/internal/utils"
6 | "os"
7 | "path"
8 | )
9 |
10 | // Config is the config for starting the engine
11 | type Config struct {
12 | AppEnv string // "production" or "development"
13 | AssetRoute string // The route to serve assets from, e.g. "/assets"
14 | FrontendDir string // The path to the frontend folder, where your React app lives
15 | GeneratedTypesPath string // The path to the generated types file
16 | PropsStructsPath string // The path to the Go structs file, the structs will be generated to TS types
17 | LayoutFilePath string // The path to the layout file, relative to the frontend dir
18 | LayoutCSSFilePath string // The path to the layout css file, relative to the frontend dir
19 | TailwindConfigPath string // The path to the tailwind config file
20 | HotReloadServerPort int // The port to run the hot reload server on, 3001 by default
21 | }
22 |
23 | // Validate validates the config
24 | func (c *Config) Validate() error {
25 | if !checkPathExists(c.FrontendDir) {
26 | return fmt.Errorf("frontend dir ar %s does not exist", c.FrontendDir)
27 | }
28 | if os.Getenv("APP_ENV") != "production" && !checkPathExists(c.PropsStructsPath) {
29 | return fmt.Errorf("props structs path at %s does not exist", c.PropsStructsPath)
30 | }
31 | if c.LayoutFilePath != "" && !checkPathExists(path.Join(c.FrontendDir, c.LayoutFilePath)) {
32 | return fmt.Errorf("layout css file path at %s/%s does not exist", c.FrontendDir, c.LayoutCSSFilePath)
33 | }
34 | if c.LayoutCSSFilePath != "" && !checkPathExists(path.Join(c.FrontendDir, c.LayoutCSSFilePath)) {
35 | return fmt.Errorf("layout css file path at %s/%s does not exist", c.FrontendDir, c.LayoutCSSFilePath)
36 | }
37 | if c.TailwindConfigPath != "" && c.LayoutCSSFilePath == "" {
38 | return fmt.Errorf("layout css file path must be provided when using tailwind")
39 | }
40 | if c.HotReloadServerPort == 0 {
41 | c.HotReloadServerPort = 3001
42 | }
43 | c.setFilePaths()
44 | return nil
45 | }
46 |
47 | // setFilePaths sets any paths in the config to their absolute paths
48 | func (c *Config) setFilePaths() {
49 | c.FrontendDir = utils.GetFullFilePath(c.FrontendDir)
50 | c.GeneratedTypesPath = utils.GetFullFilePath(c.GeneratedTypesPath)
51 | c.PropsStructsPath = utils.GetFullFilePath(c.PropsStructsPath)
52 | if c.LayoutFilePath != "" {
53 | c.LayoutFilePath = path.Join(c.FrontendDir, c.LayoutFilePath)
54 | }
55 | if c.LayoutCSSFilePath != "" {
56 | c.LayoutCSSFilePath = path.Join(c.FrontendDir, c.LayoutCSSFilePath)
57 | }
58 | if c.TailwindConfigPath != "" {
59 | c.TailwindConfigPath = utils.GetFullFilePath(c.TailwindConfigPath)
60 | }
61 | }
62 |
63 | func checkPathExists(path string) bool {
64 | _, err := os.Stat(utils.GetFullFilePath(path))
65 | return !os.IsNotExist(err)
66 | }
67 |
--------------------------------------------------------------------------------
/css.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net/http"
8 | "os"
9 | "os/exec"
10 | "path/filepath"
11 | "runtime"
12 |
13 | "github.com/buger/jsonparser"
14 | "github.com/natewong1313/go-react-ssr/internal/utils"
15 | )
16 |
17 | // BuildLayoutCSSFile builds the layout css file if it exists
18 | func (engine *Engine) BuildLayoutCSSFile() error {
19 | if engine.CachedLayoutCSSFilePath == "" && engine.Config.LayoutCSSFilePath != "" {
20 | layoutCSSCacheDir, err := utils.GetCSSCacheDir()
21 | if err != nil {
22 | return err
23 | }
24 | cachedCSSFilePath, err := createCachedCSSFile(layoutCSSCacheDir, engine.Config.LayoutCSSFilePath)
25 | if err != nil {
26 | return err
27 | }
28 | engine.CachedLayoutCSSFilePath = cachedCSSFilePath
29 | }
30 | if engine.Config.TailwindConfigPath != "" {
31 | engine.Logger.Debug().Msg("Building css file with tailwind")
32 | return engine.buildCSSWithTailwind()
33 | }
34 | return nil
35 | }
36 |
37 | // createCachedCSSFile creates a cached css file from the layout css file
38 | func createCachedCSSFile(layoutCSSCacheDir, layoutCSSFilePath string) (string, error) {
39 | cachedCSSFilePath := utils.GetFullFilePath(filepath.Join(layoutCSSCacheDir, "gossr.css"))
40 | file, err := os.Create(cachedCSSFilePath)
41 | if err != nil {
42 | return "", err
43 | }
44 | defer file.Close()
45 | globalCSSFile, err := os.Open(layoutCSSFilePath)
46 | if err != nil {
47 | return "", err
48 | }
49 | defer globalCSSFile.Close()
50 | _, err = io.Copy(file, globalCSSFile)
51 | return cachedCSSFilePath, err
52 | }
53 |
54 | // buildCSSWithTailwind builds the css file with tailwind cli
55 | func (engine *Engine) buildCSSWithTailwind() error {
56 | cmd := exec.Command("npx", "tailwindcss", "-i", engine.Config.LayoutCSSFilePath, "-o", engine.CachedLayoutCSSFilePath)
57 | // if in production, use the standalone tailwind executable instead of node
58 | if os.Getenv("APP_ENV") == "production" {
59 | executableName, err := detectTailwindDownloadName()
60 | if err != nil {
61 | return err
62 | }
63 | executableDir, err := utils.GetTailwindExecutableDir()
64 | if err != nil {
65 | return err
66 | }
67 | executablePath := filepath.Join(executableDir, executableName)
68 | // check if the executable has already been installed
69 | if _, err := os.Stat(executablePath); os.IsNotExist(err) {
70 | engine.Logger.Debug().Msgf("Downloading tailwind executable to %s", executablePath)
71 | if err = engine.downloadTailwindExecutable(executableName, executableDir); err != nil {
72 | return err
73 | }
74 | }
75 | cmd = exec.Command(executablePath, "-i", engine.Config.LayoutCSSFilePath, "-o", engine.CachedLayoutCSSFilePath)
76 | }
77 | // Set the working directory to the directory of the tailwind config file
78 | cmd.Dir = filepath.Dir(engine.Config.TailwindConfigPath)
79 | _, err := cmd.CombinedOutput()
80 | return err
81 | }
82 |
83 | // downloadTailwindExecutable downloads the tailwind executable from github releases (https://tailwindcss.com/blog/standalone-cli)
84 | func (engine *Engine) downloadTailwindExecutable(executableName string, executableDir string) error {
85 | version, err := getLatestTailwindVersion()
86 | if err != nil {
87 | return err
88 | }
89 |
90 | file, err := os.Create(filepath.Join(executableDir, executableName))
91 | if err != nil {
92 | return err
93 | }
94 | defer file.Close()
95 |
96 | resp, err := http.Get(fmt.Sprintf("https://github.com/tailwindlabs/tailwindcss/releases/download/%s/%s", version, executableName))
97 | if err != nil {
98 | return err
99 | }
100 | defer resp.Body.Close()
101 |
102 | _, err = io.Copy(file, resp.Body)
103 | if err != nil {
104 | return err
105 | }
106 | return nil
107 | }
108 |
109 | // getLatestTailwindVersion gets the latest tailwind release version from the github api
110 | func getLatestTailwindVersion() (string, error) {
111 | resp, err := http.Get("https://api.github.com/repos/tailwindlabs/tailwindcss/releases/latest")
112 | if err != nil {
113 | return "", err
114 | }
115 | defer resp.Body.Close()
116 |
117 | respBody, err := io.ReadAll(resp.Body)
118 | if err != nil {
119 | return "", err
120 | }
121 | version, err := jsonparser.GetString(respBody, "name")
122 | return version, err
123 | }
124 |
125 | // detectTailwindDownloadName detects the tailwind executable download name based on the OS and architecture
126 | func detectTailwindDownloadName() (string, error) {
127 | os := runtime.GOOS
128 | arch := runtime.GOARCH
129 | switch os {
130 | case "darwin":
131 | switch arch {
132 | case "arm64":
133 | return "tailwindcss-macos-arm64", nil
134 | case "amd64":
135 | return "tailwindcss-macos-x64", nil
136 | }
137 | case "linux":
138 | switch arch {
139 | case "arm64":
140 | return "tailwindcss-linux-arm64", nil
141 | case "arm":
142 | return "tailwindcss-linux-armv7", nil
143 | case "amd64":
144 | return "tailwindcss-linux-x64", nil
145 | }
146 | case "windows":
147 | switch arch {
148 | case "arm64":
149 | return "tailwindcss-windows-arm64.exe", nil
150 | case "amd64":
151 | return "tailwindcss-windows-x64.exe", nil
152 | }
153 | }
154 | return "", errors.New("unsupported OS/Architecture")
155 | }
156 |
--------------------------------------------------------------------------------
/css_test.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/rs/zerolog"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestEngine_BuildLayoutCSSFile(t *testing.T) {
12 | type test struct {
13 | name string
14 | shouldContain string
15 | config *Config
16 | }
17 | originalContents, err := os.ReadFile("./examples/frontend/src/Home.css")
18 | assert.Nil(t, err, "ReadFile should not return an error")
19 | tests := []test{
20 | {
21 | name: "should clone layout css file",
22 | shouldContain: string(originalContents),
23 | config: &Config{
24 | AppEnv: "production",
25 | FrontendDir: "./examples/frontend/src",
26 | LayoutCSSFilePath: "Home.css",
27 | },
28 | },
29 | {
30 | name: "should build layout css file with tailwind",
31 | shouldContain: "tailwindcss",
32 | config: &Config{
33 | AppEnv: "production",
34 | FrontendDir: "./examples/frontend-tailwind/src",
35 | LayoutCSSFilePath: "Main.css",
36 | TailwindConfigPath: "./examples/frontend-tailwind/tailwind.config.js",
37 | },
38 | },
39 | }
40 | for _, tt := range tests {
41 | t.Run(tt.name, func(t *testing.T) {
42 | tt.config.setFilePaths()
43 | engine := &Engine{
44 | Logger: zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger(),
45 | Config: tt.config,
46 | }
47 | err = engine.BuildLayoutCSSFile()
48 | assert.Nil(t, err, "BuildLayoutCSSFile should not return an error, got %v", err)
49 | assert.NotNilf(t, engine.CachedLayoutCSSFilePath, "CachedLayoutCSSFilePath should not be nil")
50 | contents, err := os.ReadFile(engine.CachedLayoutCSSFilePath)
51 | assert.Nil(t, err, "ReadFile should not return an error, got %v", err)
52 | assert.Contains(t, string(contents), tt.shouldContain)
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/engine.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "github.com/natewong1313/go-react-ssr/internal/cache"
5 | "github.com/natewong1313/go-react-ssr/internal/typeconverter"
6 | "github.com/natewong1313/go-react-ssr/internal/utils"
7 | "github.com/rs/zerolog"
8 | "os"
9 | )
10 |
11 | type Engine struct {
12 | Logger zerolog.Logger
13 | Config *Config
14 | HotReload *HotReload
15 | CacheManager *cache.Manager
16 | CachedLayoutCSSFilePath string
17 | }
18 |
19 | // New creates a new gossr Engine instance
20 | func New(config Config) (*Engine, error) {
21 | engine := &Engine{
22 | Logger: zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger(),
23 | Config: &config,
24 | CacheManager: cache.NewManager(),
25 | }
26 | if err := os.Setenv("APP_ENV", config.AppEnv); err != nil {
27 | engine.Logger.Err(err).Msg("Failed to set APP_ENV environment variable")
28 | }
29 | err := config.Validate()
30 | if err != nil {
31 | engine.Logger.Err(err).Msg("Failed to validate config")
32 | return nil, err
33 | }
34 | utils.CleanCacheDirectories()
35 | // If using a layout css file, build it and cache it
36 | if config.LayoutCSSFilePath != "" {
37 | if err = engine.BuildLayoutCSSFile(); err != nil {
38 | engine.Logger.Err(err).Msg("Failed to build layout css file")
39 | return nil, err
40 | }
41 | }
42 |
43 | // If running in production mode, return and don't start hot reload or type converter
44 | if os.Getenv("APP_ENV") == "production" {
45 | engine.Logger.Info().Msg("Running go-ssr in production mode")
46 | return engine, nil
47 | }
48 | engine.Logger.Info().Msg("Running go-ssr in development mode")
49 | engine.Logger.Debug().Msg("Starting type converter")
50 | // Start the type converter to convert Go types to Typescript types
51 | if err := typeconverter.Start(engine.Config.PropsStructsPath, engine.Config.GeneratedTypesPath); err != nil {
52 | engine.Logger.Err(err).Msg("Failed to init type converter")
53 | return nil, err
54 | }
55 |
56 | engine.Logger.Debug().Msg("Starting hot reload server")
57 | engine.HotReload = newHotReload(engine)
58 | engine.HotReload.Start()
59 | return engine, nil
60 | }
61 |
--------------------------------------------------------------------------------
/engine_test.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "fmt"
5 | "github.com/stretchr/testify/assert"
6 | "net"
7 | "os"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestNew_Dev(t *testing.T) {
13 | config := Config{
14 | AppEnv: "development",
15 | FrontendDir: "./examples/frontend-tailwind/src",
16 | GeneratedTypesPath: "./examples/frontend-tailwind/src/generated.d.ts",
17 | PropsStructsPath: "./examples/gin/models/props.go",
18 | HotReloadServerPort: 4001,
19 | }
20 |
21 | originalContents, err := os.ReadFile(config.GeneratedTypesPath)
22 | assert.Nil(t, err, "ReadFile should not return an error")
23 | err = os.Truncate(config.GeneratedTypesPath, 0)
24 | assert.Nil(t, err, "os.Truncate should not return an error, got %v", err)
25 | _, err = New(config)
26 | assert.Nil(t, err, "gossr.New should not return an error, got %v", err)
27 |
28 | contents, err := os.ReadFile(config.GeneratedTypesPath)
29 | assert.Nil(t, err, "ReadFile should not return an error")
30 | assert.Contains(t, string(contents), "Do not change, this code is generated from Golang structs", "Types file should have generated code in it")
31 |
32 | var conn net.Conn
33 | for i := 1; i <= 3; i++ {
34 | conn, _ = net.DialTimeout("tcp", net.JoinHostPort("", fmt.Sprintf("%d", config.HotReloadServerPort)), time.Second)
35 | if conn != nil {
36 | conn.Close()
37 | break
38 | }
39 | }
40 | assert.NotNil(t, conn, "Hot reload server should be running on port %d", config.HotReloadServerPort)
41 |
42 | err = os.WriteFile(config.GeneratedTypesPath, originalContents, 0644)
43 | }
44 |
45 | func TestNew_Prod(t *testing.T) {
46 | config := Config{
47 | AppEnv: "production",
48 | FrontendDir: "./examples/frontend/src",
49 | GeneratedTypesPath: "./examples/frontend/src/generated.d.ts",
50 | PropsStructsPath: "./examples/gin/models/props.go",
51 | }
52 |
53 | originalContents, err := os.ReadFile(config.GeneratedTypesPath)
54 | assert.Nil(t, err, "ReadFile should not return an error")
55 | err = os.Truncate(config.GeneratedTypesPath, 0)
56 | assert.Nil(t, err, "os.Truncate should not return an error, got %v", err)
57 | _, err = New(config)
58 | assert.Nil(t, err, "gossr.New should not return an error, got %v", err)
59 |
60 | contents, err := os.ReadFile(config.GeneratedTypesPath)
61 | assert.Nil(t, err, "ReadFile should not return an error")
62 | assert.Equal(t, string(contents), "", "Generated types file should be empty")
63 |
64 | err = os.WriteFile(config.GeneratedTypesPath, originalContents, 0644)
65 | }
66 |
--------------------------------------------------------------------------------
/examples/echo/.dockerignore:
--------------------------------------------------------------------------------
1 | */node_modules
2 | .dockerignore
3 | Dockerfile
4 | tmp
--------------------------------------------------------------------------------
/examples/echo/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16-alpine as build-frontend
2 | ADD ./frontend-mui ./build
3 | WORKDIR /build
4 | RUN npm install
5 |
6 | FROM golang:1.21-alpine
7 | RUN apk add git
8 | ADD . /app
9 | COPY --from=build-frontend /build ./app/frontend-mui
10 | WORKDIR /app
11 |
12 | RUN go mod download
13 | RUN go get -u github.com/natewong1313/go-react-ssr
14 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w -X main.APP_ENV=production" -a -o main
15 |
16 | RUN chmod +x ./main
17 | EXPOSE 8080
18 | CMD ["./main"]
19 |
20 |
--------------------------------------------------------------------------------
/examples/echo/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "1"
2 | services:
3 | app:
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 | ports:
8 | - "8080:8080"
9 | environment:
10 | - APP_ENV=production
11 |
--------------------------------------------------------------------------------
/examples/echo/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/echo
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/labstack/echo/v4 v4.11.1
7 | github.com/natewong1313/go-react-ssr v0.1.0
8 | )
9 |
10 | require (
11 | github.com/buger/jsonparser v1.1.1 // indirect
12 | github.com/creasty/defaults v1.7.0 // indirect
13 | github.com/dlclark/regexp2 v1.7.0 // indirect
14 | github.com/dop251/goja v0.0.0-20230919151941-fc55792775de // indirect
15 | github.com/evanw/esbuild v0.19.3 // indirect
16 | github.com/fsnotify/fsnotify v1.6.0 // indirect
17 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
18 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
19 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
20 | github.com/gorilla/websocket v1.5.0 // indirect
21 | github.com/labstack/gommon v0.4.0 // indirect
22 | github.com/mattn/go-colorable v0.1.13 // indirect
23 | github.com/mattn/go-isatty v0.0.19 // indirect
24 | github.com/rs/zerolog v1.30.0 // indirect
25 | github.com/tkrajina/go-reflector v0.5.5 // indirect
26 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 // indirect
27 | github.com/valyala/bytebufferpool v1.0.0 // indirect
28 | github.com/valyala/fasttemplate v1.2.2 // indirect
29 | golang.org/x/crypto v0.11.0 // indirect
30 | golang.org/x/net v0.12.0 // indirect
31 | golang.org/x/sys v0.12.0 // indirect
32 | golang.org/x/text v0.11.0 // indirect
33 | golang.org/x/time v0.3.0 // indirect
34 | )
35 |
--------------------------------------------------------------------------------
/examples/echo/go.sum:
--------------------------------------------------------------------------------
1 | github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
2 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
3 | github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
4 | github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
5 | github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
6 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
8 | github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
9 | github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13 | github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
14 | github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
15 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
16 | github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
17 | github.com/dop251/goja v0.0.0-20230919151941-fc55792775de h1:lA38Xtzr1Wo+iQdkN2E11ziKXJYRxLlzK/e2/fdxoEI=
18 | github.com/dop251/goja v0.0.0-20230919151941-fc55792775de/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
19 | github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
20 | github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
21 | github.com/evanw/esbuild v0.19.3 h1:foPr0xwQM3lBWKBtscauTN9FrmJzRDVI2+EGOs82H/I=
22 | github.com/evanw/esbuild v0.19.3/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
23 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
24 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
25 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
26 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
27 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
28 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
29 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
30 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
31 | github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
32 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
33 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
34 | github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
35 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
36 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
37 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
38 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
39 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
40 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
41 | github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
42 | github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
43 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
44 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
45 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
46 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
47 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
48 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
49 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
50 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
51 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
52 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
53 | github.com/natewong1313/go-react-ssr v0.1.0 h1:v/8q4dHGkyWXumrSIYZlZf/03DdG0RSMtQ1VcF1Ba5k=
54 | github.com/natewong1313/go-react-ssr v0.1.0/go.mod h1:48ue0an62Sqbk9Xgl2HiD9AA/lNJ7Wbz9mFhZHs9JYA=
55 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
56 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
57 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
58 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
59 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
60 | github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
61 | github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
62 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
63 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
64 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
65 | github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ=
66 | github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
67 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 h1:W/Ta9Kqo2lV+7bVXuQoUhZ0bDlnjwtPpKsy3A9M1nYg=
68 | github.com/tkrajina/typescriptify-golang-structs v0.1.10/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
69 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
70 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
71 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
72 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
73 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
74 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
75 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
76 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
77 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
78 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
79 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
80 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
81 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
82 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
83 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
84 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
85 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
86 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
87 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
88 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
89 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
90 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
91 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
92 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
93 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
94 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
95 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
96 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
98 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
99 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
100 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
101 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
102 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
103 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
104 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
105 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
106 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
107 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
108 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
109 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
110 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
111 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
112 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
113 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
114 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
115 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
116 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
117 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
118 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
119 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
120 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
121 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
122 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
123 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
124 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
125 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
126 |
--------------------------------------------------------------------------------
/examples/echo/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "example.com/echo/models"
5 | "github.com/labstack/echo/v4"
6 | "github.com/labstack/echo/v4/middleware"
7 | gossr "github.com/natewong1313/go-react-ssr"
8 | "log"
9 | "math/rand"
10 | "net/http"
11 | )
12 |
13 | var APP_ENV string
14 |
15 | func main() {
16 | e := echo.New()
17 | e.Use(middleware.Recover())
18 | e.Static("/assets", "../frontend-mui/public/")
19 |
20 | engine, err := gossr.New(gossr.Config{
21 | AppEnv: APP_ENV,
22 | AssetRoute: "/assets",
23 | FrontendDir: "../frontend-mui/src",
24 | GeneratedTypesPath: "../frontend-mui/src/generated.d.ts",
25 | PropsStructsPath: "./models/props.go",
26 | LayoutFilePath: "Layout.tsx",
27 | })
28 | if err != nil {
29 | log.Fatal("Failed to init go-react-ssr")
30 | }
31 |
32 | e.GET("/", func(c echo.Context) error {
33 | response := engine.RenderRoute(gossr.RenderConfig{
34 | File: "Home.tsx",
35 | Title: "Echo example app",
36 | MetaTags: map[string]string{
37 | "og:title": "Echo example app",
38 | "description": "Hello world!",
39 | },
40 | Props: &models.IndexRouteProps{
41 | InitialCount: rand.Intn(100),
42 | },
43 | })
44 | return c.HTML(http.StatusOK, string(response))
45 | })
46 |
47 | e.Start(":8080")
48 | }
49 |
--------------------------------------------------------------------------------
/examples/echo/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net"
6 | "net/http"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func Test_main(t *testing.T) {
14 | go main()
15 | // Wait for server to start
16 | for {
17 | conn, _ := net.DialTimeout("tcp", net.JoinHostPort("", "8080"), time.Millisecond*1000)
18 | if conn != nil {
19 | conn.Close()
20 | break
21 | }
22 | }
23 | res, err := http.Get("http://localhost:8080")
24 | assert.Nil(t, err)
25 | assert.Equal(t, 200, res.StatusCode)
26 |
27 | resBody, err := io.ReadAll(res.Body)
28 | assert.Nil(t, err)
29 | assert.NotNil(t, resBody)
30 | assert.NotContains(t, string(resBody), "An error occured!")
31 | }
32 |
--------------------------------------------------------------------------------
/examples/echo/models/props.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type IndexRouteProps struct {
4 | InitialCount int `json:"initialCount"`
5 | }
6 |
--------------------------------------------------------------------------------
/examples/fiber/.dockerignore:
--------------------------------------------------------------------------------
1 | */node_modules
2 | .dockerignore
3 | Dockerfile
4 | tmp
--------------------------------------------------------------------------------
/examples/fiber/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16-alpine as build-frontend
2 | ADD ./frontend ./build
3 | WORKDIR /build
4 | RUN npm install
5 |
6 | FROM golang:1.21-alpine
7 | RUN apk add git
8 | ADD . /app
9 | COPY --from=build-frontend /build ./app/frontend
10 | WORKDIR /app
11 |
12 | RUN go mod download
13 | RUN go get -u github.com/natewong1313/go-react-ssr
14 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w -X main.APP_ENV=production" -a -o main
15 |
16 | RUN chmod +x ./main
17 | EXPOSE 8080
18 | CMD ["./main"]
19 |
20 |
--------------------------------------------------------------------------------
/examples/fiber/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "1"
2 | services:
3 | app:
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 | ports:
8 | - "8080:8080"
9 | environment:
10 | - APP_ENV=production
11 |
--------------------------------------------------------------------------------
/examples/fiber/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/fiber
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gofiber/fiber/v2 v2.49.2
7 | github.com/natewong1313/go-react-ssr v0.0.693
8 | github.com/stretchr/testify v1.8.3
9 | )
10 |
11 | require (
12 | github.com/andybalholm/brotli v1.0.5 // indirect
13 | github.com/buger/jsonparser v1.1.1 // indirect
14 | github.com/creasty/defaults v1.7.0 // indirect
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/evanw/esbuild v0.19.3 // indirect
17 | github.com/fsnotify/fsnotify v1.6.0 // indirect
18 | github.com/google/uuid v1.3.1 // indirect
19 | github.com/gorilla/websocket v1.5.0 // indirect
20 | github.com/joho/godotenv v1.5.1 // indirect
21 | github.com/klauspost/compress v1.17.0 // indirect
22 | github.com/mattn/go-colorable v0.1.13 // indirect
23 | github.com/mattn/go-isatty v0.0.19 // indirect
24 | github.com/mattn/go-runewidth v0.0.15 // indirect
25 | github.com/pmezard/go-difflib v1.0.0 // indirect
26 | github.com/rivo/uniseg v0.4.4 // indirect
27 | github.com/rs/zerolog v1.31.0 // indirect
28 | github.com/tkrajina/go-reflector v0.5.6 // indirect
29 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 // indirect
30 | github.com/valyala/bytebufferpool v1.0.0 // indirect
31 | github.com/valyala/fasthttp v1.50.0 // indirect
32 | github.com/valyala/tcplisten v1.0.0 // indirect
33 | golang.org/x/sys v0.12.0 // indirect
34 | gopkg.in/yaml.v3 v3.0.1 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/examples/fiber/go.sum:
--------------------------------------------------------------------------------
1 | github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
2 | github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
3 | github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
4 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
5 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
6 | github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
7 | github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/evanw/esbuild v0.19.3 h1:foPr0xwQM3lBWKBtscauTN9FrmJzRDVI2+EGOs82H/I=
12 | github.com/evanw/esbuild v0.19.3/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
13 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
14 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
15 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
16 | github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs=
17 | github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts=
18 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
19 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
20 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
21 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
22 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
23 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
24 | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
25 | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
26 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
27 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
28 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
29 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
30 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
31 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
32 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
33 | github.com/natewong1313/go-react-ssr v0.0.693 h1:eDTrK+wlDnodv48HVFKH/r+NixdLAe+zaYwHp+SGSw4=
34 | github.com/natewong1313/go-react-ssr v0.0.693/go.mod h1:x1jsB3lxKxlprkOx51VKCiF+JE01oyvahqIrjo3kQRc=
35 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
36 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
37 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
38 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
39 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
40 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
41 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
42 | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
43 | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
44 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
45 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
46 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
47 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
48 | github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
49 | github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
50 | github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
51 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 h1:W/Ta9Kqo2lV+7bVXuQoUhZ0bDlnjwtPpKsy3A9M1nYg=
52 | github.com/tkrajina/typescriptify-golang-structs v0.1.10/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
53 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
54 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
55 | github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
56 | github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
57 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
58 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
59 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
60 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
61 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
62 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
63 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
64 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
65 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
66 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
67 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
68 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
69 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
70 |
--------------------------------------------------------------------------------
/examples/fiber/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "math/rand"
6 |
7 | "example.com/fiber/models"
8 | "github.com/gofiber/fiber/v2"
9 | "github.com/gofiber/fiber/v2/middleware/favicon"
10 | "github.com/gofiber/fiber/v2/middleware/logger"
11 | gossr "github.com/natewong1313/go-react-ssr"
12 | )
13 |
14 | var APP_ENV string
15 |
16 | func main() {
17 | app := fiber.New()
18 | app.Use(logger.New())
19 | app.Use(favicon.New(favicon.Config{
20 | File: "../frontend/public/favicon.ico",
21 | URL: "/favicon.ico",
22 | }))
23 | app.Static("/assets", "../frontend/public/")
24 |
25 | engine, err := gossr.New(gossr.Config{
26 | AppEnv: APP_ENV,
27 | AssetRoute: "/assets",
28 | FrontendDir: "../frontend/src",
29 | GeneratedTypesPath: "../frontend/src/generated.d.ts",
30 | PropsStructsPath: "./models/props.go",
31 | })
32 | if err != nil {
33 | log.Fatal("Failed to init go-react-ssr")
34 | }
35 |
36 | app.Get("/", func(c *fiber.Ctx) error {
37 | response := engine.RenderRoute(gossr.RenderConfig{
38 | File: "Home.tsx",
39 | Title: "Fiber example app",
40 | MetaTags: map[string]string{
41 | "og:title": "Fiber example app",
42 | "description": "Hello world!",
43 | },
44 | Props: &models.IndexRouteProps{
45 | InitialCount: rand.Intn(100),
46 | },
47 | })
48 | c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
49 | return c.SendString(string(response))
50 | })
51 |
52 | app.Listen(":8080")
53 | }
54 |
--------------------------------------------------------------------------------
/examples/fiber/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net"
6 | "net/http"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func Test_main(t *testing.T) {
14 | go main()
15 | // Wait for server to start
16 | for {
17 | conn, _ := net.DialTimeout("tcp", net.JoinHostPort("", "8080"), time.Millisecond*1000)
18 | if conn != nil {
19 | conn.Close()
20 | break
21 | }
22 | }
23 | res, err := http.Get("http://localhost:8080")
24 | assert.Nil(t, err)
25 | assert.Equal(t, 200, res.StatusCode)
26 |
27 | resBody, err := io.ReadAll(res.Body)
28 | assert.Nil(t, err)
29 | assert.NotNil(t, resBody)
30 | assert.NotContains(t, string(resBody), "An error occured!")
31 | }
32 |
--------------------------------------------------------------------------------
/examples/fiber/models/props.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type IndexRouteProps struct {
4 | InitialCount int `json:"initialCount"`
5 | }
6 |
--------------------------------------------------------------------------------
/examples/frontend-mui/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-mui",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "dependencies": {
8 | "@emotion/react": "^11.11.1",
9 | "@emotion/styled": "^11.11.0",
10 | "@mui/material": "^5.14.14",
11 | "react": "^18.2.0",
12 | "react-dom": "^18.2.0"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^18.2.21",
16 | "@types/react-dom": "^18.2.7",
17 | "typescript": "^5.2.2"
18 | }
19 | },
20 | "node_modules/@babel/code-frame": {
21 | "version": "7.22.13",
22 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
23 | "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
24 | "dependencies": {
25 | "@babel/highlight": "^7.22.13",
26 | "chalk": "^2.4.2"
27 | },
28 | "engines": {
29 | "node": ">=6.9.0"
30 | }
31 | },
32 | "node_modules/@babel/helper-module-imports": {
33 | "version": "7.22.15",
34 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
35 | "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
36 | "dependencies": {
37 | "@babel/types": "^7.22.15"
38 | },
39 | "engines": {
40 | "node": ">=6.9.0"
41 | }
42 | },
43 | "node_modules/@babel/helper-string-parser": {
44 | "version": "7.22.5",
45 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
46 | "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
47 | "engines": {
48 | "node": ">=6.9.0"
49 | }
50 | },
51 | "node_modules/@babel/helper-validator-identifier": {
52 | "version": "7.22.20",
53 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
54 | "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
55 | "engines": {
56 | "node": ">=6.9.0"
57 | }
58 | },
59 | "node_modules/@babel/highlight": {
60 | "version": "7.22.20",
61 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
62 | "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
63 | "dependencies": {
64 | "@babel/helper-validator-identifier": "^7.22.20",
65 | "chalk": "^2.4.2",
66 | "js-tokens": "^4.0.0"
67 | },
68 | "engines": {
69 | "node": ">=6.9.0"
70 | }
71 | },
72 | "node_modules/@babel/runtime": {
73 | "version": "7.23.2",
74 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
75 | "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
76 | "dependencies": {
77 | "regenerator-runtime": "^0.14.0"
78 | },
79 | "engines": {
80 | "node": ">=6.9.0"
81 | }
82 | },
83 | "node_modules/@babel/types": {
84 | "version": "7.23.0",
85 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
86 | "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
87 | "dependencies": {
88 | "@babel/helper-string-parser": "^7.22.5",
89 | "@babel/helper-validator-identifier": "^7.22.20",
90 | "to-fast-properties": "^2.0.0"
91 | },
92 | "engines": {
93 | "node": ">=6.9.0"
94 | }
95 | },
96 | "node_modules/@emotion/babel-plugin": {
97 | "version": "11.11.0",
98 | "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
99 | "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
100 | "dependencies": {
101 | "@babel/helper-module-imports": "^7.16.7",
102 | "@babel/runtime": "^7.18.3",
103 | "@emotion/hash": "^0.9.1",
104 | "@emotion/memoize": "^0.8.1",
105 | "@emotion/serialize": "^1.1.2",
106 | "babel-plugin-macros": "^3.1.0",
107 | "convert-source-map": "^1.5.0",
108 | "escape-string-regexp": "^4.0.0",
109 | "find-root": "^1.1.0",
110 | "source-map": "^0.5.7",
111 | "stylis": "4.2.0"
112 | }
113 | },
114 | "node_modules/@emotion/cache": {
115 | "version": "11.11.0",
116 | "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
117 | "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
118 | "dependencies": {
119 | "@emotion/memoize": "^0.8.1",
120 | "@emotion/sheet": "^1.2.2",
121 | "@emotion/utils": "^1.2.1",
122 | "@emotion/weak-memoize": "^0.3.1",
123 | "stylis": "4.2.0"
124 | }
125 | },
126 | "node_modules/@emotion/hash": {
127 | "version": "0.9.1",
128 | "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
129 | "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
130 | },
131 | "node_modules/@emotion/is-prop-valid": {
132 | "version": "1.2.1",
133 | "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
134 | "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
135 | "dependencies": {
136 | "@emotion/memoize": "^0.8.1"
137 | }
138 | },
139 | "node_modules/@emotion/memoize": {
140 | "version": "0.8.1",
141 | "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
142 | "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
143 | },
144 | "node_modules/@emotion/react": {
145 | "version": "11.11.1",
146 | "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz",
147 | "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==",
148 | "dependencies": {
149 | "@babel/runtime": "^7.18.3",
150 | "@emotion/babel-plugin": "^11.11.0",
151 | "@emotion/cache": "^11.11.0",
152 | "@emotion/serialize": "^1.1.2",
153 | "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
154 | "@emotion/utils": "^1.2.1",
155 | "@emotion/weak-memoize": "^0.3.1",
156 | "hoist-non-react-statics": "^3.3.1"
157 | },
158 | "peerDependencies": {
159 | "react": ">=16.8.0"
160 | },
161 | "peerDependenciesMeta": {
162 | "@types/react": {
163 | "optional": true
164 | }
165 | }
166 | },
167 | "node_modules/@emotion/serialize": {
168 | "version": "1.1.2",
169 | "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz",
170 | "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==",
171 | "dependencies": {
172 | "@emotion/hash": "^0.9.1",
173 | "@emotion/memoize": "^0.8.1",
174 | "@emotion/unitless": "^0.8.1",
175 | "@emotion/utils": "^1.2.1",
176 | "csstype": "^3.0.2"
177 | }
178 | },
179 | "node_modules/@emotion/sheet": {
180 | "version": "1.2.2",
181 | "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
182 | "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
183 | },
184 | "node_modules/@emotion/styled": {
185 | "version": "11.11.0",
186 | "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz",
187 | "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==",
188 | "dependencies": {
189 | "@babel/runtime": "^7.18.3",
190 | "@emotion/babel-plugin": "^11.11.0",
191 | "@emotion/is-prop-valid": "^1.2.1",
192 | "@emotion/serialize": "^1.1.2",
193 | "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
194 | "@emotion/utils": "^1.2.1"
195 | },
196 | "peerDependencies": {
197 | "@emotion/react": "^11.0.0-rc.0",
198 | "react": ">=16.8.0"
199 | },
200 | "peerDependenciesMeta": {
201 | "@types/react": {
202 | "optional": true
203 | }
204 | }
205 | },
206 | "node_modules/@emotion/unitless": {
207 | "version": "0.8.1",
208 | "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
209 | "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
210 | },
211 | "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
212 | "version": "1.0.1",
213 | "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
214 | "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
215 | "peerDependencies": {
216 | "react": ">=16.8.0"
217 | }
218 | },
219 | "node_modules/@emotion/utils": {
220 | "version": "1.2.1",
221 | "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
222 | "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
223 | },
224 | "node_modules/@emotion/weak-memoize": {
225 | "version": "0.3.1",
226 | "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
227 | "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
228 | },
229 | "node_modules/@floating-ui/core": {
230 | "version": "1.5.0",
231 | "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
232 | "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
233 | "dependencies": {
234 | "@floating-ui/utils": "^0.1.3"
235 | }
236 | },
237 | "node_modules/@floating-ui/dom": {
238 | "version": "1.5.3",
239 | "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
240 | "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
241 | "dependencies": {
242 | "@floating-ui/core": "^1.4.2",
243 | "@floating-ui/utils": "^0.1.3"
244 | }
245 | },
246 | "node_modules/@floating-ui/react-dom": {
247 | "version": "2.0.2",
248 | "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz",
249 | "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==",
250 | "dependencies": {
251 | "@floating-ui/dom": "^1.5.1"
252 | },
253 | "peerDependencies": {
254 | "react": ">=16.8.0",
255 | "react-dom": ">=16.8.0"
256 | }
257 | },
258 | "node_modules/@floating-ui/utils": {
259 | "version": "0.1.6",
260 | "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
261 | "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
262 | },
263 | "node_modules/@mui/base": {
264 | "version": "5.0.0-beta.20",
265 | "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.20.tgz",
266 | "integrity": "sha512-CS2pUuqxST7ch9VNDCklRYDbJ3rru20Tx7na92QvVVKfu3RL4z/QLuVIc8jYGsdCnauMaeUSlFNLAJNb0yXe6w==",
267 | "dependencies": {
268 | "@babel/runtime": "^7.23.1",
269 | "@floating-ui/react-dom": "^2.0.2",
270 | "@mui/types": "^7.2.6",
271 | "@mui/utils": "^5.14.13",
272 | "@popperjs/core": "^2.11.8",
273 | "clsx": "^2.0.0",
274 | "prop-types": "^15.8.1"
275 | },
276 | "engines": {
277 | "node": ">=12.0.0"
278 | },
279 | "funding": {
280 | "type": "opencollective",
281 | "url": "https://opencollective.com/mui"
282 | },
283 | "peerDependencies": {
284 | "@types/react": "^17.0.0 || ^18.0.0",
285 | "react": "^17.0.0 || ^18.0.0",
286 | "react-dom": "^17.0.0 || ^18.0.0"
287 | },
288 | "peerDependenciesMeta": {
289 | "@types/react": {
290 | "optional": true
291 | }
292 | }
293 | },
294 | "node_modules/@mui/core-downloads-tracker": {
295 | "version": "5.14.14",
296 | "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.14.tgz",
297 | "integrity": "sha512-Rw/xKiTOUgXD8hdKqj60aC6QcGprMipG7ne2giK6Mz7b4PlhL/xog9xLeclY3BxsRLkZQ05egFnIEY1CSibTbw==",
298 | "funding": {
299 | "type": "opencollective",
300 | "url": "https://opencollective.com/mui"
301 | }
302 | },
303 | "node_modules/@mui/material": {
304 | "version": "5.14.14",
305 | "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.14.tgz",
306 | "integrity": "sha512-cAmCwAHFQXxb44kWbVFkhKATN8tACgMsFwrXo8ro6WzYW73U/qsR5AcCiJIhCyYYg+gcftfkmNcpRaV3JjhHCg==",
307 | "dependencies": {
308 | "@babel/runtime": "^7.23.1",
309 | "@mui/base": "5.0.0-beta.20",
310 | "@mui/core-downloads-tracker": "^5.14.14",
311 | "@mui/system": "^5.14.14",
312 | "@mui/types": "^7.2.6",
313 | "@mui/utils": "^5.14.13",
314 | "@types/react-transition-group": "^4.4.7",
315 | "clsx": "^2.0.0",
316 | "csstype": "^3.1.2",
317 | "prop-types": "^15.8.1",
318 | "react-is": "^18.2.0",
319 | "react-transition-group": "^4.4.5"
320 | },
321 | "engines": {
322 | "node": ">=12.0.0"
323 | },
324 | "funding": {
325 | "type": "opencollective",
326 | "url": "https://opencollective.com/mui"
327 | },
328 | "peerDependencies": {
329 | "@emotion/react": "^11.5.0",
330 | "@emotion/styled": "^11.3.0",
331 | "@types/react": "^17.0.0 || ^18.0.0",
332 | "react": "^17.0.0 || ^18.0.0",
333 | "react-dom": "^17.0.0 || ^18.0.0"
334 | },
335 | "peerDependenciesMeta": {
336 | "@emotion/react": {
337 | "optional": true
338 | },
339 | "@emotion/styled": {
340 | "optional": true
341 | },
342 | "@types/react": {
343 | "optional": true
344 | }
345 | }
346 | },
347 | "node_modules/@mui/private-theming": {
348 | "version": "5.14.14",
349 | "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.14.tgz",
350 | "integrity": "sha512-n77au3CQj9uu16hak2Y+rvbGSBaJKxziG/gEbOLVGrAuqZ+ycVSkorCfN6Y/4XgYOpG/xvmuiY3JwhAEOzY3iA==",
351 | "dependencies": {
352 | "@babel/runtime": "^7.23.1",
353 | "@mui/utils": "^5.14.13",
354 | "prop-types": "^15.8.1"
355 | },
356 | "engines": {
357 | "node": ">=12.0.0"
358 | },
359 | "funding": {
360 | "type": "opencollective",
361 | "url": "https://opencollective.com/mui"
362 | },
363 | "peerDependencies": {
364 | "@types/react": "^17.0.0 || ^18.0.0",
365 | "react": "^17.0.0 || ^18.0.0"
366 | },
367 | "peerDependenciesMeta": {
368 | "@types/react": {
369 | "optional": true
370 | }
371 | }
372 | },
373 | "node_modules/@mui/styled-engine": {
374 | "version": "5.14.14",
375 | "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.14.tgz",
376 | "integrity": "sha512-sF3DS2PVG+cFWvkVHQQaGFpL1h6gSwOW3L91pdxPLQDHDZ5mZ/X0SlXU5XA+WjypoysG4urdAQC7CH/BRvUiqg==",
377 | "dependencies": {
378 | "@babel/runtime": "^7.23.1",
379 | "@emotion/cache": "^11.11.0",
380 | "csstype": "^3.1.2",
381 | "prop-types": "^15.8.1"
382 | },
383 | "engines": {
384 | "node": ">=12.0.0"
385 | },
386 | "funding": {
387 | "type": "opencollective",
388 | "url": "https://opencollective.com/mui"
389 | },
390 | "peerDependencies": {
391 | "@emotion/react": "^11.4.1",
392 | "@emotion/styled": "^11.3.0",
393 | "react": "^17.0.0 || ^18.0.0"
394 | },
395 | "peerDependenciesMeta": {
396 | "@emotion/react": {
397 | "optional": true
398 | },
399 | "@emotion/styled": {
400 | "optional": true
401 | }
402 | }
403 | },
404 | "node_modules/@mui/system": {
405 | "version": "5.14.14",
406 | "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.14.tgz",
407 | "integrity": "sha512-y4InFmCgGGWXnz+iK4jRTWVikY0HgYnABjz4wgiUgEa2W1H8M4ow+27BegExUWPkj4TWthQ2qG9FOGSMtI+PKA==",
408 | "dependencies": {
409 | "@babel/runtime": "^7.23.1",
410 | "@mui/private-theming": "^5.14.14",
411 | "@mui/styled-engine": "^5.14.13",
412 | "@mui/types": "^7.2.6",
413 | "@mui/utils": "^5.14.13",
414 | "clsx": "^2.0.0",
415 | "csstype": "^3.1.2",
416 | "prop-types": "^15.8.1"
417 | },
418 | "engines": {
419 | "node": ">=12.0.0"
420 | },
421 | "funding": {
422 | "type": "opencollective",
423 | "url": "https://opencollective.com/mui"
424 | },
425 | "peerDependencies": {
426 | "@emotion/react": "^11.5.0",
427 | "@emotion/styled": "^11.3.0",
428 | "@types/react": "^17.0.0 || ^18.0.0",
429 | "react": "^17.0.0 || ^18.0.0"
430 | },
431 | "peerDependenciesMeta": {
432 | "@emotion/react": {
433 | "optional": true
434 | },
435 | "@emotion/styled": {
436 | "optional": true
437 | },
438 | "@types/react": {
439 | "optional": true
440 | }
441 | }
442 | },
443 | "node_modules/@mui/types": {
444 | "version": "7.2.6",
445 | "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.6.tgz",
446 | "integrity": "sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng==",
447 | "peerDependencies": {
448 | "@types/react": "^17.0.0 || ^18.0.0"
449 | },
450 | "peerDependenciesMeta": {
451 | "@types/react": {
452 | "optional": true
453 | }
454 | }
455 | },
456 | "node_modules/@mui/utils": {
457 | "version": "5.14.14",
458 | "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.14.tgz",
459 | "integrity": "sha512-3AKp8uksje5sRfVrtgG9Q/2TBsHWVBUtA0NaXliZqGcXo8J+A+Agp0qUW2rJ+ivgPWTCCubz9FZVT2IQZ3bGsw==",
460 | "dependencies": {
461 | "@babel/runtime": "^7.23.1",
462 | "@types/prop-types": "^15.7.7",
463 | "prop-types": "^15.8.1",
464 | "react-is": "^18.2.0"
465 | },
466 | "engines": {
467 | "node": ">=12.0.0"
468 | },
469 | "funding": {
470 | "type": "opencollective",
471 | "url": "https://opencollective.com/mui"
472 | },
473 | "peerDependencies": {
474 | "@types/react": "^17.0.0 || ^18.0.0",
475 | "react": "^17.0.0 || ^18.0.0"
476 | },
477 | "peerDependenciesMeta": {
478 | "@types/react": {
479 | "optional": true
480 | }
481 | }
482 | },
483 | "node_modules/@popperjs/core": {
484 | "version": "2.11.8",
485 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
486 | "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
487 | "funding": {
488 | "type": "opencollective",
489 | "url": "https://opencollective.com/popperjs"
490 | }
491 | },
492 | "node_modules/@types/parse-json": {
493 | "version": "4.0.1",
494 | "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz",
495 | "integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng=="
496 | },
497 | "node_modules/@types/prop-types": {
498 | "version": "15.7.9",
499 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz",
500 | "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g=="
501 | },
502 | "node_modules/@types/react": {
503 | "version": "18.2.21",
504 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
505 | "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==",
506 | "dependencies": {
507 | "@types/prop-types": "*",
508 | "@types/scheduler": "*",
509 | "csstype": "^3.0.2"
510 | }
511 | },
512 | "node_modules/@types/react-dom": {
513 | "version": "18.2.7",
514 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
515 | "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
516 | "dev": true,
517 | "dependencies": {
518 | "@types/react": "*"
519 | }
520 | },
521 | "node_modules/@types/react-transition-group": {
522 | "version": "4.4.8",
523 | "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz",
524 | "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==",
525 | "dependencies": {
526 | "@types/react": "*"
527 | }
528 | },
529 | "node_modules/@types/scheduler": {
530 | "version": "0.16.3",
531 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
532 | "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
533 | },
534 | "node_modules/ansi-styles": {
535 | "version": "3.2.1",
536 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
537 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
538 | "dependencies": {
539 | "color-convert": "^1.9.0"
540 | },
541 | "engines": {
542 | "node": ">=4"
543 | }
544 | },
545 | "node_modules/babel-plugin-macros": {
546 | "version": "3.1.0",
547 | "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
548 | "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
549 | "dependencies": {
550 | "@babel/runtime": "^7.12.5",
551 | "cosmiconfig": "^7.0.0",
552 | "resolve": "^1.19.0"
553 | },
554 | "engines": {
555 | "node": ">=10",
556 | "npm": ">=6"
557 | }
558 | },
559 | "node_modules/callsites": {
560 | "version": "3.1.0",
561 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
562 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
563 | "engines": {
564 | "node": ">=6"
565 | }
566 | },
567 | "node_modules/chalk": {
568 | "version": "2.4.2",
569 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
570 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
571 | "dependencies": {
572 | "ansi-styles": "^3.2.1",
573 | "escape-string-regexp": "^1.0.5",
574 | "supports-color": "^5.3.0"
575 | },
576 | "engines": {
577 | "node": ">=4"
578 | }
579 | },
580 | "node_modules/chalk/node_modules/escape-string-regexp": {
581 | "version": "1.0.5",
582 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
583 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
584 | "engines": {
585 | "node": ">=0.8.0"
586 | }
587 | },
588 | "node_modules/clsx": {
589 | "version": "2.0.0",
590 | "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
591 | "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
592 | "engines": {
593 | "node": ">=6"
594 | }
595 | },
596 | "node_modules/color-convert": {
597 | "version": "1.9.3",
598 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
599 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
600 | "dependencies": {
601 | "color-name": "1.1.3"
602 | }
603 | },
604 | "node_modules/color-name": {
605 | "version": "1.1.3",
606 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
607 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
608 | },
609 | "node_modules/convert-source-map": {
610 | "version": "1.9.0",
611 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
612 | "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
613 | },
614 | "node_modules/cosmiconfig": {
615 | "version": "7.1.0",
616 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
617 | "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
618 | "dependencies": {
619 | "@types/parse-json": "^4.0.0",
620 | "import-fresh": "^3.2.1",
621 | "parse-json": "^5.0.0",
622 | "path-type": "^4.0.0",
623 | "yaml": "^1.10.0"
624 | },
625 | "engines": {
626 | "node": ">=10"
627 | }
628 | },
629 | "node_modules/csstype": {
630 | "version": "3.1.2",
631 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
632 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
633 | },
634 | "node_modules/dom-helpers": {
635 | "version": "5.2.1",
636 | "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
637 | "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
638 | "dependencies": {
639 | "@babel/runtime": "^7.8.7",
640 | "csstype": "^3.0.2"
641 | }
642 | },
643 | "node_modules/error-ex": {
644 | "version": "1.3.2",
645 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
646 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
647 | "dependencies": {
648 | "is-arrayish": "^0.2.1"
649 | }
650 | },
651 | "node_modules/escape-string-regexp": {
652 | "version": "4.0.0",
653 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
654 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
655 | "engines": {
656 | "node": ">=10"
657 | },
658 | "funding": {
659 | "url": "https://github.com/sponsors/sindresorhus"
660 | }
661 | },
662 | "node_modules/find-root": {
663 | "version": "1.1.0",
664 | "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
665 | "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
666 | },
667 | "node_modules/function-bind": {
668 | "version": "1.1.2",
669 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
670 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
671 | "funding": {
672 | "url": "https://github.com/sponsors/ljharb"
673 | }
674 | },
675 | "node_modules/has-flag": {
676 | "version": "3.0.0",
677 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
678 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
679 | "engines": {
680 | "node": ">=4"
681 | }
682 | },
683 | "node_modules/hasown": {
684 | "version": "2.0.0",
685 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
686 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
687 | "dependencies": {
688 | "function-bind": "^1.1.2"
689 | },
690 | "engines": {
691 | "node": ">= 0.4"
692 | }
693 | },
694 | "node_modules/hoist-non-react-statics": {
695 | "version": "3.3.2",
696 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
697 | "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
698 | "dependencies": {
699 | "react-is": "^16.7.0"
700 | }
701 | },
702 | "node_modules/hoist-non-react-statics/node_modules/react-is": {
703 | "version": "16.13.1",
704 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
705 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
706 | },
707 | "node_modules/import-fresh": {
708 | "version": "3.3.0",
709 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
710 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
711 | "dependencies": {
712 | "parent-module": "^1.0.0",
713 | "resolve-from": "^4.0.0"
714 | },
715 | "engines": {
716 | "node": ">=6"
717 | },
718 | "funding": {
719 | "url": "https://github.com/sponsors/sindresorhus"
720 | }
721 | },
722 | "node_modules/is-arrayish": {
723 | "version": "0.2.1",
724 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
725 | "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
726 | },
727 | "node_modules/is-core-module": {
728 | "version": "2.13.1",
729 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
730 | "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
731 | "dependencies": {
732 | "hasown": "^2.0.0"
733 | },
734 | "funding": {
735 | "url": "https://github.com/sponsors/ljharb"
736 | }
737 | },
738 | "node_modules/js-tokens": {
739 | "version": "4.0.0",
740 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
741 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
742 | },
743 | "node_modules/json-parse-even-better-errors": {
744 | "version": "2.3.1",
745 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
746 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
747 | },
748 | "node_modules/lines-and-columns": {
749 | "version": "1.2.4",
750 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
751 | "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
752 | },
753 | "node_modules/loose-envify": {
754 | "version": "1.4.0",
755 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
756 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
757 | "dependencies": {
758 | "js-tokens": "^3.0.0 || ^4.0.0"
759 | },
760 | "bin": {
761 | "loose-envify": "cli.js"
762 | }
763 | },
764 | "node_modules/object-assign": {
765 | "version": "4.1.1",
766 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
767 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
768 | "engines": {
769 | "node": ">=0.10.0"
770 | }
771 | },
772 | "node_modules/parent-module": {
773 | "version": "1.0.1",
774 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
775 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
776 | "dependencies": {
777 | "callsites": "^3.0.0"
778 | },
779 | "engines": {
780 | "node": ">=6"
781 | }
782 | },
783 | "node_modules/parse-json": {
784 | "version": "5.2.0",
785 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
786 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
787 | "dependencies": {
788 | "@babel/code-frame": "^7.0.0",
789 | "error-ex": "^1.3.1",
790 | "json-parse-even-better-errors": "^2.3.0",
791 | "lines-and-columns": "^1.1.6"
792 | },
793 | "engines": {
794 | "node": ">=8"
795 | },
796 | "funding": {
797 | "url": "https://github.com/sponsors/sindresorhus"
798 | }
799 | },
800 | "node_modules/path-parse": {
801 | "version": "1.0.7",
802 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
803 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
804 | },
805 | "node_modules/path-type": {
806 | "version": "4.0.0",
807 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
808 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
809 | "engines": {
810 | "node": ">=8"
811 | }
812 | },
813 | "node_modules/prop-types": {
814 | "version": "15.8.1",
815 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
816 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
817 | "dependencies": {
818 | "loose-envify": "^1.4.0",
819 | "object-assign": "^4.1.1",
820 | "react-is": "^16.13.1"
821 | }
822 | },
823 | "node_modules/prop-types/node_modules/react-is": {
824 | "version": "16.13.1",
825 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
826 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
827 | },
828 | "node_modules/react": {
829 | "version": "18.2.0",
830 | "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
831 | "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
832 | "dependencies": {
833 | "loose-envify": "^1.1.0"
834 | },
835 | "engines": {
836 | "node": ">=0.10.0"
837 | }
838 | },
839 | "node_modules/react-dom": {
840 | "version": "18.2.0",
841 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
842 | "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
843 | "dependencies": {
844 | "loose-envify": "^1.1.0",
845 | "scheduler": "^0.23.0"
846 | },
847 | "peerDependencies": {
848 | "react": "^18.2.0"
849 | }
850 | },
851 | "node_modules/react-is": {
852 | "version": "18.2.0",
853 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
854 | "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
855 | },
856 | "node_modules/react-transition-group": {
857 | "version": "4.4.5",
858 | "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
859 | "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
860 | "dependencies": {
861 | "@babel/runtime": "^7.5.5",
862 | "dom-helpers": "^5.0.1",
863 | "loose-envify": "^1.4.0",
864 | "prop-types": "^15.6.2"
865 | },
866 | "peerDependencies": {
867 | "react": ">=16.6.0",
868 | "react-dom": ">=16.6.0"
869 | }
870 | },
871 | "node_modules/regenerator-runtime": {
872 | "version": "0.14.0",
873 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
874 | "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
875 | },
876 | "node_modules/resolve": {
877 | "version": "1.22.8",
878 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
879 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
880 | "dependencies": {
881 | "is-core-module": "^2.13.0",
882 | "path-parse": "^1.0.7",
883 | "supports-preserve-symlinks-flag": "^1.0.0"
884 | },
885 | "bin": {
886 | "resolve": "bin/resolve"
887 | },
888 | "funding": {
889 | "url": "https://github.com/sponsors/ljharb"
890 | }
891 | },
892 | "node_modules/resolve-from": {
893 | "version": "4.0.0",
894 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
895 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
896 | "engines": {
897 | "node": ">=4"
898 | }
899 | },
900 | "node_modules/scheduler": {
901 | "version": "0.23.0",
902 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
903 | "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
904 | "dependencies": {
905 | "loose-envify": "^1.1.0"
906 | }
907 | },
908 | "node_modules/source-map": {
909 | "version": "0.5.7",
910 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
911 | "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
912 | "engines": {
913 | "node": ">=0.10.0"
914 | }
915 | },
916 | "node_modules/stylis": {
917 | "version": "4.2.0",
918 | "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
919 | "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
920 | },
921 | "node_modules/supports-color": {
922 | "version": "5.5.0",
923 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
924 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
925 | "dependencies": {
926 | "has-flag": "^3.0.0"
927 | },
928 | "engines": {
929 | "node": ">=4"
930 | }
931 | },
932 | "node_modules/supports-preserve-symlinks-flag": {
933 | "version": "1.0.0",
934 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
935 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
936 | "engines": {
937 | "node": ">= 0.4"
938 | },
939 | "funding": {
940 | "url": "https://github.com/sponsors/ljharb"
941 | }
942 | },
943 | "node_modules/to-fast-properties": {
944 | "version": "2.0.0",
945 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
946 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
947 | "engines": {
948 | "node": ">=4"
949 | }
950 | },
951 | "node_modules/typescript": {
952 | "version": "5.2.2",
953 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
954 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
955 | "dev": true,
956 | "bin": {
957 | "tsc": "bin/tsc",
958 | "tsserver": "bin/tsserver"
959 | },
960 | "engines": {
961 | "node": ">=14.17"
962 | }
963 | },
964 | "node_modules/yaml": {
965 | "version": "1.10.2",
966 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
967 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
968 | "engines": {
969 | "node": ">= 6"
970 | }
971 | }
972 | }
973 | }
974 |
--------------------------------------------------------------------------------
/examples/frontend-mui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@emotion/react": "^11.11.1",
4 | "@emotion/styled": "^11.11.0",
5 | "@mui/material": "^5.14.14",
6 | "react": "^18.2.0",
7 | "react-dom": "^18.2.0"
8 | },
9 | "devDependencies": {
10 | "@types/react": "^18.2.21",
11 | "@types/react-dom": "^18.2.7",
12 | "typescript": "^5.2.2"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/frontend-mui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend-mui/public/favicon.ico
--------------------------------------------------------------------------------
/examples/frontend-mui/public/go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend-mui/public/go.png
--------------------------------------------------------------------------------
/examples/frontend-mui/public/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend-mui/public/react.png
--------------------------------------------------------------------------------
/examples/frontend-mui/src/Home.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #242424;
3 | margin: 0 !important;
4 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
5 | }
6 |
7 | .home {
8 | height: 100vh;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | flex-direction: column;
13 | }
14 |
15 | .img-container {
16 | display: flex;
17 | gap: 24px;
18 | align-items: center;
19 | }
20 |
21 | h1 {
22 | padding-top: 20px;
23 | color: #fff;
24 | font-size: 42px;
25 | margin-bottom: 0;
26 | }
27 |
28 | .counter-container {
29 | padding: 28px 0px;
30 | }
31 |
32 | a {
33 | color: #888;
34 | text-decoration: none;
35 | }
36 |
37 | a:hover {
38 | text-decoration: underline;
39 | }
40 |
--------------------------------------------------------------------------------
/examples/frontend-mui/src/Home.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { IndexRouteProps } from "./generated";
3 | import GoLogo from "../public/go.png";
4 | import ReactLogo from "../public/react.png";
5 | import "./Home.css";
6 | import Counter from "./components/Counter";
7 |
8 | function Home({ initialCount }: IndexRouteProps) {
9 | const [count, setCount] = useState(initialCount);
10 |
11 | return (
12 |
25 | );
26 | }
27 |
28 | export default Home;
29 |
--------------------------------------------------------------------------------
/examples/frontend-mui/src/Layout.tsx:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 | import { CacheProvider } from "@emotion/react";
3 |
4 | const cache = createCache({ key: "css" })
5 |
6 | export default function Layout({ children }: { children: React.ReactNode }) {
7 | console.log("Hello from Layout");
8 | return {children};
9 | }
10 |
--------------------------------------------------------------------------------
/examples/frontend-mui/src/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | import Button from "@mui/material/Button";
2 |
3 | type Props = {
4 | count: number;
5 | increment: () => void;
6 | };
7 | export default function Counter({ count, increment }: Props) {
8 | return (
9 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/frontend-mui/src/generated.d.ts:
--------------------------------------------------------------------------------
1 | /* Do not change, this code is generated from Golang structs */
2 |
3 |
4 | export interface IndexRouteProps {
5 | initialCount: number;
6 | }
--------------------------------------------------------------------------------
/examples/frontend-mui/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.css";
2 | declare module "*.png";
3 |
--------------------------------------------------------------------------------
/examples/frontend-mui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "module": "ESNext",
6 | "skipLibCheck": true,
7 | "moduleResolution": "bundler",
8 | "allowImportingTsExtensions": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "noEmit": true,
12 | "jsx": "react-jsx",
13 | "strict": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noFallthroughCasesInSwitch": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "react": "^18.2.0",
4 | "react-dom": "^18.2.0"
5 | },
6 | "devDependencies": {
7 | "@types/react": "^18.2.21",
8 | "@types/react-dom": "^18.2.7",
9 | "tailwindcss": "^3.3.3",
10 | "typescript": "^5.2.2"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend-tailwind/public/favicon.ico
--------------------------------------------------------------------------------
/examples/frontend-tailwind/public/go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend-tailwind/public/go.png
--------------------------------------------------------------------------------
/examples/frontend-tailwind/public/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend-tailwind/public/react.png
--------------------------------------------------------------------------------
/examples/frontend-tailwind/src/Home.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { IndexRouteProps } from "./generated";
3 | import GoLogo from "../public/go.png";
4 | import ReactLogo from "../public/react.png";
5 | import Counter from "./components/Counter";
6 |
7 | function Home({ initialCount }: IndexRouteProps) {
8 | const [count, setCount] = useState(initialCount);
9 |
10 | return (
11 |
24 | );
25 | }
26 |
27 | export default Home;
28 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/src/Main.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/src/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | count: number;
3 | increment: () => void;
4 | };
5 | export default function Counter({ count, increment }: Props) {
6 | return (
7 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/src/generated.d.ts:
--------------------------------------------------------------------------------
1 | /* Do not change, this code is generated from Golang structs */
2 |
3 |
4 | export interface IndexRouteProps {
5 | initialCount: number;
6 | }
--------------------------------------------------------------------------------
/examples/frontend-tailwind/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.css";
2 | declare module "*.png";
3 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./src/**/*.{ts,tsx,js,jsx}"],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/examples/frontend-tailwind/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "module": "ESNext",
6 | "skipLibCheck": true,
7 | "moduleResolution": "bundler",
8 | "allowImportingTsExtensions": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "noEmit": true,
12 | "jsx": "react-jsx",
13 | "strict": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noFallthroughCasesInSwitch": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/frontend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "dependencies": {
8 | "react": "^18.2.0",
9 | "react-dom": "^18.2.0"
10 | },
11 | "devDependencies": {
12 | "@types/react": "^18.2.21",
13 | "@types/react-dom": "^18.2.7",
14 | "typescript": "^5.2.2"
15 | }
16 | },
17 | "node_modules/@types/prop-types": {
18 | "version": "15.7.5",
19 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
20 | "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
21 | "dev": true
22 | },
23 | "node_modules/@types/react": {
24 | "version": "18.2.21",
25 | "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
26 | "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==",
27 | "dev": true,
28 | "dependencies": {
29 | "@types/prop-types": "*",
30 | "@types/scheduler": "*",
31 | "csstype": "^3.0.2"
32 | }
33 | },
34 | "node_modules/@types/react-dom": {
35 | "version": "18.2.7",
36 | "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
37 | "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
38 | "dev": true,
39 | "dependencies": {
40 | "@types/react": "*"
41 | }
42 | },
43 | "node_modules/@types/scheduler": {
44 | "version": "0.16.3",
45 | "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
46 | "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
47 | "dev": true
48 | },
49 | "node_modules/csstype": {
50 | "version": "3.1.2",
51 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
52 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
53 | "dev": true
54 | },
55 | "node_modules/js-tokens": {
56 | "version": "4.0.0",
57 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
58 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
59 | },
60 | "node_modules/loose-envify": {
61 | "version": "1.4.0",
62 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
63 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
64 | "dependencies": {
65 | "js-tokens": "^3.0.0 || ^4.0.0"
66 | },
67 | "bin": {
68 | "loose-envify": "cli.js"
69 | }
70 | },
71 | "node_modules/react": {
72 | "version": "18.2.0",
73 | "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
74 | "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
75 | "dependencies": {
76 | "loose-envify": "^1.1.0"
77 | },
78 | "engines": {
79 | "node": ">=0.10.0"
80 | }
81 | },
82 | "node_modules/react-dom": {
83 | "version": "18.2.0",
84 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
85 | "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
86 | "dependencies": {
87 | "loose-envify": "^1.1.0",
88 | "scheduler": "^0.23.0"
89 | },
90 | "peerDependencies": {
91 | "react": "^18.2.0"
92 | }
93 | },
94 | "node_modules/scheduler": {
95 | "version": "0.23.0",
96 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
97 | "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
98 | "dependencies": {
99 | "loose-envify": "^1.1.0"
100 | }
101 | },
102 | "node_modules/typescript": {
103 | "version": "5.2.2",
104 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
105 | "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
106 | "dev": true,
107 | "bin": {
108 | "tsc": "bin/tsc",
109 | "tsserver": "bin/tsserver"
110 | },
111 | "engines": {
112 | "node": ">=14.17"
113 | }
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/examples/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "react": "^18.2.0",
4 | "react-dom": "^18.2.0"
5 | },
6 | "devDependencies": {
7 | "@types/react": "^18.2.21",
8 | "@types/react-dom": "^18.2.7",
9 | "typescript": "^5.2.2"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/examples/frontend/public/go.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend/public/go.png
--------------------------------------------------------------------------------
/examples/frontend/public/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/natewong1313/go-react-ssr/960cd71be1380bf0428d790d111df29e5916e9d4/examples/frontend/public/react.png
--------------------------------------------------------------------------------
/examples/frontend/src/Home.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #242424;
3 | margin: 0 !important;
4 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
5 | }
6 |
7 | .home {
8 | height: 100vh;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | flex-direction: column;
13 | }
14 |
15 | .img-container {
16 | display: flex;
17 | gap: 24px;
18 | align-items: center;
19 | }
20 |
21 | h1 {
22 | padding-top: 20px;
23 | color: #fff;
24 | font-size: 42px;
25 | margin-bottom: 0;
26 | }
27 |
28 | .counter-container {
29 | padding: 28px 0px;
30 | }
31 |
32 | a {
33 | color: #888;
34 | text-decoration: none;
35 | }
36 |
37 | a:hover {
38 | text-decoration: underline;
39 | }
40 |
--------------------------------------------------------------------------------
/examples/frontend/src/Home.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { IndexRouteProps } from "./generated";
3 | import GoLogo from "../public/go.png";
4 | import ReactLogo from "../public/react.png";
5 | import "./Home.css";
6 | import Counter from "./components/Counter";
7 |
8 | function Home({ initialCount }: IndexRouteProps) {
9 | const [count, setCount] = useState(initialCount);
10 |
11 | return (
12 |
25 | );
26 | }
27 |
28 | export default Home;
29 |
--------------------------------------------------------------------------------
/examples/frontend/src/Layout.tsx:
--------------------------------------------------------------------------------
1 | export default function Layout({ children }: { children: React.ReactNode }) {
2 | console.log("Hello from Layout");
3 | return <>{children}>;
4 | }
5 |
--------------------------------------------------------------------------------
/examples/frontend/src/components/Counter.module.css:
--------------------------------------------------------------------------------
1 | .counter {
2 | color: white;
3 | background-color: #1a1a1a;
4 | font-size: 16px;
5 | padding: 10px 16px;
6 | border: 2px solid transparent;
7 | border-radius: 8px;
8 | cursor: pointer;
9 | }
10 |
11 | .counter:hover {
12 | border: 2px solid #06a8c5;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/frontend/src/components/Counter.tsx:
--------------------------------------------------------------------------------
1 | import styles from "./Counter.module.css";
2 |
3 | type Props = {
4 | count: number;
5 | increment: () => void;
6 | };
7 | export default function Counter({ count, increment }: Props) {
8 | return (
9 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/examples/frontend/src/generated.d.ts:
--------------------------------------------------------------------------------
1 | /* Do not change, this code is generated from Golang structs */
2 |
3 |
4 | export interface IndexRouteProps {
5 | initialCount: number;
6 | }
--------------------------------------------------------------------------------
/examples/frontend/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.css";
2 | declare module "*.png";
3 |
--------------------------------------------------------------------------------
/examples/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "module": "ESNext",
6 | "skipLibCheck": true,
7 | "moduleResolution": "bundler",
8 | "allowImportingTsExtensions": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "noEmit": true,
12 | "jsx": "react-jsx",
13 | "strict": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noFallthroughCasesInSwitch": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/examples/gin/.dockerignore:
--------------------------------------------------------------------------------
1 | */node_modules
2 | .dockerignore
3 | Dockerfile
4 | tmp
--------------------------------------------------------------------------------
/examples/gin/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16-alpine as build-frontend
2 | ADD ./frontend-tailwind ./build
3 | WORKDIR /build
4 | RUN npm install
5 |
6 | FROM golang:1.21-alpine
7 | RUN apk add git
8 | ADD . /app
9 | COPY --from=build-frontend /build ./app/frontend-tailwind
10 | WORKDIR /app
11 |
12 | RUN go mod download
13 | RUN go get -u github.com/natewong1313/go-react-ssr
14 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w -X main.APP_ENV=production" -a -o main
15 |
16 | RUN chmod +x ./main
17 | EXPOSE 8080
18 | CMD ["./main"]
19 |
--------------------------------------------------------------------------------
/examples/gin/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "1"
2 | services:
3 | app:
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 | ports:
8 | - "8080:8080"
9 | environment:
10 | - APP_ENV=production
11 |
--------------------------------------------------------------------------------
/examples/gin/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/gin
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.1
7 | github.com/natewong1313/go-react-ssr v0.0.3
8 | github.com/stretchr/testify v1.8.3
9 | )
10 |
11 | require (
12 | github.com/buger/jsonparser v1.1.1 // indirect
13 | github.com/bytedance/sonic v1.9.1 // indirect
14 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
15 | github.com/creasty/defaults v1.7.0 // indirect
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/evanw/esbuild v0.19.3 // indirect
18 | github.com/fsnotify/fsnotify v1.6.0 // indirect
19 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
20 | github.com/gin-contrib/sse v0.1.0 // indirect
21 | github.com/go-playground/locales v0.14.1 // indirect
22 | github.com/go-playground/universal-translator v0.18.1 // indirect
23 | github.com/go-playground/validator/v10 v10.14.0 // indirect
24 | github.com/goccy/go-json v0.10.2 // indirect
25 | github.com/gorilla/websocket v1.5.0 // indirect
26 | github.com/joho/godotenv v1.5.1 // indirect
27 | github.com/json-iterator/go v1.1.12 // indirect
28 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
29 | github.com/leodido/go-urn v1.2.4 // indirect
30 | github.com/mattn/go-colorable v0.1.13 // indirect
31 | github.com/mattn/go-isatty v0.0.19 // indirect
32 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
33 | github.com/modern-go/reflect2 v1.0.2 // indirect
34 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
35 | github.com/pmezard/go-difflib v1.0.0 // indirect
36 | github.com/rs/zerolog v1.30.0 // indirect
37 | github.com/tkrajina/go-reflector v0.5.6 // indirect
38 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 // indirect
39 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
40 | github.com/ugorji/go/codec v1.2.11 // indirect
41 | golang.org/x/arch v0.3.0 // indirect
42 | golang.org/x/crypto v0.9.0 // indirect
43 | golang.org/x/net v0.10.0 // indirect
44 | golang.org/x/sys v0.12.0 // indirect
45 | golang.org/x/text v0.9.0 // indirect
46 | google.golang.org/protobuf v1.30.0 // indirect
47 | gopkg.in/yaml.v3 v3.0.1 // indirect
48 | )
49 |
--------------------------------------------------------------------------------
/examples/gin/go.sum:
--------------------------------------------------------------------------------
1 | github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
2 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
3 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
4 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
5 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
6 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
7 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
9 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
10 | github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
11 | github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15 | github.com/evanw/esbuild v0.19.3 h1:foPr0xwQM3lBWKBtscauTN9FrmJzRDVI2+EGOs82H/I=
16 | github.com/evanw/esbuild v0.19.3/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
17 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
18 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
19 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
20 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
21 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
22 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
23 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
24 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
25 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
26 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
27 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
28 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
29 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
30 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
31 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
32 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
33 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
34 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
35 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
36 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
37 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
38 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
39 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
40 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
41 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
42 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
43 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
44 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
45 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
46 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
47 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
48 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
49 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
50 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
51 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
52 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
53 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
54 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
55 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
56 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
57 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
58 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
59 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
60 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
61 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
62 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
63 | github.com/natewong1313/go-react-ssr v0.0.3 h1:fbcwVdAqR7qrtx9Ea8Od54zou1fzn9xEQXBpiqIXmoU=
64 | github.com/natewong1313/go-react-ssr v0.0.3/go.mod h1:mNRYw/XYoE/BEdjDARW+dPqSMr9VE3xRlz7J3BUk1ZE=
65 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
66 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
67 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
68 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
69 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
70 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
71 | github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
72 | github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
73 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
74 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
75 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
76 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
77 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
78 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
79 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
80 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
81 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
82 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
83 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
84 | github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
85 | github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
86 | github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
87 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 h1:W/Ta9Kqo2lV+7bVXuQoUhZ0bDlnjwtPpKsy3A9M1nYg=
88 | github.com/tkrajina/typescriptify-golang-structs v0.1.10/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
89 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
90 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
91 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
92 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
93 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
94 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
95 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
96 | golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
97 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
98 | golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
99 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
100 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
101 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
102 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
103 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
104 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
105 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
106 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
107 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
108 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
109 | golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
110 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
111 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
112 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
113 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
114 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
115 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
116 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
117 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
118 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
119 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
120 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
121 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
122 |
--------------------------------------------------------------------------------
/examples/gin/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "math/rand"
6 |
7 | "example.com/gin/models"
8 |
9 | "github.com/gin-gonic/gin"
10 | gossr "github.com/natewong1313/go-react-ssr"
11 | )
12 |
13 | var APP_ENV string
14 |
15 | func main() {
16 | g := gin.Default()
17 | g.StaticFile("favicon.ico", "../frontend-tailwind/public/favicon.ico")
18 | g.Static("/assets", "../frontend-tailwind/public")
19 | engine, err := gossr.New(gossr.Config{
20 | AppEnv: APP_ENV,
21 | AssetRoute: "/assets",
22 | FrontendDir: "../frontend-tailwind/src",
23 | GeneratedTypesPath: "../frontend-tailwind/src/generated.d.ts",
24 | TailwindConfigPath: "../frontend-tailwind/tailwind.config.js",
25 | LayoutCSSFilePath: "Main.css",
26 | PropsStructsPath: "./models/props.go",
27 | })
28 | if err != nil {
29 | log.Fatal("Failed to init go-react-ssr")
30 | }
31 |
32 | g.GET("/", func(c *gin.Context) {
33 | c.Writer.Write(engine.RenderRoute(gossr.RenderConfig{
34 | File: "Home.tsx",
35 | Title: "Gin example app",
36 | MetaTags: map[string]string{
37 | "og:title": "Gin example app",
38 | "description": "Hello world!",
39 | },
40 | Props: &models.IndexRouteProps{
41 | InitialCount: rand.Intn(100),
42 | },
43 | }))
44 | })
45 | g.Run()
46 | }
47 |
--------------------------------------------------------------------------------
/examples/gin/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net"
6 | "net/http"
7 | "testing"
8 | "time"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func Test_main(t *testing.T) {
14 | go main()
15 | // Wait for server to start
16 | for {
17 | conn, _ := net.DialTimeout("tcp", net.JoinHostPort("", "8080"), time.Millisecond*1000)
18 | if conn != nil {
19 | conn.Close()
20 | break
21 | }
22 | }
23 | res, err := http.Get("http://localhost:8080")
24 | assert.Nil(t, err)
25 | assert.Equal(t, 200, res.StatusCode)
26 |
27 | resBody, err := io.ReadAll(res.Body)
28 | assert.Nil(t, err)
29 | assert.NotNil(t, resBody)
30 | assert.NotContains(t, string(resBody), "An error occured!")
31 | }
32 |
--------------------------------------------------------------------------------
/examples/gin/models/props.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | type IndexRouteProps struct {
4 | InitialCount int `json:"initialCount"`
5 | }
6 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/natewong1313/go-react-ssr
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/buger/jsonparser v1.1.1
7 | github.com/buke/quickjs-go v0.2.4
8 | github.com/evanw/esbuild v0.19.11
9 | github.com/fsnotify/fsnotify v1.7.0
10 | github.com/gorilla/websocket v1.5.1
11 | github.com/rs/zerolog v1.31.0
12 | github.com/tkrajina/typescriptify-golang-structs v0.1.11
13 | )
14 |
15 | require (
16 | github.com/mattn/go-colorable v0.1.13 // indirect
17 | github.com/stretchr/testify v1.8.4 // indirect
18 | )
19 |
20 | require (
21 | github.com/mattn/go-isatty v0.0.20 // indirect
22 | github.com/tkrajina/go-reflector v0.5.6 // indirect
23 | golang.org/x/net v0.19.0 // indirect
24 | golang.org/x/sys v0.16.0 // indirect
25 | )
26 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
2 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
3 | github.com/buke/quickjs-go v0.2.3 h1:aoQ46FaMUdDhQf57sKKWkDQj1BiAOzJi9v/A7SNVyTU=
4 | github.com/buke/quickjs-go v0.2.3/go.mod h1:d+CLE8FY8v4gQJkPlwcinM+E9mhREMpYy5Zn7nlgE9s=
5 | github.com/buke/quickjs-go v0.2.4 h1:l4Rk6EwgwOcf1JUxFJ+1rVOlGJd1Ofdt+b9U8B6pO1Y=
6 | github.com/buke/quickjs-go v0.2.4/go.mod h1:d+CLE8FY8v4gQJkPlwcinM+E9mhREMpYy5Zn7nlgE9s=
7 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10 | github.com/evanw/esbuild v0.19.3 h1:foPr0xwQM3lBWKBtscauTN9FrmJzRDVI2+EGOs82H/I=
11 | github.com/evanw/esbuild v0.19.3/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
12 | github.com/evanw/esbuild v0.19.11 h1:mbPO1VJ/df//jjUd+p/nRLYCpizXxXb2w/zZMShxa2k=
13 | github.com/evanw/esbuild v0.19.11/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
14 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
15 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
16 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
17 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
18 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
19 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
20 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
21 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
22 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
23 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
24 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
25 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
26 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
27 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
28 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
29 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
30 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
31 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
32 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
33 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
35 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
36 | github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
37 | github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
38 | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
39 | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
40 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
41 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
42 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
43 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
44 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
45 | github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ=
46 | github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
47 | github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQSepKdE=
48 | github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
49 | github.com/tkrajina/typescriptify-golang-structs v0.1.10 h1:W/Ta9Kqo2lV+7bVXuQoUhZ0bDlnjwtPpKsy3A9M1nYg=
50 | github.com/tkrajina/typescriptify-golang-structs v0.1.10/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
51 | github.com/tkrajina/typescriptify-golang-structs v0.1.11 h1:zEIVczF/iWgs4eTY7NQqbBe23OVlFVk9sWLX/FDYi4Q=
52 | github.com/tkrajina/typescriptify-golang-structs v0.1.11/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
53 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
54 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
55 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
56 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
57 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
58 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
59 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
60 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
61 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
62 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
63 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
64 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
65 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
66 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
67 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
68 |
--------------------------------------------------------------------------------
/go.work:
--------------------------------------------------------------------------------
1 | go 1.20
2 |
3 | use (
4 | .
5 | ./examples/fiber
6 | ./examples/gin
7 | ./examples/echo
8 | ./gossr-cli
9 | )
10 |
--------------------------------------------------------------------------------
/go.work.sum:
--------------------------------------------------------------------------------
1 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
2 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
3 | github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
4 | github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d h1:W1n4DvpzZGOISgp7wWNtraLcHtnmnTwBlJidqtMIuwQ=
5 | github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
6 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
7 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
8 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
9 | github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2 h1:rcanfLhLDA8nozr/K289V1zcntHr3V+SHlXwzz1ZI2g=
10 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
11 | github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
12 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
13 | github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
14 | github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
15 | github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
16 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
17 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
18 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
19 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
20 | github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
21 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
22 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
23 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
24 | github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
25 | github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
26 | github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
27 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
28 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
29 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
30 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
31 | golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
32 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
33 | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
34 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
35 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
36 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
37 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
39 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
40 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
41 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
42 | golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
43 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
44 | golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
45 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
46 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
47 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
48 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
49 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
50 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
51 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
52 | gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
53 | rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
54 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/create/bootstrapper.go:
--------------------------------------------------------------------------------
1 | package create
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 | "strings"
7 | "sync"
8 |
9 | cp "github.com/otiai10/copy"
10 |
11 | "github.com/natewong1313/go-react-ssr/gossr-cli/logger"
12 | "github.com/natewong1313/go-react-ssr/gossr-cli/utils"
13 | )
14 |
15 | type Bootstrapper struct {
16 | TempDirPath string
17 | ProjectDir string
18 | GoModuleName string
19 | FrontendDir string
20 | WebFramework string
21 | StylingPlugin string
22 | }
23 |
24 | func (b *Bootstrapper) Start() {
25 | b.TempDirPath = createTempDir()
26 | b.cloneRepo()
27 | b.moveGoFiles()
28 | var wg sync.WaitGroup
29 | wg.Add(2)
30 | go b.setupFrontend(&wg)
31 | go b.setupBackend(&wg)
32 | wg.Wait()
33 | logger.L.Info().Msg("Project setup complete! 🎉")
34 | }
35 |
36 | func (b *Bootstrapper) cloneRepo() {
37 | logger.L.Info().Msg("Cloning example repository")
38 | cmd := exec.Command("git", "clone", "https://github.com/natewong1313/go-react-ssr.git")
39 | cmd.Dir = b.TempDirPath
40 | err := cmd.Run()
41 | if err != nil {
42 | utils.HandleError(err)
43 | }
44 | }
45 |
46 | func (b *Bootstrapper) moveGoFiles() {
47 | logger.L.Info().Msg("Setting up Go files")
48 | err := cp.Copy(b.TempDirPath+"/go-react-ssr/examples/"+strings.ToLower(b.WebFramework), b.ProjectDir)
49 | if err != nil {
50 | utils.HandleError(err)
51 | }
52 | }
53 |
54 | func (b *Bootstrapper) setupFrontend(wg *sync.WaitGroup) {
55 | b.createFrontendFolder()
56 | b.installNPMDependencies()
57 | wg.Done()
58 | }
59 |
60 | func (b *Bootstrapper) setupBackend(wg *sync.WaitGroup) {
61 | b.updateGoModules()
62 | b.replaceImportsInGoFile()
63 | b.updateDockerFile()
64 | wg.Done()
65 | }
66 |
67 | func (b *Bootstrapper) createFrontendFolder() {
68 | logger.L.Info().Msg("Creating /frontend folder")
69 | frontendFolderFromGit := b.TempDirPath + "/go-react-ssr/examples/frontend"
70 | if b.StylingPlugin == "Tailwind" {
71 | frontendFolderFromGit = b.TempDirPath + "/go-react-ssr/examples/frontend-tailwind"
72 | } else if b.StylingPlugin == "Material UI" {
73 | frontendFolderFromGit = b.TempDirPath + "/go-react-ssr/examples/frontend-mui"
74 | }
75 | err := cp.Copy(frontendFolderFromGit, b.ProjectDir+"/frontend")
76 | if err != nil {
77 | utils.HandleError(err)
78 | }
79 | }
80 |
81 | func (b *Bootstrapper) installNPMDependencies() {
82 | logger.L.Info().Msg("Installing npm dependencies")
83 | cmd := exec.Command("npm", "install")
84 | cmd.Dir = b.ProjectDir + "/frontend"
85 | err := cmd.Run()
86 | if err != nil {
87 | utils.HandleError(err)
88 | }
89 | }
90 |
91 | func (b *Bootstrapper) updateGoModules() {
92 | logger.L.Info().Msg("Installing Go modules")
93 | cmd := exec.Command("go", "get", "-u", "github.com/natewong1313/go-react-ssr")
94 | cmd.Dir = b.ProjectDir
95 | err := cmd.Run()
96 | if err != nil {
97 | utils.HandleError(err)
98 | }
99 | }
100 |
101 | func (b *Bootstrapper) replaceImportsInGoFile() {
102 | logger.L.Info().Msg("Updating imports in main.go")
103 | read, err := os.ReadFile(b.ProjectDir + "/main.go")
104 | if err != nil {
105 | utils.HandleError(err)
106 | }
107 | newContents := strings.Replace(string(read), "../frontend", "./frontend", -1)
108 | newContents = strings.Replace(newContents, "-tailwind/", "/", -1)
109 | newContents = strings.Replace(newContents, "-mui/", "/", -1)
110 | err = os.WriteFile(b.ProjectDir+"/main.go", []byte(newContents), 0644)
111 | if err != nil {
112 | utils.HandleError(err)
113 | }
114 | }
115 |
116 | func (b *Bootstrapper) updateDockerFile() {
117 | logger.L.Info().Msg("Updating Dockerfile")
118 | read, err := os.ReadFile(b.ProjectDir + "/Dockerfile")
119 | if err != nil {
120 | utils.HandleError(err)
121 | }
122 | var contents string
123 | contents = strings.Replace(string(read), "frontend-tailwind", "frontend", -1)
124 | contents = strings.Replace(string(read), "frontend-mui", "frontend", -1)
125 | err = os.WriteFile(b.ProjectDir+"/Dockerfile", []byte(contents), 0644)
126 | if err != nil {
127 | utils.HandleError(err)
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/create/create.go:
--------------------------------------------------------------------------------
1 | package create
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 |
8 | "github.com/natewong1313/go-react-ssr/gossr-cli/cmd"
9 | "github.com/natewong1313/go-react-ssr/gossr-cli/logger"
10 | "github.com/natewong1313/go-react-ssr/gossr-cli/utils"
11 | "github.com/spf13/cobra"
12 | )
13 |
14 | // createCmd represents the create command
15 | var createCmd = &cobra.Command{
16 | Use: "create",
17 | Short: "Create a new Go SSR project",
18 | Long: "Create a new Go SSR project",
19 | Run: create,
20 | }
21 |
22 | func init() {
23 | cmd.RootCmd.AddCommand(createCmd)
24 | }
25 |
26 | func create(cmd *cobra.Command, args []string) {
27 | checkNodeInstalled()
28 | fmt.Println("Welcome to the creation wizard!")
29 | projectDir := prompt_getProjectDirectory(args)
30 | webFramework := prompt_selectWebFramework()
31 | stylingPlugin := prompt_selectStylingPlugin()
32 |
33 | projectDirExists := utils.CheckPathExists(projectDir)
34 | if projectDirExists {
35 | projectDirEmpty := utils.CheckPathEmpty(projectDir)
36 | if !projectDirEmpty && !prompt_shouldWipeDirectory() {
37 | os.Exit(0)
38 | } else {
39 | wipeDirectory(projectDir)
40 | }
41 |
42 | } else {
43 | if err := os.MkdirAll(projectDir, 0777); err != nil {
44 | utils.HandleError(err)
45 | }
46 | }
47 |
48 | bootstrapper := Bootstrapper{
49 | ProjectDir: projectDir,
50 | WebFramework: webFramework,
51 | StylingPlugin: stylingPlugin,
52 | }
53 | bootstrapper.Start()
54 |
55 | }
56 |
57 | func checkNodeInstalled() bool {
58 | cmd := exec.Command("node", "-v")
59 | err := cmd.Run()
60 | if err != nil {
61 | logger.L.Error().Msg("Node.js is not installed. Please install Node and try again.")
62 | os.Exit(1)
63 | }
64 | return true
65 | }
66 |
67 | func wipeDirectory(projectDir string) {
68 | logger.L.Info().Msg("Wiping directory " + projectDir)
69 | if err := os.RemoveAll(projectDir); err != nil {
70 | utils.HandleError(err)
71 | }
72 | if err := os.MkdirAll(projectDir, 0777); err != nil {
73 | utils.HandleError(err)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/create/prompts.go:
--------------------------------------------------------------------------------
1 | package create
2 |
3 | import (
4 | "path/filepath"
5 | "strings"
6 |
7 | "github.com/manifoldco/promptui"
8 | "github.com/natewong1313/go-react-ssr/gossr-cli/logger"
9 | "github.com/natewong1313/go-react-ssr/gossr-cli/utils"
10 | )
11 |
12 | func prompt_getProjectDirectory(args []string) string {
13 | display := func(path string) string {
14 | logger.L.Info().Msg("Creating project at " + path)
15 | return path
16 | }
17 | if len(args) > 0 {
18 | projectDir, err := filepath.Abs(args[0])
19 | projectDir = filepath.ToSlash(projectDir)
20 | if err != nil {
21 | utils.HandleError(err)
22 | }
23 | return display(projectDir)
24 | }
25 |
26 | prompt := promptui.Prompt{
27 | Label: "Enter the path of your project (leave blank to use current directory)",
28 | }
29 |
30 | result, err := prompt.Run()
31 | projectDir, err := filepath.Abs(result)
32 | if err != nil {
33 | utils.HandleError(err)
34 | }
35 | projectDir = filepath.ToSlash(projectDir)
36 | return display(projectDir)
37 | }
38 |
39 | func prompt_selectWebFramework() string {
40 | prompt := promptui.Select{
41 | Label: "Select a web framework to use",
42 | Items: []string{"Gin", "Fiber", "Echo"},
43 | }
44 |
45 | _, result, err := prompt.Run()
46 | if err != nil {
47 | utils.HandleError(err)
48 | }
49 | return result
50 | }
51 |
52 | func prompt_selectStylingPlugin() string {
53 | prompt := promptui.Select{
54 | Label: "Select a styling plugin to use",
55 | Items: []string{"None", "Tailwind", "Material UI"},
56 | }
57 |
58 | _, result, err := prompt.Run()
59 | if err != nil {
60 | utils.HandleError(err)
61 | }
62 | return result
63 | }
64 |
65 | func prompt_isUsingTypescript() bool {
66 | prompt := promptui.Prompt{
67 | Label: "Use Typescript? (y/n)",
68 | Default: "y",
69 | }
70 |
71 | result, err := prompt.Run()
72 | if err != nil {
73 | utils.HandleError(err)
74 | }
75 | return strings.ToLower(result) == "y"
76 | }
77 |
78 | func prompt_shouldWipeDirectory() bool {
79 | prompt := promptui.Prompt{
80 | Label: "Directory is not empty. Continue? (this will wipe the directory) (y/n)",
81 | Default: "n",
82 | }
83 |
84 | result, err := prompt.Run()
85 | if err != nil {
86 | utils.HandleError(err)
87 | }
88 | return strings.ToLower(result) == "y"
89 | }
90 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/create/temp_dir.go:
--------------------------------------------------------------------------------
1 | package create
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | )
7 |
8 | func createTempDir() string {
9 | osCacheDir, _ := os.UserCacheDir()
10 | tempDirPath := filepath.Join(osCacheDir, "gossr-cli")
11 | os.RemoveAll(tempDirPath)
12 | os.MkdirAll(tempDirPath, os.ModePerm)
13 |
14 | return tempDirPath
15 | }
16 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/natewong1313/go-react-ssr/gossr-cli/logger"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | // rootCmd represents the base command when called without any subcommands
11 | var RootCmd = &cobra.Command{
12 | Use: "gossr-cli",
13 | Short: "This application helps you get a go-react-ssr powered app up and running in no time.",
14 | Long: `This application helps you get a go-react-ssr powered app up and running in no time.
15 | Complete documentation is available at https://github.com/natewong1313/go-react-ssr`,
16 | }
17 |
18 | // Execute adds all child commands to the root command and sets flags appropriately.
19 | // This is called by main.main(). It only needs to happen once to the rootCmd.
20 | func Execute() {
21 | logger.Init()
22 | err := RootCmd.Execute()
23 | if err != nil {
24 | os.Exit(1)
25 | }
26 | }
27 |
28 | func init() {
29 | // Here you will define your flags and configuration settings.
30 | // Cobra supports persistent flags, which, if defined here,
31 | // will be global for your application.
32 |
33 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.test.yaml)")
34 |
35 | // Cobra also supports local flags, which will only run
36 | // when this action is called directly.
37 | }
38 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/update/check_update.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "errors"
5 | "os"
6 | "path/filepath"
7 |
8 | "github.com/natewong1313/go-react-ssr/gossr-cli/utils"
9 | )
10 |
11 | func CheckNeedsUpdate() bool {
12 | configDirPath := getConfigDir()
13 | VersionFilePath = filepath.Join(configDirPath, "version")
14 |
15 | if _, err := os.Stat(VersionFilePath); errors.Is(err, os.ErrNotExist) {
16 | createVersionFile()
17 | return false
18 | }
19 | currentVersion, err := os.ReadFile(VersionFilePath)
20 | if err != nil {
21 | utils.HandleError(err)
22 | }
23 | return string(currentVersion) != getLatestVersion()
24 | }
25 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/update/latest_version.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "io"
5 | "net/http"
6 |
7 | "github.com/buger/jsonparser"
8 | "github.com/natewong1313/go-react-ssr/gossr-cli/utils"
9 | )
10 |
11 | func getLatestVersion() string {
12 | res, err := http.Get("https://proxy.golang.org/github.com/natewong1313/go-react-ssr/@latest")
13 | if err != nil {
14 | utils.HandleError(err)
15 | }
16 | defer res.Body.Close()
17 | resBody, err := io.ReadAll(res.Body)
18 | if err != nil {
19 | utils.HandleError(err)
20 | }
21 | version, err := jsonparser.GetString(resBody, "Version")
22 | if err != nil {
23 | utils.HandleError(err)
24 | }
25 | return version
26 | }
27 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/update/update.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "os/exec"
5 |
6 | "github.com/natewong1313/go-react-ssr/gossr-cli/cmd"
7 | "github.com/natewong1313/go-react-ssr/gossr-cli/logger"
8 | "github.com/spf13/cobra"
9 | )
10 |
11 | // updateCmd represents the update command
12 | var updateCmd = &cobra.Command{
13 | Use: "update",
14 | Short: "Update the cli to the latest version",
15 | Long: "Update the cli to the latest version",
16 | Run: update,
17 | }
18 |
19 | func init() {
20 | if CheckNeedsUpdate() {
21 | cmd.RootCmd.AddCommand(updateCmd)
22 | }
23 | }
24 |
25 | func update(cmd *cobra.Command, args []string) {
26 | exec.Command("go", "install", "github.com/natewong1313/go-react-ssr/gossr-cli@latest").Run()
27 | updateVersionFile()
28 | logger.L.Info().Msg("Updated to latest version!")
29 | }
30 |
--------------------------------------------------------------------------------
/gossr-cli/cmd/update/version_file.go:
--------------------------------------------------------------------------------
1 | package update
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 |
7 | "github.com/natewong1313/go-react-ssr/gossr-cli/utils"
8 | )
9 |
10 | var VersionFilePath string
11 |
12 | func getConfigDir() string {
13 | configDir, _ := os.UserConfigDir()
14 | configDirPath := filepath.Join(configDir, "gossr-cli")
15 | os.MkdirAll(configDirPath, os.ModePerm)
16 | return configDirPath
17 | }
18 |
19 | func createVersionFile() {
20 | file, err := os.Create(VersionFilePath)
21 | if err != nil {
22 | utils.HandleError(err)
23 | }
24 | defer file.Close()
25 | file.WriteString(getLatestVersion())
26 | }
27 |
28 | func updateVersionFile() {
29 | file, err := os.OpenFile(VersionFilePath, os.O_WRONLY, 0644)
30 | if err != nil {
31 | utils.HandleError(err)
32 | }
33 | defer file.Close()
34 | file.WriteString(getLatestVersion())
35 | }
36 |
--------------------------------------------------------------------------------
/gossr-cli/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/natewong1313/go-react-ssr/gossr-cli
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/buger/jsonparser v1.1.1
7 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
8 | github.com/fatih/color v1.15.0
9 | github.com/manifoldco/promptui v0.9.0
10 | github.com/otiai10/copy v1.12.0
11 | github.com/rs/zerolog v1.31.0
12 | github.com/spf13/cobra v1.7.0
13 | )
14 |
15 | require (
16 | github.com/chzyer/readline v1.5.1 // indirect
17 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
18 | github.com/mattn/go-colorable v0.1.13 // indirect
19 | github.com/mattn/go-isatty v0.0.19 // indirect
20 | github.com/spf13/pflag v1.0.5 // indirect
21 | golang.org/x/sys v0.12.0 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/gossr-cli/go.sum:
--------------------------------------------------------------------------------
1 | github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
2 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
3 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
4 | github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
5 | github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
7 | github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
8 | github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
9 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
10 | github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
11 | github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
12 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
13 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
14 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
15 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
16 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
17 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
18 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
19 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
20 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
21 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
22 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
23 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
24 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
25 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
26 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
27 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
28 | github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=
29 | github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
30 | github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
31 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
32 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
33 | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
34 | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
35 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
36 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
37 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
38 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
39 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
40 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
41 | golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
42 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
43 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
44 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
45 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
47 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
48 |
--------------------------------------------------------------------------------
/gossr-cli/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/rs/zerolog"
7 | )
8 |
9 | var L zerolog.Logger
10 |
11 | // Init initializes a global logger instance
12 | func Init() {
13 | L = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger()
14 | }
15 |
--------------------------------------------------------------------------------
/gossr-cli/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/common-nighthawk/go-figure"
7 | "github.com/fatih/color"
8 | "github.com/natewong1313/go-react-ssr/gossr-cli/cmd"
9 | _ "github.com/natewong1313/go-react-ssr/gossr-cli/cmd/create"
10 | "github.com/natewong1313/go-react-ssr/gossr-cli/cmd/update"
11 | )
12 |
13 | func main() {
14 | art := figure.NewFigure("Go - SSR CLI", "slant", true)
15 | art.Print()
16 | fmt.Println()
17 | if update.CheckNeedsUpdate() {
18 | color.Magenta("🚨 A new version of gossr-cli is available! Run `gossr-cli update` to update. 🚨\n\n")
19 | }
20 | cmd.Execute()
21 | }
22 |
--------------------------------------------------------------------------------
/gossr-cli/utils/error.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "os"
5 | "runtime"
6 |
7 | "github.com/natewong1313/go-react-ssr/gossr-cli/logger"
8 | )
9 |
10 | func HandleError(err error) {
11 | if err.Error() == "^C" {
12 | logger.L.Info().Msg("Goodbye 👋")
13 | } else {
14 | _, filename, line, _ := runtime.Caller(1)
15 | logger.L.Error().Err(err).Msgf("An error occurred in [%s:%d]", filename, line)
16 | }
17 | os.Exit(1)
18 | }
19 |
--------------------------------------------------------------------------------
/gossr-cli/utils/path.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "io"
5 | "os"
6 | )
7 |
8 | func CheckPathExists(projectDir string) bool {
9 | if _, err := os.Stat(projectDir); os.IsNotExist(err) {
10 | return false
11 | }
12 | return true
13 | }
14 |
15 | func CheckPathEmpty(projectDir string) bool {
16 | f, err := os.Open(projectDir)
17 | if err != nil {
18 | HandleError(err)
19 | }
20 | defer f.Close()
21 |
22 | if _, err = f.Readdirnames(1); err == io.EOF {
23 | return true
24 | }
25 | return false
26 | }
27 |
--------------------------------------------------------------------------------
/hotreload.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "fmt"
5 | "github.com/fsnotify/fsnotify"
6 | "github.com/gorilla/websocket"
7 | "github.com/natewong1313/go-react-ssr/internal/utils"
8 | "github.com/rs/zerolog"
9 | "net/http"
10 | "os"
11 | "path/filepath"
12 | "strings"
13 | )
14 |
15 | type HotReload struct {
16 | engine *Engine
17 | logger zerolog.Logger
18 | connectedClients map[string][]*websocket.Conn
19 | }
20 |
21 | // newHotReload creates a new HotReload instance
22 | func newHotReload(engine *Engine) *HotReload {
23 | return &HotReload{
24 | engine: engine,
25 | logger: engine.Logger,
26 | connectedClients: make(map[string][]*websocket.Conn),
27 | }
28 | }
29 |
30 | // Start starts the hot reload server and watcher
31 | func (hr *HotReload) Start() {
32 | go hr.startServer()
33 | go hr.startWatcher()
34 | }
35 |
36 | // startServer starts the hot reload websocket server
37 | func (hr *HotReload) startServer() {
38 | hr.logger.Info().Msgf("Hot reload websocket running on port %d", hr.engine.Config.HotReloadServerPort)
39 | upgrader := websocket.Upgrader{
40 | CheckOrigin: func(r *http.Request) bool {
41 | return true
42 | },
43 | }
44 | http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
45 | ws, err := upgrader.Upgrade(w, r, nil)
46 | if err != nil {
47 | hr.logger.Err(err).Msg("Failed to upgrade websocket")
48 | return
49 | }
50 | // Client should send routeID as first message
51 | _, routeID, err := ws.ReadMessage()
52 | if err != nil {
53 | hr.logger.Err(err).Msg("Failed to read message from websocket")
54 | return
55 | }
56 | err = ws.WriteMessage(1, []byte("Connected"))
57 | if err != nil {
58 | hr.logger.Err(err).Msg("Failed to write message to websocket")
59 | return
60 | }
61 | // Add client to connectedClients
62 | hr.connectedClients[string(routeID)] = append(hr.connectedClients[string(routeID)], ws)
63 | })
64 | err := http.ListenAndServe(fmt.Sprintf(":%d", hr.engine.Config.HotReloadServerPort), nil)
65 | if err != nil {
66 | hr.logger.Err(err).Msg("Hot reload server quit unexpectedly")
67 | }
68 | }
69 |
70 | // startWatcher starts the file watcher
71 | func (hr *HotReload) startWatcher() {
72 | watcher, err := fsnotify.NewWatcher()
73 | if err != nil {
74 | hr.logger.Err(err).Msg("Failed to start watcher")
75 | return
76 | }
77 | defer watcher.Close()
78 | // Walk through all files in the frontend directory and add them to the watcher
79 | if err = filepath.Walk(hr.engine.Config.FrontendDir, func(path string, fi os.FileInfo, err error) error {
80 | if fi.Mode().IsDir() {
81 | return watcher.Add(path)
82 | }
83 | return nil
84 | }); err != nil {
85 | hr.logger.Err(err).Msg("Failed to add files in directory to watcher")
86 | return
87 | }
88 |
89 | for {
90 | select {
91 | case event := <-watcher.Events:
92 | // Watch for file created, deleted, updated, or renamed events
93 | if event.Op.String() != "CHMOD" && !strings.Contains(event.Name, "gossr-temporary") {
94 | filePath := utils.GetFullFilePath(event.Name)
95 | hr.logger.Info().Msgf("File changed: %s, reloading", filePath)
96 | // Store the routes that need to be reloaded
97 | var routeIDS []string
98 | switch {
99 | case filePath == hr.engine.Config.LayoutFilePath: // If the layout file has been updated, reload all routes
100 | routeIDS = hr.engine.CacheManager.GetAllRouteIDS()
101 | case hr.layoutCSSFileUpdated(filePath): // If the global css file has been updated, rebuild it and reload all routes
102 | if err := hr.engine.BuildLayoutCSSFile(); err != nil {
103 | hr.logger.Err(err).Msg("Failed to build global css file")
104 | continue
105 | }
106 | routeIDS = hr.engine.CacheManager.GetAllRouteIDS()
107 | case hr.needsTailwindRecompile(filePath): // If tailwind is enabled and a React file has been updated, rebuild the global css file and reload all routes
108 | if err := hr.engine.BuildLayoutCSSFile(); err != nil {
109 | hr.logger.Err(err).Msg("Failed to build global css file")
110 | continue
111 | }
112 | fallthrough
113 | default:
114 | // Get all route ids that use that file or have it as a dependency
115 | routeIDS = hr.engine.CacheManager.GetRouteIDSWithFile(filePath)
116 | }
117 | // Find any parent files that import the file that was modified and delete their cached build
118 | parentFiles := hr.engine.CacheManager.GetParentFilesFromDependency(filePath)
119 | for _, parentFile := range parentFiles {
120 | hr.engine.CacheManager.RemoveServerBuild(parentFile)
121 | hr.engine.CacheManager.RemoveClientBuild(parentFile)
122 | }
123 | // Reload any routes that import the modified file
124 | go hr.broadcastFileUpdateToClients(routeIDS)
125 |
126 | }
127 | case err := <-watcher.Errors:
128 | hr.logger.Err(err).Msg("Error watching files")
129 | }
130 | }
131 | }
132 |
133 | // layoutCSSFileUpdated checks if the layout css file has been updated
134 | func (hr *HotReload) layoutCSSFileUpdated(filePath string) bool {
135 | return utils.GetFullFilePath(filePath) == hr.engine.Config.LayoutCSSFilePath
136 | }
137 |
138 | // needsTailwindRecompile checks if the file that was updated is a React file
139 | func (hr *HotReload) needsTailwindRecompile(filePath string) bool {
140 | if hr.engine.Config.TailwindConfigPath == "" {
141 | return false
142 | }
143 | fileTypes := []string{".tsx", ".ts", ".jsx", ".js"}
144 | for _, fileType := range fileTypes {
145 | if strings.HasSuffix(filePath, fileType) {
146 | return true
147 | }
148 | }
149 | return false
150 | }
151 |
152 | // broadcastFileUpdateToClients sends a message to all connected clients to reload the page
153 | func (hr *HotReload) broadcastFileUpdateToClients(routeIDS []string) {
154 | // Iterate over each route ID
155 | for _, routeID := range routeIDS {
156 | // Find all clients listening for that route ID
157 | for i, ws := range hr.connectedClients[routeID] {
158 | // Send reload message to client
159 | err := ws.WriteMessage(1, []byte("reload"))
160 | if err != nil {
161 | // remove client if browser is closed or page changed
162 | hr.connectedClients[routeID] = append(hr.connectedClients[routeID][:i], hr.connectedClients[routeID][i+1:]...)
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/internal/cache/manager.go:
--------------------------------------------------------------------------------
1 | package cache
2 |
3 | import (
4 | "github.com/natewong1313/go-react-ssr/internal/reactbuilder"
5 | "sync"
6 | )
7 |
8 | type Manager struct {
9 | serverBuilds *serverBuilds
10 | clientBuilds *clientBuilds
11 | routeIDToParentFile *routeIDToParentFile
12 | parentFileToDependencies *parentFileToDependencies
13 | }
14 |
15 | func NewManager() *Manager {
16 | return &Manager{
17 | serverBuilds: &serverBuilds{
18 | builds: make(map[string]reactbuilder.BuildResult),
19 | lock: sync.RWMutex{},
20 | },
21 | clientBuilds: &clientBuilds{
22 | builds: make(map[string]reactbuilder.BuildResult),
23 | lock: sync.RWMutex{},
24 | },
25 | routeIDToParentFile: &routeIDToParentFile{
26 | reactFiles: make(map[string]string),
27 | lock: sync.RWMutex{},
28 | },
29 | parentFileToDependencies: &parentFileToDependencies{
30 | dependencies: make(map[string][]string),
31 | lock: sync.RWMutex{},
32 | },
33 | }
34 | }
35 |
36 | type serverBuilds struct {
37 | builds map[string]reactbuilder.BuildResult
38 | lock sync.RWMutex
39 | }
40 |
41 | func (cm *Manager) GetServerBuild(filePath string) (reactbuilder.BuildResult, bool) {
42 | cm.serverBuilds.lock.RLock()
43 | defer cm.serverBuilds.lock.RUnlock()
44 | build, ok := cm.serverBuilds.builds[filePath]
45 | return build, ok
46 | }
47 |
48 | func (cm *Manager) SetServerBuild(filePath string, build reactbuilder.BuildResult) {
49 | cm.serverBuilds.lock.Lock()
50 | defer cm.serverBuilds.lock.Unlock()
51 | cm.serverBuilds.builds[filePath] = build
52 | }
53 |
54 | func (cm *Manager) RemoveServerBuild(filePath string) {
55 | cm.serverBuilds.lock.Lock()
56 | defer cm.serverBuilds.lock.Unlock()
57 | if _, ok := cm.serverBuilds.builds[filePath]; !ok {
58 | return
59 | }
60 | delete(cm.serverBuilds.builds, filePath)
61 | }
62 |
63 | type clientBuilds struct {
64 | builds map[string]reactbuilder.BuildResult
65 | lock sync.RWMutex
66 | }
67 |
68 | func (cm *Manager) GetClientBuild(filePath string) (reactbuilder.BuildResult, bool) {
69 | cm.clientBuilds.lock.RLock()
70 | defer cm.clientBuilds.lock.RUnlock()
71 | build, ok := cm.clientBuilds.builds[filePath]
72 | return build, ok
73 | }
74 |
75 | func (cm *Manager) SetClientBuild(filePath string, build reactbuilder.BuildResult) {
76 | cm.clientBuilds.lock.Lock()
77 | defer cm.clientBuilds.lock.Unlock()
78 | cm.clientBuilds.builds[filePath] = build
79 | }
80 |
81 | func (cm *Manager) RemoveClientBuild(filePath string) {
82 | cm.clientBuilds.lock.Lock()
83 | defer cm.clientBuilds.lock.Unlock()
84 | if _, ok := cm.clientBuilds.builds[filePath]; !ok {
85 | return
86 | }
87 | delete(cm.clientBuilds.builds, filePath)
88 | }
89 |
90 | type routeIDToParentFile struct {
91 | reactFiles map[string]string
92 | lock sync.RWMutex
93 | }
94 |
95 | func (cm *Manager) SetParentFile(routeID, filePath string) {
96 | cm.routeIDToParentFile.lock.Lock()
97 | defer cm.routeIDToParentFile.lock.Unlock()
98 | cm.routeIDToParentFile.reactFiles[routeID] = filePath
99 | }
100 |
101 | func (cm *Manager) GetRouteIDSForParentFile(filePath string) []string {
102 | cm.routeIDToParentFile.lock.RLock()
103 | defer cm.routeIDToParentFile.lock.RUnlock()
104 | var routes []string
105 | for route, file := range cm.routeIDToParentFile.reactFiles {
106 | if file == filePath {
107 | routes = append(routes, route)
108 | }
109 | }
110 | return routes
111 | }
112 |
113 | func (cm *Manager) GetAllRouteIDS() []string {
114 | cm.routeIDToParentFile.lock.RLock()
115 | defer cm.routeIDToParentFile.lock.RUnlock()
116 | routes := make([]string, 0, len(cm.routeIDToParentFile.reactFiles))
117 | for route := range cm.routeIDToParentFile.reactFiles {
118 | routes = append(routes, route)
119 | }
120 | return routes
121 | }
122 |
123 | func (cm *Manager) GetRouteIDSWithFile(filePath string) []string {
124 | reactFilesWithDependency := cm.GetParentFilesFromDependency(filePath)
125 | if len(reactFilesWithDependency) == 0 {
126 | reactFilesWithDependency = []string{filePath}
127 | }
128 | var routeIDS []string
129 | for _, reactFile := range reactFilesWithDependency {
130 | routeIDS = append(routeIDS, cm.GetRouteIDSForParentFile(reactFile)...)
131 | }
132 | return routeIDS
133 | }
134 |
135 | type parentFileToDependencies struct {
136 | dependencies map[string][]string
137 | lock sync.RWMutex
138 | }
139 |
140 | func (cm *Manager) SetParentFileDependencies(filePath string, dependencies []string) {
141 | cm.parentFileToDependencies.lock.Lock()
142 | defer cm.parentFileToDependencies.lock.Unlock()
143 | cm.parentFileToDependencies.dependencies[filePath] = dependencies
144 | }
145 |
146 | func (cm *Manager) GetParentFilesFromDependency(dependencyPath string) []string {
147 | cm.parentFileToDependencies.lock.RLock()
148 | defer cm.parentFileToDependencies.lock.RUnlock()
149 | var parentFilePaths []string
150 | for parentFilePath, dependencies := range cm.parentFileToDependencies.dependencies {
151 | for _, dependency := range dependencies {
152 | if dependency == dependencyPath {
153 | parentFilePaths = append(parentFilePaths, parentFilePath)
154 | }
155 | }
156 | }
157 | return parentFilePaths
158 | }
159 |
--------------------------------------------------------------------------------
/internal/html/base_template.go:
--------------------------------------------------------------------------------
1 | package html
2 |
3 | const BaseTemplate = `
4 |
5 |
6 |
7 |
8 | {{ .Title }}
9 | {{range $k, $v := .MetaTags}} {{end}}
10 | {{range $k, $v := .OGMetaTags}} {{end}}
11 | {{range .Links}}{{end}}
12 |
13 |
16 |
17 |
18 | {{ .ServerHTML }}
19 |
26 |
33 | {{if .IsDev}}
34 |
47 | {{end}}
48 |
49 |
50 | `
51 |
--------------------------------------------------------------------------------
/internal/html/error_template.go:
--------------------------------------------------------------------------------
1 | package html
2 |
3 | const ErrorTemplate = `
4 |
5 |
6 |
7 |
8 | An error occured!
9 |
10 |
18 |
19 |
20 | An error occured
21 | {{ .Error }}
22 | {{if .IsDev}}
23 |
36 | {{end}}
37 |
38 |
39 | `
40 |
--------------------------------------------------------------------------------
/internal/html/render_html.go:
--------------------------------------------------------------------------------
1 | package html
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "html/template"
7 | "os"
8 | "runtime"
9 | "strings"
10 | )
11 |
12 | type Params struct {
13 | Title string
14 | MetaTags map[string]string
15 | OGMetaTags map[string]string
16 | Links []struct {
17 | Href string
18 | Rel string
19 | Media string
20 | Hreflang string
21 | Type string
22 | Title string
23 | }
24 | JS template.JS
25 | CSS template.CSS
26 | RouteID string
27 | IsDev bool
28 | ServerHTML template.HTML
29 | }
30 |
31 | // RenderHTMLString Renders the HTML template in internal/html with the given parameters
32 | func RenderHTMLString(params Params) []byte {
33 | params.IsDev = os.Getenv("APP_ENV") != "production"
34 | params.OGMetaTags = getOGMetaTags(params.MetaTags)
35 | params.MetaTags = getMetaTags(params.MetaTags)
36 | t := template.Must(template.New("").Parse(BaseTemplate))
37 | var output bytes.Buffer
38 | err := t.Execute(&output, params)
39 | if err != nil {
40 | return RenderError(err, params.RouteID)
41 | }
42 | return output.Bytes()
43 | }
44 |
45 | func getMetaTags(metaTags map[string]string) map[string]string {
46 | newMetaTags := make(map[string]string)
47 | for key, value := range metaTags {
48 | if !strings.HasPrefix(key, "og:") {
49 | newMetaTags[key] = value
50 | }
51 | }
52 | return newMetaTags
53 | }
54 |
55 | func getOGMetaTags(metaTags map[string]string) map[string]string {
56 | newMetaTags := make(map[string]string)
57 | for key, value := range metaTags {
58 | if strings.HasPrefix(key, "og:") {
59 | newMetaTags[key] = value
60 | }
61 | }
62 | return newMetaTags
63 | }
64 |
65 | type ErrorParams struct {
66 | Error string
67 | RouteID string
68 | IsDev bool
69 | }
70 |
71 | // RenderError Renders the error template with the given error
72 | func RenderError(e error, routeID string) []byte {
73 | t := template.Must(template.New("").Parse(ErrorTemplate))
74 | var output bytes.Buffer
75 | _, filename, line, _ := runtime.Caller(1)
76 | t.Execute(&output, ErrorParams{
77 | Error: fmt.Sprintf("%s line %d: %v", filename, line, e),
78 | RouteID: routeID,
79 | IsDev: os.Getenv("APP_ENV") != "production",
80 | })
81 | return output.Bytes()
82 | }
83 |
--------------------------------------------------------------------------------
/internal/reactbuilder/build.go:
--------------------------------------------------------------------------------
1 | package reactbuilder
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/buger/jsonparser"
9 | esbuildApi "github.com/evanw/esbuild/pkg/api"
10 | "github.com/natewong1313/go-react-ssr/internal/utils"
11 | )
12 |
13 | var loaders = map[string]esbuildApi.Loader{
14 | ".png": esbuildApi.LoaderFile,
15 | ".svg": esbuildApi.LoaderFile,
16 | ".jpg": esbuildApi.LoaderFile,
17 | ".jpeg": esbuildApi.LoaderFile,
18 | ".gif": esbuildApi.LoaderFile,
19 | ".bmp": esbuildApi.LoaderFile,
20 | ".woff2": esbuildApi.LoaderFile,
21 | ".woff": esbuildApi.LoaderFile,
22 | ".ttf": esbuildApi.LoaderFile,
23 | ".eot": esbuildApi.LoaderFile,
24 | }
25 |
26 | var textEncoderPolyfill = `function TextEncoder(){}TextEncoder.prototype.encode=function(string){var octets=[];var length=string.length;var i=0;while(i>c));c-=6;while(c>=0){octets.push(0x80|((codePoint>>c)&0x3F));c-=6}i+=codePoint>=0x10000?2:1}return octets};function TextDecoder(){}TextDecoder.prototype.decode=function(octets){var string="";var i=0;while(i0){var k=0;while(k 0 {
84 | fileLocation := "unknown"
85 | lineNum := "unknown"
86 | if result.Errors[0].Location != nil {
87 | fileLocation = result.Errors[0].Location.File
88 | lineNum = result.Errors[0].Location.LineText
89 | }
90 | return BuildResult{}, fmt.Errorf("%s
in %s
at %s", result.Errors[0].Text, fileLocation, lineNum)
91 | }
92 |
93 | var br BuildResult
94 | for _, file := range result.OutputFiles {
95 | if strings.HasSuffix(file.Path, "stdin.js") {
96 | br.JS = string(file.Contents)
97 | } else if strings.HasSuffix(file.Path, "stdin.css") {
98 | br.CSS = string(file.Contents)
99 | }
100 | }
101 | if isClient {
102 | br.Dependencies = getDependencyPathsFromMetafile(result.Metafile)
103 | }
104 | return br, nil
105 | }
106 |
107 | // getDependencyPathsFromMetafile parses dependencies from esbuild metafile and returns the paths of the dependencies
108 | func getDependencyPathsFromMetafile(metafile string) []string {
109 | var dependencyPaths []string
110 | // Parse the metafile and get the paths of the dependencies
111 | // Ignore dependencies in node_modules
112 | err := jsonparser.ObjectEach([]byte(metafile), func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
113 | if !strings.Contains(string(key), "/node_modules/") {
114 | dependencyPaths = append(dependencyPaths, utils.GetFullFilePath(string(key)))
115 | }
116 | return nil
117 | }, "inputs")
118 | if err != nil {
119 | return nil
120 | }
121 | return dependencyPaths
122 | }
123 |
--------------------------------------------------------------------------------
/internal/reactbuilder/contents.go:
--------------------------------------------------------------------------------
1 | package reactbuilder
2 |
3 | import (
4 | "strings"
5 | "text/template"
6 | )
7 |
8 | var baseTemplate = `
9 | import React from "react";
10 | {{range $import := .Imports}}{{$import}} {{end}}
11 | import App from "{{ .FilePath }}";
12 | {{ if .SuppressConsoleLog }}console.log = () => {};{{ end }}
13 | {{ .RenderFunction }}`
14 | var serverRenderFunction = `renderToString();`
15 | var serverRenderFunctionWithLayout = `renderToString();`
16 | var clientRenderFunction = `hydrateRoot(document.getElementById("root"), );`
17 | var clientRenderFunctionWithLayout = `hydrateRoot(document.getElementById("root"), );`
18 |
19 | func buildWithTemplate(buildTemplate string, params map[string]interface{}) (string, error) {
20 | templ, err := template.New("buildTemplate").Parse(buildTemplate)
21 | if err != nil {
22 | return "", err
23 | }
24 | var out strings.Builder
25 | err = templ.Execute(&out, params)
26 | if err != nil {
27 | return "", err
28 | }
29 | return out.String(), nil
30 | }
31 |
32 | func GenerateServerBuildContents(imports []string, filePath string, useLayout bool) (string, error) {
33 | imports = append(imports, `import { renderToString } from "react-dom/server.browser";`)
34 | params := map[string]interface{}{
35 | "Imports": imports,
36 | "FilePath": filePath,
37 | "RenderFunction": serverRenderFunction,
38 | "SuppressConsoleLog": true,
39 | }
40 | if useLayout {
41 | params["RenderFunction"] = serverRenderFunctionWithLayout
42 | }
43 | return buildWithTemplate(baseTemplate, params)
44 | }
45 |
46 | func GenerateClientBuildContents(imports []string, filePath string, useLayout bool) (string, error) {
47 | imports = append(imports, `import { hydrateRoot } from "react-dom/client";`)
48 | params := map[string]interface{}{
49 | "Imports": imports,
50 | "FilePath": filePath,
51 | "RenderFunction": clientRenderFunction,
52 | }
53 | if useLayout {
54 | params["RenderFunction"] = clientRenderFunctionWithLayout
55 | }
56 | return buildWithTemplate(baseTemplate, params)
57 | }
58 |
--------------------------------------------------------------------------------
/internal/typeconverter/create_temp_file.go:
--------------------------------------------------------------------------------
1 | package typeconverter
2 |
3 | import (
4 | "os"
5 | "os/exec"
6 | "path/filepath"
7 | "strings"
8 | "text/template"
9 |
10 | "github.com/natewong1313/go-react-ssr/internal/utils"
11 | )
12 |
13 | // https://github.com/tkrajina/typescriptify-golang-structs/blob/master/tscriptify/main.go#L139
14 | func createTemporaryFile(structsFilePath, generatedTypesPath, cacheDir string, structNames []string) (string, error) {
15 | temporaryFilePath := filepath.ToSlash(filepath.Join(cacheDir, "generator.go"))
16 | file, err := os.Create(temporaryFilePath)
17 | if err != nil {
18 | return temporaryFilePath, err
19 | }
20 | defer file.Close()
21 |
22 | t := template.Must(template.New("").Parse(TEMPLATE))
23 |
24 | structsArr := make([]string, 0)
25 | for _, structName := range structNames {
26 | structName = strings.TrimSpace(structName)
27 | if len(structName) > 0 {
28 | structsArr = append(structsArr, "m."+structName)
29 | }
30 | }
31 |
32 | var params TemplateParams
33 | params.Structs = structsArr
34 |
35 | params.ModuleName, err = getModuleName(structsFilePath)
36 | if err != nil {
37 | return temporaryFilePath, err
38 | }
39 | params.Interface = true
40 | params.TargetFile = utils.GetFullFilePath(generatedTypesPath)
41 |
42 | err = t.Execute(file, params)
43 | if err != nil {
44 | return temporaryFilePath, err
45 | }
46 |
47 | return temporaryFilePath, nil
48 | }
49 |
50 | // getModuleName gets the module name of the props structs file
51 | func getModuleName(propsStructsPath string) (string, error) {
52 | dir := filepath.ToSlash(filepath.Dir(utils.GetFullFilePath(propsStructsPath)))
53 | cmd := exec.Command("go", "list")
54 | cmd.Dir = dir
55 | output, err := cmd.CombinedOutput()
56 | if err != nil {
57 | return "", err
58 | }
59 | return strings.TrimSpace(string(output)), nil
60 | }
61 |
--------------------------------------------------------------------------------
/internal/typeconverter/get_structs.go:
--------------------------------------------------------------------------------
1 | package typeconverter
2 |
3 | import (
4 | "go/ast"
5 | "go/parser"
6 | "go/token"
7 | "os"
8 | )
9 |
10 | // https://gist.github.com/LukaGiorgadze/570a89a5c3c6d006120da8c29f6684ee
11 | func getStructNamesFromFile(filePath string) (structs []string, err error) {
12 | data, err := os.ReadFile(filePath)
13 | if err != nil {
14 | return
15 | }
16 | fset := token.NewFileSet()
17 | f, err := parser.ParseFile(fset, "", string(data), 0)
18 | if err != nil {
19 | return
20 | }
21 | for _, decl := range f.Decls {
22 | gen, ok := decl.(*ast.GenDecl)
23 | if !ok || gen.Tok != token.TYPE {
24 | continue
25 | }
26 | for _, spec := range gen.Specs {
27 | ts, ok := spec.(*ast.TypeSpec)
28 | if !ok {
29 | continue
30 | }
31 | structs = append(structs, ts.Name.Name)
32 | }
33 | }
34 | return
35 | }
36 |
--------------------------------------------------------------------------------
/internal/typeconverter/models.go:
--------------------------------------------------------------------------------
1 | package typeconverter
2 |
3 | import "strings"
4 |
5 | const TEMPLATE = `package main
6 |
7 | import (
8 | m "{{ .ModuleName }}"
9 | "github.com/tkrajina/typescriptify-golang-structs/typescriptify"
10 | )
11 |
12 | func main() {
13 | t := typescriptify.New()
14 | t.CreateInterface = {{ .Interface }}
15 | t.BackupDir = ""
16 | {{ range .Structs }} t.Add({{ . }}{})
17 | {{ end }}
18 | {{ range .CustomImports }} t.AddImport("{{ . }}")
19 | {{ end }}
20 | err := t.ConvertToFile(` + "`{{ .TargetFile }}`" + `)
21 | if err != nil {
22 | panic(err.Error())
23 | }
24 | }`
25 |
26 | type TemplateParams struct {
27 | ModuleName string
28 | TargetFile string
29 | Structs []string
30 | InitParams map[string]interface{}
31 | CustomImports arrayImports
32 | Interface bool
33 | Verbose bool
34 | }
35 |
36 | type arrayImports []string
37 |
38 | func (i *arrayImports) String() string {
39 | return "// custom imports:\n\n" + strings.Join(*i, "\n")
40 | }
41 |
42 | func (i *arrayImports) Set(value string) error {
43 | *i = append(*i, value)
44 | return nil
45 | }
46 |
--------------------------------------------------------------------------------
/internal/typeconverter/start.go:
--------------------------------------------------------------------------------
1 | package typeconverter
2 |
3 | import (
4 | "github.com/natewong1313/go-react-ssr/internal/utils"
5 | "os/exec"
6 |
7 | _ "github.com/tkrajina/typescriptify-golang-structs/typescriptify"
8 | )
9 |
10 | // Start starts the type converter
11 | // It gets the name of structs in PropsStructsPath and generates a temporary file to run the type converter
12 | func Start(structsFilePath, generatedTypesPath string) error {
13 | // Get struct names from file
14 | structNames, err := getStructNamesFromFile(structsFilePath)
15 | if err != nil {
16 | return err
17 | }
18 | // Create a folder for the temporary generator files
19 | cacheDir, err := utils.GetTypeConverterCacheDir()
20 | if err != nil {
21 | return err
22 | }
23 | // Create the generator file
24 | temporaryFilePath, err := createTemporaryFile(structsFilePath, generatedTypesPath, cacheDir, structNames)
25 | if err != nil {
26 | return err
27 | }
28 |
29 | // Run the file
30 | cmd := exec.Command("go", "run", temporaryFilePath)
31 | _, err = cmd.CombinedOutput()
32 | if err != nil {
33 | return err
34 | }
35 | return nil
36 | }
37 |
--------------------------------------------------------------------------------
/internal/utils/cachedir.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | )
7 |
8 | func CleanCacheDirectories() {
9 | cacheDir, err := createCacheDirIfNotExists()
10 | if err != nil {
11 | return
12 | }
13 | typeConverterCacheDir, _ := GetTypeConverterCacheDir()
14 | if err := os.RemoveAll(typeConverterCacheDir); err != nil {
15 | return
16 | }
17 | cssCacheDir, _ := GetCSSCacheDir()
18 | if err := os.RemoveAll(cssCacheDir); err != nil {
19 | return
20 | }
21 | // Remove GetServerBuildCacheDir
22 | if err := os.RemoveAll(filepath.Join(cacheDir, "builds")); err != nil {
23 | return
24 | }
25 | }
26 |
27 | // createCacheDirIfNotExists creates the user cache directory if it doesn't exist
28 | func createCacheDirIfNotExists() (string, error) {
29 | userCacheDir, err := os.UserCacheDir()
30 | if err != nil {
31 | return "", err
32 | }
33 | gossrCacheDirPath := filepath.Join(userCacheDir, "gossr")
34 | err = os.MkdirAll(gossrCacheDirPath, os.ModePerm)
35 | return gossrCacheDirPath, err
36 | }
37 |
38 | // GetTypeConverterCacheDir returns the path to the type converter cache directory
39 | func GetTypeConverterCacheDir() (string, error) {
40 | cacheDir, err := createCacheDirIfNotExists()
41 | if err != nil {
42 | return "", err
43 | }
44 | typeConverterCacheDir := filepath.Join(cacheDir, "typeconverter")
45 | err = os.MkdirAll(typeConverterCacheDir, os.ModePerm)
46 | return typeConverterCacheDir, err
47 | }
48 |
49 | // GetServerBuildCacheDir returns the path to the server build cache directory for the given route
50 | func GetServerBuildCacheDir(fileName string) (string, error) {
51 | cacheDir, err := createCacheDirIfNotExists()
52 | if err != nil {
53 | return "", err
54 | }
55 | serverBuildCacheDir := filepath.Join(cacheDir, "builds")
56 | err = os.MkdirAll(serverBuildCacheDir, os.ModePerm)
57 | if err != nil {
58 | return "", err
59 | }
60 | routeCacheDir := filepath.Join(serverBuildCacheDir, fileName)
61 | err = os.MkdirAll(routeCacheDir, os.ModePerm)
62 | return routeCacheDir, err
63 | }
64 |
65 | // GetCSSCacheDir returns the path to the server build cache directory for the given route
66 | func GetCSSCacheDir() (string, error) {
67 | cacheDir, err := createCacheDirIfNotExists()
68 | if err != nil {
69 | return "", err
70 | }
71 | cssCacheDir := filepath.Join(cacheDir, "css_builds")
72 | err = os.MkdirAll(cssCacheDir, os.ModePerm)
73 | return cssCacheDir, err
74 | }
75 |
76 | // GetTailwindExecutableDir returns the path to the tailwind executable directory
77 | func GetTailwindExecutableDir() (string, error) {
78 | cacheDir, err := createCacheDirIfNotExists()
79 | if err != nil {
80 | return "", err
81 | }
82 | tailwindCacheDir := filepath.Join(cacheDir, "tailwind")
83 | err = os.MkdirAll(tailwindCacheDir, os.ModePerm)
84 | return tailwindCacheDir, err
85 | }
86 |
--------------------------------------------------------------------------------
/internal/utils/filepath.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "path/filepath"
4 |
5 | // Returns the absolute path of the file without returning an error
6 | func GetFullFilePath(filePath string) string {
7 | fp, _ := filepath.Abs(filePath)
8 | return filepath.ToSlash(fp)
9 | }
10 |
--------------------------------------------------------------------------------
/render.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/natewong1313/go-react-ssr/internal/html"
7 | "github.com/natewong1313/go-react-ssr/internal/utils"
8 | "github.com/rs/zerolog"
9 | "html/template"
10 | "os"
11 | "path/filepath"
12 | "runtime"
13 | )
14 |
15 | // RenderConfig is the config for rendering a route
16 | type RenderConfig struct {
17 | File string
18 | Title string
19 | MetaTags map[string]string
20 | Props interface{}
21 | }
22 |
23 | // RenderRoute renders a route to html
24 | func (engine *Engine) RenderRoute(renderConfig RenderConfig) []byte {
25 | // routeID is the program counter of the caller
26 | pc, _, _, _ := runtime.Caller(1)
27 | routeID := fmt.Sprint(pc)
28 |
29 | props, err := propsToString(renderConfig.Props)
30 | if err != nil {
31 | return html.RenderError(err, routeID)
32 | }
33 | task := renderTask{
34 | engine: engine,
35 | logger: zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger(),
36 | routeID: routeID,
37 | props: props,
38 | filePath: filepath.ToSlash(utils.GetFullFilePath(engine.Config.FrontendDir + "/" + renderConfig.File)),
39 | config: renderConfig,
40 | }
41 | renderedHTML, css, js, err := task.Start()
42 | if err != nil {
43 | return html.RenderError(err, task.routeID)
44 | }
45 | return html.RenderHTMLString(html.Params{
46 | Title: renderConfig.Title,
47 | MetaTags: renderConfig.MetaTags,
48 | JS: template.JS(js),
49 | CSS: template.CSS(css),
50 | RouteID: task.routeID,
51 | ServerHTML: template.HTML(renderedHTML),
52 | })
53 | }
54 |
55 | // Convert props to JSON string, or set to null if no props are passed
56 | func propsToString(props interface{}) (string, error) {
57 | if props != nil {
58 | propsJSON, err := json.Marshal(props)
59 | return string(propsJSON), err
60 | }
61 | return "null", nil
62 | }
63 |
--------------------------------------------------------------------------------
/rendertask.go:
--------------------------------------------------------------------------------
1 | package go_ssr
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/buke/quickjs-go"
7 | "github.com/natewong1313/go-react-ssr/internal/reactbuilder"
8 | "github.com/rs/zerolog"
9 | )
10 |
11 | type renderTask struct {
12 | engine *Engine
13 | logger zerolog.Logger
14 | routeID string
15 | filePath string
16 | props string
17 | config RenderConfig
18 | serverRenderResult chan serverRenderResult
19 | clientRenderResult chan clientRenderResult
20 | }
21 |
22 | type serverRenderResult struct {
23 | html string
24 | css string
25 | err error
26 | }
27 |
28 | type clientRenderResult struct {
29 | js string
30 | dependencies []string
31 | err error
32 | }
33 |
34 | // Start starts the render task, returns the rendered html, css, and js for hydration
35 | func (rt *renderTask) Start() (string, string, string, error) {
36 | rt.serverRenderResult = make(chan serverRenderResult)
37 | rt.clientRenderResult = make(chan clientRenderResult)
38 | // Assigns the parent file to the routeID so that the cache can be invalidated when the parent file changes
39 | rt.engine.CacheManager.SetParentFile(rt.routeID, rt.filePath)
40 |
41 | // Render for server and client concurrently
42 | go rt.doRender("server")
43 | go rt.doRender("client")
44 |
45 | // Wait for both to finish
46 | srResult := <-rt.serverRenderResult
47 | if srResult.err != nil {
48 | rt.logger.Error().Err(srResult.err).Msg("Failed to build for server")
49 | return "", "", "", srResult.err
50 | }
51 | crResult := <-rt.clientRenderResult
52 | if crResult.err != nil {
53 | rt.logger.Error().Err(crResult.err).Msg("Failed to build for client")
54 | return "", "", "", crResult.err
55 | }
56 |
57 | // Set the parent file dependencies so that the cache can be invalidated a dependency changes
58 | go rt.engine.CacheManager.SetParentFileDependencies(rt.filePath, crResult.dependencies)
59 | return srResult.html, srResult.css, crResult.js, nil
60 | }
61 |
62 | func (rt *renderTask) doRender(buildType string) {
63 | // Check if the build is in the cache
64 | build, buildFound := rt.getBuildFromCache(buildType)
65 | if !buildFound {
66 | // Build the file if it's not in the cache
67 | newBuild, err := rt.buildFile(buildType)
68 | if err != nil {
69 | rt.handleBuildError(err, buildType)
70 | return
71 | }
72 | rt.updateBuildCache(newBuild, buildType)
73 | build = newBuild
74 | }
75 | // JS is built without props so that the props can be injected into cached JS builds
76 | js := injectProps(build.JS, rt.props)
77 | if buildType == "server" {
78 | // Then call that file with node to get the rendered HTML
79 | renderedHTML, err := renderReactToHTMLNew(js)
80 | rt.serverRenderResult <- serverRenderResult{html: renderedHTML, css: build.CSS, err: err}
81 | } else {
82 | rt.clientRenderResult <- clientRenderResult{js: js, dependencies: build.Dependencies}
83 | }
84 | }
85 |
86 | // getBuild returns the build from the cache if it exists
87 | func (rt *renderTask) getBuildFromCache(buildType string) (reactbuilder.BuildResult, bool) {
88 | if buildType == "server" {
89 | return rt.engine.CacheManager.GetServerBuild(rt.filePath)
90 | } else {
91 | return rt.engine.CacheManager.GetClientBuild(rt.filePath)
92 | }
93 | }
94 |
95 | // buildFile gets the contents of the file to be built and builds it with reactbuilder
96 | func (rt *renderTask) buildFile(buildType string) (reactbuilder.BuildResult, error) {
97 | buildContents, err := rt.getBuildContents(buildType)
98 | if err != nil {
99 | return reactbuilder.BuildResult{}, err
100 | }
101 | if buildType == "server" {
102 | return reactbuilder.BuildServer(buildContents, rt.engine.Config.FrontendDir, rt.engine.Config.AssetRoute)
103 | } else {
104 | return reactbuilder.BuildClient(buildContents, rt.engine.Config.FrontendDir, rt.engine.Config.AssetRoute)
105 | }
106 | }
107 |
108 | // getBuildContents gets the required imports based on the config and returns the contents to be built with reactbuilder
109 | func (rt *renderTask) getBuildContents(buildType string) (string, error) {
110 | var imports []string
111 | if rt.engine.CachedLayoutCSSFilePath != "" {
112 | imports = append(imports, fmt.Sprintf(`import "%s";`, rt.engine.CachedLayoutCSSFilePath))
113 | }
114 | if rt.engine.Config.LayoutFilePath != "" {
115 | imports = append(imports, fmt.Sprintf(`import Layout from "%s";`, rt.engine.Config.LayoutFilePath))
116 | }
117 | if buildType == "server" {
118 | return reactbuilder.GenerateServerBuildContents(imports, rt.filePath, rt.engine.Config.LayoutFilePath != "")
119 | } else {
120 | return reactbuilder.GenerateClientBuildContents(imports, rt.filePath, rt.engine.Config.LayoutFilePath != "")
121 | }
122 | }
123 |
124 | // handleBuildError handles the error from building the file and sends it to the appropriate channel
125 | func (rt *renderTask) handleBuildError(err error, buildType string) {
126 | if buildType == "server" {
127 | rt.serverRenderResult <- serverRenderResult{err: err}
128 | } else {
129 | rt.clientRenderResult <- clientRenderResult{err: err}
130 | }
131 | }
132 |
133 | // updateBuildCache updates the cache with the new build
134 | func (rt *renderTask) updateBuildCache(build reactbuilder.BuildResult, buildType string) {
135 | if buildType == "server" {
136 | rt.engine.CacheManager.SetServerBuild(rt.filePath, build)
137 | } else {
138 | rt.engine.CacheManager.SetClientBuild(rt.filePath, build)
139 | }
140 | }
141 |
142 | // injectProps injects the props into the already compiled JS
143 | func injectProps(compiledJS, props string) string {
144 | return fmt.Sprintf(`var props = %s; %s`, props, compiledJS)
145 | }
146 |
147 | // renderReactToHTML uses node to execute the server js file which outputs the rendered HTML
148 | func renderReactToHTMLNew(js string) (string, error) {
149 | rt := quickjs.NewRuntime()
150 | defer rt.Close()
151 | ctx := rt.NewContext()
152 | defer ctx.Close()
153 | res, err := ctx.Eval(js)
154 | if err != nil {
155 | return "", err
156 | }
157 | return res.String(), nil
158 | }
159 |
--------------------------------------------------------------------------------