Skip to content

Commit

Permalink
Adding react client vite (#321)
Browse files Browse the repository at this point in the history
  • Loading branch information
Melkeydev authored Nov 17, 2024
1 parent 9da39fa commit fca0244
Show file tree
Hide file tree
Showing 35 changed files with 826 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/generate-linter-advanced.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
framework: [chi, gin, fiber, gorilla/mux, httprouter, standard-library, echo]
driver: [postgres]
git: [commit]
advanced: [htmx, githubaction, websocket, tailwind, docker]
advanced: [htmx, githubaction, websocket, tailwind, docker, react]

runs-on: ubuntu-latest
steps:
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ gives the option to integrate with one of the more popular Go frameworks (and th
- Integrate with a popular frameworks
- Focus on the actual code of your application


## Table of Contents

- [Install](#install)
Expand Down Expand Up @@ -113,8 +112,9 @@ You can now use the `--advanced` flag when running the `create` command to get a
- [Websocket](https://pkg.go.dev/github.com/coder/websocket) sets up a websocket endpoint
- [Tailwind](https://tailwindcss.com/) css framework
- Docker configuration for go project
- [React](https://react.dev/) frontend written in TypeScript, including an example fetch request to the backend

Note: selecting tailwind option automatically selects htmx.
Note: Selecting Tailwind option will automatically select HTMX unless React is explicitly selected

<a id="blueprint-ui"></a>

Expand Down Expand Up @@ -155,40 +155,52 @@ go-blueprint create --advanced
Advanced features can be enabled using the `--feature` flag along with the `--advanced` flag.

HTMX:

```bash
go-blueprint create --advanced --feature htmx
```

CI/CD workflow:

```bash
go-blueprint create --advanced --feature githubaction
```

Websocket:

```bash
go-blueprint create --advanced --feature websocket
```

Tailwind:

```bash
go-blueprint create --advanced --feature tailwind
```

Docker:

```bash
go-blueprint create --advanced --feature docker
```

React:

```bash
go-blueprint create --advanced --feature react
```

Or all features at once:

```bash
go-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature docker -git commit
go-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature docker -git commit --feature react
```

<p align="center">
<img src="./public/blueprint_advanced.png" alt="Advanced Options" width="800"/>
</p>

**Visit [documentation](https://docs.go-blueprint.dev) to learn more about blueprint and its features.**
**Visit [documentation](https://docs.go-blueprint.dev) to learn more about blueprint and its features.**

<a id="github-stats"></a>

Expand Down
9 changes: 9 additions & 0 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,22 @@ var createCmd = &cobra.Command{
fmt.Println(endingMsgStyle.Render("\nNext steps:"))
fmt.Println(endingMsgStyle.Render(fmt.Sprintf("• cd into the newly created project with: `cd %s`\n", utils.GetRootDir(project.ProjectName))))

if options.Advanced.Choices["React"] {
options.Advanced.Choices["Htmx"] = false
options.Advanced.Choices["Tailwind"] = false
fmt.Println(endingMsgStyle.Render("• cd into frontend\n"))
fmt.Println(endingMsgStyle.Render("• npm install\n"))
fmt.Println(endingMsgStyle.Render("• npm run dev\n"))
}

if options.Advanced.Choices["Tailwind"] {
options.Advanced.Choices["Htmx"] = true
fmt.Println(endingMsgStyle.Render("• Install the tailwind standalone cli if you haven't already, grab the executable for your platform from the latest release on GitHub\n"))
fmt.Println(endingMsgStyle.Render("• More info about the Tailwind CLI: https://tailwindcss.com/blog/standalone-cli\n"))
}

if options.Advanced.Choices["Htmx"] {
options.Advanced.Choices["react"] = false
fmt.Println(endingMsgStyle.Render("• Install the templ cli if you haven't already by running `go install github.com/a-h/templ/cmd/templ@latest`\n"))
fmt.Println(endingMsgStyle.Render("• Generate templ function files by running `templ generate`\n"))
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/flags/advancedFeatures.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ const (
GoProjectWorkflow string = "githubaction"
Websocket string = "websocket"
Tailwind string = "tailwind"
React string = "react"
Docker string = "docker"
)

var AllowedAdvancedFeatures = []string{string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Docker)}
var AllowedAdvancedFeatures = []string{string(React), string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Docker)}

func (f AdvancedFeatures) String() string {
return strings.Join(f, ",")
Expand Down
118 changes: 118 additions & 0 deletions cmd/program/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -384,6 +385,17 @@ func (p *Project) CreateMainFile() error {
return err
}

if p.AdvancedOptions[string(flags.React)] {
// deselect htmx option automatically since react is selected
p.AdvancedOptions[string(flags.Htmx)] = false
if err := p.CreateViteReactProject(projectPath); err != nil {
return fmt.Errorf("failed to set up React project: %w", err)
}

// if everything went smoothly, remove tailwing flag option
p.AdvancedOptions[string(flags.Tailwind)] = false
}

if p.AdvancedOptions[string(flags.Tailwind)] {
// select htmx option automatically since tailwind is selected
p.AdvancedOptions[string(flags.Htmx)] = true
Expand Down Expand Up @@ -769,6 +781,104 @@ func (p *Project) CreateFileWithInjection(pathToCreate string, projectPath strin
return nil
}

func (p *Project) CreateViteReactProject(projectPath string) error {
if err := checkNpmInstalled(); err != nil {
return err
}

originalDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
defer func() {
if err := os.Chdir(originalDir); err != nil {
fmt.Fprintf(os.Stderr, "failed to change back to original directory: %v\n", err)
}
}()

// change into the project directory to run vite command
err = os.Chdir(projectPath)
if err != nil {
fmt.Println("failed to change into project directory: %w", err)
}

// the interactive vite command will not work as we can't interact with it
fmt.Println("Installing create-vite...")
cmd := exec.Command("npm", "create", "vite@latest", "frontend", "--", "--template", "react-ts")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to use create-vite: %w", err)
}

frontendPath := filepath.Join(projectPath, "frontend")
if err := os.MkdirAll(frontendPath, 0755); err != nil {
return fmt.Errorf("failed to create frontend directory: %w", err)
}

if err := os.Chdir(frontendPath); err != nil {
return fmt.Errorf("failed to change to frontend directory: %w", err)
}

srcDir := filepath.Join(frontendPath, "src")
if err := os.MkdirAll(srcDir, 0755); err != nil {
return fmt.Errorf("failed to create src directory: %w", err)
}

if err := os.WriteFile(filepath.Join(srcDir, "App.tsx"), advanced.ReactAppfile(), 0644); err != nil {
return fmt.Errorf("failed to write App.tsx template: %w", err)
}

// Handle Tailwind configuration if selected
if p.AdvancedOptions[string(flags.Tailwind)] {
fmt.Println("Tailwind selected. Configuring with React...")
cmd := exec.Command("npm", "install", "-D", "tailwindcss", "postcss", "autoprefixer")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to install Tailwind: %w", err)
}
cmd = exec.Command("npx", "tailwindcss", "init", "-p")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to initialize Tailwind: %w", err)
}

// use the tailwind config file
err = os.WriteFile("tailwind.config.js", advanced.ReactTailwindConfigTemplate(), 0644)
if err != nil {
return fmt.Errorf("failed to write tailwind config: %w", err)
}

srcDir := filepath.Join(frontendPath, "src")
if err := os.MkdirAll(srcDir, 0755); err != nil {
return fmt.Errorf("failed to create src directory: %w", err)
}

err = os.WriteFile(filepath.Join(srcDir, "index.css"), advanced.InputCssTemplateReact(), 0644)
if err != nil {
return fmt.Errorf("failed to update index.css: %w", err)
}

if err := os.WriteFile(filepath.Join(srcDir, "App.tsx"), advanced.ReactTailwindAppfile(), 0644); err != nil {
return fmt.Errorf("failed to write App.tsx template: %w", err)
}

if err := os.Remove(filepath.Join(srcDir, "App.css")); err != nil {
// Don't return error if file doesn't exist
if !os.IsNotExist(err) {
return fmt.Errorf("failed to remove App.css: %w", err)
}
}

// set to false to not re-do in next step
p.AdvancedOptions[string(flags.Tailwind)] = false
}

return nil
}

func (p *Project) CreateHtmxTemplates() {
routesPlaceHolder := ""
importsPlaceHolder := ""
Expand Down Expand Up @@ -827,3 +937,11 @@ func (p *Project) CreateWebsocketImports(appDir string) {
newImports := strings.Join([]string{string(p.AdvancedTemplates.TemplateImports), importBuffer.String()}, "\n")
p.AdvancedTemplates.TemplateImports = newImports
}

func checkNpmInstalled() error {
cmd := exec.Command("npm", "--version")
if err := cmd.Run(); err != nil {
return fmt.Errorf("npm is not installed: %w", err)
}
return nil
}
11 changes: 0 additions & 11 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ import (
"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "go-blueprint",
Short: "A program to spin up a quick Go project using a popular framework",
Long: `Go Blueprint is a CLI tool that allows users to spin up a Go project with the corresponding structure seamlessly.
It also gives the option to integrate with one of the more popular Go frameworks!`,
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
Expand All @@ -27,14 +24,6 @@ func Execute() {
}

func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.

// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-blueprint.yaml)")

// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.AddCommand(versionCmd)
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
9 changes: 7 additions & 2 deletions cmd/steps/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,15 @@ func InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps
StepName: "Advanced Features",
Headers: "Which advanced features do you want?",
Options: []Item{
{
Flag: "React",
Title: "React",
Desc: "Use Vite to spin up a React project in TypeScript. This disables selecting HTMX/Templ",
},
{
Flag: "Htmx",
Title: "HTMX/Templ",
Desc: "Add starter HTMX and Templ files.",
Desc: "Add starter HTMX and Templ files. This disables selecting React",
},
{
Flag: "GitHubAction",
Expand All @@ -111,7 +116,7 @@ func InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps
{
Flag: "Tailwind",
Title: "TailwindCSS",
Desc: "A utility-first CSS framework (selecting this will automatically add HTMX)",
Desc: "A utility-first CSS framework (selecting this will automatically add HTMX unless React is specified)",
},
{
Flag: "Docker",
Expand Down
14 changes: 14 additions & 0 deletions cmd/template/advanced/files/docker/docker_compose.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@ services:
BLUEPRINT_DB_URL: ${BLUEPRINT_DB_URL}
volumes:
- sqlite_bp:/app/db
{{- end }}
{{- if .AdvancedOptions.react }}
frontend:
build:
context: .
dockerfile: Dockerfile
target: frontend
restart: unless-stopped
ports:
- 5173:5173
depends_on:
- app
{{- end }}

{{- if and (.AdvancedOptions.docker) (eq .DBDriver "sqlite") }}
volumes:
sqlite_bp:
{{- end }}
16 changes: 16 additions & 0 deletions cmd/template/advanced/files/docker/dockerfile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,19 @@ WORKDIR /app
COPY --from=build /app/main /app/main
EXPOSE ${PORT}
CMD ["./main"]

{{ if .AdvancedOptions.react}}
FROM node:20 AS frontend_builder
WORKDIR /frontend

COPY frontend/package*.json ./
RUN npm install
COPY frontend/. .
RUN npm run build

FROM node:23-slim AS frontend
RUN npm install -g serve
COPY --from=frontend_builder /frontend/dist /app/dist
EXPOSE 5173
CMD ["serve", "-s", "/app/dist", "-l", "5173"]
{{- end}}
Loading

0 comments on commit fca0244

Please sign in to comment.