{"id":15521458,"url":"https://github.com/sjc5/kiruna","last_synced_at":"2025-03-16T21:30:38.685Z","repository":{"id":224134731,"uuid":"762130784","full_name":"sjc5/kiruna","owner":"sjc5","description":"A simple, powerful application orchestrator in Go, featuring granular build hooks, static asset optimization, live browser refresh for fullstack apps, and excellent dev-prod parity.","archived":false,"fork":false,"pushed_at":"2025-02-18T04:35:59.000Z","size":338,"stargazers_count":27,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-16T05:51:17.354Z","etag":null,"topics":["asset-pipeline","build-tool","bundler","dev-server","file-hashing","go","golang","hot-reload","live-refresh","live-reload","vite"],"latest_commit_sha":null,"homepage":"https://github.com/sjc5/kiruna","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sjc5.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-23T06:30:09.000Z","updated_at":"2025-03-03T20:54:49.000Z","dependencies_parsed_at":"2024-03-20T01:27:56.263Z","dependency_job_id":"a36d81bb-7fb9-4b8a-b533-486e4018144c","html_url":"https://github.com/sjc5/kiruna","commit_stats":null,"previous_names":["sjc5/kiruna"],"tags_count":61,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjc5%2Fkiruna","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjc5%2Fkiruna/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjc5%2Fkiruna/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjc5%2Fkiruna/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sjc5","download_url":"https://codeload.github.com/sjc5/kiruna/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243936415,"owners_count":20371510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asset-pipeline","build-tool","bundler","dev-server","file-hashing","go","golang","hot-reload","live-refresh","live-reload","vite"],"created_at":"2024-10-02T10:34:50.248Z","updated_at":"2025-03-16T21:30:38.678Z","avatar_url":"https://github.com/sjc5.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🏔️ Kiruna\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/sjc5/kiruna.svg)](https://pkg.go.dev/github.com/sjc5/kiruna)\n[![Go Report Card](https://goreportcard.com/badge/github.com/sjc5/kiruna)](https://goreportcard.com/report/github.com/sjc5/kiruna)\n\n\u003cimg src=\"/banner.webp\" alt=\"Kiruna logo banner\"\u003e\n\nKiruna is a simple, powerful library for building and optimizing fullstack Go applications, with live browser refresh and excellent dev-prod parity. It's sort of like Vite, but for Go apps.\n\n### Dev server features\n\n- Automatic smart rebuilds and browser refreshes\n- Instant hot reloading for CSS files (without a full page refresh)\n- Highly configurable to support any use case\n- Glob pattern file watching\n- Granular build hooks with customizable timing strategies\n\n### Production optimizations\n\n- Static asset hashing and embedding\n- Basic CSS bundling and minification\n- Critical CSS inlining\n- Safely serve public static assets with immutable cache headers\n\nDev-time reloads are smart and fast. Based on the type of file you edit and your associated configuration options, Kiruna will do the minimum amount of work necessary to get your changes to your browser as quickly as possible.\n\nKiruna has a few lightweight runtime helpers for referencing hashed static assets from Go code and templates (e.g., `Kiruna.GetPublicURL(\"favicon.ico\")`) and for including your CSS in your HTML templates (e.g., `Kiruna.GetCriticalCSSStyleElement()`, `Kiruna.GetStyleSheetLinkElement()`). They have zero third-party dependencies and are aggressively cached whenever possible, so you can feel free to call them even in the hot path of your application.\n\nKiruna is completely decoupled from any specific frameworks or libraries, so you can use it with any Go server framework or router you choose, or just use the standard library. Moreover, unlike some alternatives, Kiruna doesn't require you to install any tooling on your machine. It is orchestrated solely from inside your repo and its dependencies.\n\n## Starter Tutorial From Scratch (~5 minutes)\n\nLet's get a Kiruna project set up from scratch. This should only take a few minutes to complete. The only prerequisite is that you have Go installed on your machine.\n\n### Scaffolding\n\nStart by initializing a new Go module in an empty directory, replacing `your-module-name` with your own module name:\n\n```sh\ngo mod init your-module-name\n```\n\nThen run the following commands to create the necessary directories and files for your project:\n\n```sh\n# Scaffold directories\nmkdir -p cmd/app cmd/build cmd/dev\nmkdir -p static/private static/public/prehashed\nmkdir -p styles/critical styles/normal\nmkdir -p dist/kiruna internal/platform\n\n# Create placeholder files\ntouch cmd/app/main.go cmd/build/main.go cmd/dev/main.go\ntouch static/private/index.go.html\ntouch styles/critical/main.css styles/normal/main.css\ntouch dist/kiruna/x dist/dist.go internal/platform/kiruna.go\n```\n\n---\n\n### Setup `dist/dist.go`\n\nNow copy this into your `dist/dist.go` file:\n\n```go\npackage dist\n\nimport \"embed\"\n\n//go:embed kiruna\nvar FS embed.FS\n```\n\n---\n\n### Setup `internal/platform/kiruna.go`\n\nNow copy this into your `internal/platform/kiruna.go` file, replacing `your-module-name` with your own module name:\n\n```go\npackage platform\n\nimport (\n\t\"your-module-name/dist\"\n\n\t\"github.com/sjc5/kiruna\"\n)\n\nvar Kiruna = kiruna.New(\u0026kiruna.Config{\n\tDistFS:           dist.FS,\n\tMainAppEntry:     \"cmd/app/main.go\",\n\tPrivateStaticDir: \"./static/private\",\n\tPublicStaticDir:  \"./static/public\",\n\tStylesDir:        \"./styles\",\n\tDistDir:          \"./dist\",\n})\n```\n\n---\n\n### Add Kiruna as a dependency\n\nNow go get Kiruna and tidy up:\n\n```sh\ngo get github.com/sjc5/kiruna\ngo mod tidy\n```\n\n---\n\n### Setup `static/private/index.go.html`\n\nNow copy this into your `static/private/index.go.html` file:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /\u003e\n    {{.Kiruna.GetCriticalCSSStyleElement}} {{.Kiruna.GetStyleSheetLinkElement}}\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv\u003e\n      \u003ch1\u003eHello, world!\u003c/h1\u003e\n      \u003cp\u003eHello from \"static/private/index.go.html\"\u003c/p\u003e\n    \u003c/div\u003e\n    {{.Kiruna.GetRefreshScript}}\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n---\n\n### Setup `cmd/app/main.go`\n\nAnd now copy this into your `cmd/app/main.go` file, replacing `your-module-name` with your own module name:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"your-module-name/internal/platform\"\n\n\t\"github.com/sjc5/kiruna\"\n)\n\nfunc main() {\n\t// Health check endpoint\n\thttp.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Write([]byte(\"OK\"))\n\t})\n\n\t// Serve static files from \"dist/kiruna/static/public\" directory, accessible at \"/public/\"\n\thttp.Handle(\"/public/\", platform.Kiruna.MustGetServeStaticHandler(\"/public/\", true))\n\n\t// Serve an HTML file using html/template\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tprivateFS, err := platform.Kiruna.GetPrivateFS()\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\thttp.Error(w, \"Error loading template\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\ttmpl, err := template.ParseFS(privateFS, \"index.go.html\")\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\thttp.Error(w, \"Error loading template\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\terr = tmpl.Execute(w, struct{ Kiruna *kiruna.Kiruna }{\n\t\t\tKiruna: platform.Kiruna,\n\t\t})\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"Error executing template\", http.StatusInternalServerError)\n\t\t}\n\t})\n\n\tport := kiruna.MustGetPort()\n\n\tfmt.Printf(\"Starting server on: http://localhost:%d\\n\", port)\n\thttp.ListenAndServe(fmt.Sprintf(\":%d\", port), nil)\n}\n```\n\n---\n\n### Setup `cmd/build/main.go`\n\nAnd copy this into your `cmd/build/main.go` file, replacing `your-module-name` with your own module name:\n\n```go\npackage main\n\nimport \"your-module-name/internal/platform\"\n\nfunc main() {\n\terr := platform.Kiruna.Build()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n```\n\nThis file is what you'll want to run when you're ready to build for production. Running `go run ./cmd/build` will build your project and save your binary to `dist/bin/main`. Assuming you used `DistFS` to embed your static assets, you can now run your binary from anywhere on the build machine, and it will serve your static assets from the embedded filesystem. If you chose not to embed your static assets, you'll just need to make sure that the binary is a sibling of the `dist/kiruna` directory in order to serve your static assets from disk.\n\n\u003e [!NOTE]\n\u003e Oftentimes you'll want to handle compilation of your Go binary yourself. In such cases, you can use `platform.Kiruna.BuildWithoutCompilingGo()` instead of `platform.Kiruna.Build()`. This will run all the same Kiruna-specific processing (static asset hashing, etc.) but will stop short of producing an executable.\n\n---\n\n### Setup `cmd/dev/main.go`\n\nNow copy this into your `cmd/dev/main.go` file, replacing `your-module-name` with your own module name:\n\n```go\npackage main\n\nimport (\n\t\"your-module-name/internal/platform\"\n\n\t\"github.com/sjc5/kiruna\"\n)\n\nfunc main() {\n\tplatform.Kiruna.MustStartDev(\u0026kiruna.DevConfig{\n\t\tHealthcheckEndpoint: \"/healthz\",\n\t\tWatchedFiles:        kiruna.WatchedFiles{{Pattern: \"**/*.go.html\"}},\n\t})\n}\n```\n\n---\n\n### Run the dev server\n\nNow try running the dev server:\n\n```sh\ngo run ./cmd/dev\n```\n\nIf you copied everything correctly, you should see some logging, with a link to your site on localhost, either at port `8080` or some fallback port. If you see an error, double check that you copied everything correctly.\n\n---\n\n### Edit critical CSS\n\nNow paste the following into your `styles/critical/main.css` file, and hit save:\n\n```css\nbody {\n  background-color: darkblue;\n  color: white;\n}\n```\n\nIf you leave your browser open and your dev server running, you should see the changes reflected in your browser nearly instantly via hot CSS reloading. Notice that the CSS above is being inlined into your document head. This is because it is in the `styles/critical` directory.\n\n---\n\n### Edit normal CSS\n\nNow let's make sure your normal stylesheet is also working. Copy this into your `styles/normal/main.css` file:\n\n```css\nh1 {\n  color: red;\n}\n```\n\nWhen you hit save, this should also hot reload.\n\n\u003e [!NOTE]\n\u003e You can put multiple css stylesheets into the `styles/critical` and `styles/normal` directories. In each case, the CSS will be minified and concatenated in alphabetical order by filename.\n\n---\n\n### Edit your HTML template\n\nNow let's try editing your html template at `static/private/index.go.html`.\n\nFind the line that says `\u003ch1\u003eHello, world!\u003c/h1\u003e` (line 10) and change it to: `\u003ch1 style=\"color: green;\"\u003eHello, world!\u003c/h1\u003e`.\n\nWhen you hit save, your browser page should automatically refresh itself. This happens because of the `{Pattern: \"**/*.go.html\"}` item in the `kiruna.WatchedFiles` slice in `cmd/dev/main.go`. If you were to remove that item and restart your dev server, the page would not reload when you save your html file (if you don't believe me, go give it a try).\n\nWhen you want to watch different file types, you can add them to the `kiruna.WatchedFiles` slice using glob patterns, and there are a whole bunch of ways to tweak this to get your desired reload behavior and sequencing, including callbacks and more. Feel free to explore your auto-complete options here or dive into the Kiruna source code to learn more.\n\n---\n\n### Setup .gitignore\n\nIf desired, you can bootstrap a new `.gitignore` file by running the following:\n\n```sh\necho \"dist/*\\n\\!dist/dist.go\" \u003e .gitignore\n```\n\n## Alternatives\n\nIf you're just looking for automatic Go application rebuilds only, without automatic browser refreshes or static asset build tooling, then Kiruna may be overkill for you, and you could just use \u003ca href=\"https://github.com/cosmtrek/air\" target=\"_blank\"\u003eAir\u003c/a\u003e instead.\n\nThat said, you can put Kiruna into a simpler `ServerOnly` mode if you want, which skips the frontend-targeted build steps and just does automatic Go application rebuilds.\n\nIn either case, one benefit of Kiruna over Air is that it doesn't require you to install any tooling on your machine. It is orchestrated solely from inside your repo and its dependencies. So when a new developer joins your team, they can just clone your repo and be ready to rock as soon as they run `go mod tidy` (instead of needing to install and configure Air on their machine first).\n\n## Copyright and License\n\nCopyright 2024 Samuel J. Cook. Licensed under the BSD 3-Clause License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjc5%2Fkiruna","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsjc5%2Fkiruna","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjc5%2Fkiruna/lists"}