diff --git a/.air.toml b/.air.toml index 987e76e..305f2e9 100644 --- a/.air.toml +++ b/.air.toml @@ -1,13 +1,13 @@ root = "." testdata_dir = "testdata" -tmp_dir = "tmp" +tmp_dir = "bin" [build] args_bin = ["-port=8080"] bin = "./tmp/main" - cmd = "go generate ./... && go build -o ./tmp/main ." + cmd = "go generate ./frontend/generate_dev.go && go build -o ./tmp/main ." delay = 1000 - exclude_dir = ["assets", "tmp", "vendor", "testdata", "frontend/node_modules"] + exclude_dir = ["assets", "bin", "vendor", "testdata", "frontend/node_modules"] exclude_file = [] exclude_regex = ["_test.go"] exclude_unchanged = false diff --git a/.gitignore b/.gitignore index e7ca334..e4cfdea 100644 --- a/.gitignore +++ b/.gitignore @@ -30,5 +30,5 @@ assets/ .DS_Store Thumbs.db -# For air temp files -tmp/ +# For release binaries and air temp files +bin/ diff --git a/Makefile b/Makefile index fa833bd..d4840ef 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,29 @@ -.PHONY: generate build run dev clean install-air +.PHONY: generate generate-dev generate-prod build run dev clean install-air KO_DOCKER_REPO?=ttl.sh/jason -# Generate frontend assets -generate: - go generate ./... +# Generate frontend assets (development mode) +generate-dev: + go generate ./frontend/generate_dev.go -# Build the Go binary -build: generate - go build -o gojs . +# Generate frontend assets (production mode) +generate-prod: + go generate ./frontend/generate_prod.go + +# Build the Go binary (production) +release: generate-prod + go build -o bin/gojs . # Run the server (regenerates assets first) -run: generate +run: generate-dev go run . -port=8080 # Development mode - watch for changes and auto-reload dev: go tool air -image: - KO_DOCKER_REPO=$(KO_DOCKER_REPO) ko build +image: generate-prod + KO_DOCKER_REPO=$(KO_DOCKER_REPO) ko build -P --sbom=none # Clean generated files clean: diff --git a/README.md b/README.md index bfe22d9..7541c68 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ Changes to any file triggers a rebuild and reload, which takes ~3 seconds. ## Build & Run ```bash -make build # Build standalone binary -./gojs # Run binary +make release # Build standalone release binary (~7.7 MB) +./bin/gojs # Run binary # Or run directly: make run # Generate assets and run diff --git a/frontend/generate_dev.go b/frontend/generate_dev.go new file mode 100644 index 0000000..0870408 --- /dev/null +++ b/frontend/generate_dev.go @@ -0,0 +1,5 @@ +//go:build ignore + +package main + +//go:generate sh -c "npm install && npm run build:dev" diff --git a/frontend/generate.go b/frontend/generate_prod.go similarity index 61% rename from frontend/generate.go rename to frontend/generate_prod.go index 4cba026..d0db7f0 100644 --- a/frontend/generate.go +++ b/frontend/generate_prod.go @@ -1,3 +1,5 @@ -package frontend +//go:build ignore + +package main //go:generate sh -c "npm install && npm run build" diff --git a/frontend/package.json b/frontend/package.json index b466dbb..736d19d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,8 @@ "private": true, "scripts": { "build": "webpack --mode=production", - "dev": "webpack --mode=development --watch" + "build:dev": "webpack --mode=development", + "watch": "webpack --mode=development --watch" }, "devDependencies": { "typescript": "^5.0.0", diff --git a/frontend/src/index.html b/frontend/src/index.html index 4806ac6..7303666 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -1,5 +1,6 @@ + @@ -12,9 +13,11 @@ padding: 20px; line-height: 1.6; } + .container { text-align: center; } + button { background: #007cba; color: white; @@ -24,9 +27,11 @@ cursor: pointer; margin: 10px; } + button:hover { background: #005a85; } + #output { margin-top: 20px; padding: 20px; @@ -35,6 +40,7 @@ } +

Go + TypeScript Frontend

@@ -43,4 +49,5 @@
+ diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index a685390..135e410 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -1,29 +1,54 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -module.exports = { - entry: './src/index.ts', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, +module.exports = (env, argv) => { + const isDevelopment = argv.mode === 'development'; + + return { + entry: './src/index.ts', + mode: argv.mode || 'production', + devtool: isDevelopment ? 'eval-source-map' : false, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + filename: isDevelopment ? 'bundle.js' : 'bundle.[contenthash].js', + path: path.resolve(__dirname, '../assets'), + clean: true, + }, + optimization: { + minimize: !isDevelopment, + splitChunks: isDevelopment ? false : { + chunks: 'all', + minSize: 0, + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all', + }, + }, }, + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './src/index.html', + filename: 'index.html', + minify: isDevelopment ? false : { + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + }, + }), ], - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'], - }, - output: { - filename: 'bundle.js', - path: path.resolve(__dirname, '../assets'), - clean: true, - }, - plugins: [ - new HtmlWebpackPlugin({ - template: './src/index.html', - filename: 'index.html', - }), - ], + }; }; \ No newline at end of file diff --git a/main.go b/main.go index fd4b810..1fdbfc4 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,8 @@ import ( "io/fs" "log" "net/http" + "os" + "runtime/debug" ) //go:embed assets/* @@ -24,6 +26,45 @@ func main() { http.Handle("/", http.FileServer(http.FS(assetsFS))) addr := fmt.Sprintf(":%d", *port) + fmt.Printf("Version: %s\n", version()) + fmt.Printf("Binary size: %d bytes\n", binarySize()) + fmt.Printf("Embedded assets size: %d bytes\n", assetsSize()) fmt.Printf("Server starting on http://localhost%s\n", addr) log.Fatal(http.ListenAndServe(addr, nil)) } + +func version() string { + bi, ok := debug.ReadBuildInfo() + if !ok { + return "devel" + } + return bi.Main.Version +} + +func binarySize() int64 { + info, err := os.Stat(os.Args[0]) + if err != nil { + log.Fatal(err) + } + return info.Size() +} + +func assetsSize() int64 { + var total int64 + if err := fs.WalkDir(assets, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + info, err := d.Info() + if err != nil { + return err + } + total += info.Size() + } + return nil + }); err != nil { + log.Fatal(err) + } + return total +}