The Svelte frontend track¶
Go projects built on the go-tool-base pattern (keyrx, haileys-app) embed
a Svelte web UI directly into the Go binary via //go:embed. The four
Svelte components —
svelte-build,
svelte-lint,
svelte-test,
svelte-security — exist to
handle two coupling points that a normal Go-only pipeline doesn't have to
think about, plus the ordinary frontend validation work every UI needs.
Coupling point one: the release binary needs the real bundle¶
The pattern looks like this: pkg/studio/web/ is a Vite/Svelte app; its
built output lands in pkg/studio/web/embed/, which //go:embed
all:web/embed compiles into the Go binary. A committed placeholder
bundle in that directory means go build compiles standalone even before
Node has ever run — but the real bundle has to exist before a release
binary ships, or users get the placeholder's "install a release for the
full UI" notice instead of the actual application.
Two ways to close that gap exist, and which one a project needs depends on how it already builds the bundle:
go:generate(the common case for keyrx and haileys-app) — a//go:generate build-web.shdirective, invoked by goreleaser's ownbefore: hooks: - go generate ./..., builds the frontend as part of the ordinary release build. This also means a plaingo build/go installworks standalone for anyone with Node available locally — something a CI-only mechanism can't offer.svelte-build— for a project that doesn't have thatgo:generatehook, this component builds the bundle as a separate CI job and hands it to the release job as an artifact:goreleaser: needs: [{ job: svelte-build, artifacts: true }].
These are not both used on the same project. A project with the
go:generate hook that also wired svelte-build would build the bundle
twice at tag time — once via the before-hook, once via the artifact handoff
— for no benefit. svelte-build exists specifically for projects that
don't have the go:generate mechanism; where it does exist, the hook is
the single build path, and only svelte-lint / svelte-test /
svelte-security apply.
This was a genuine correction, not the original design: the components
initially assumed the artifact-handoff was the only sensible mechanism and
rejected a goreleaser before-hook outright as coupling the npm build into
every consumer's .goreleaser.yaml. In practice, the projects that
motivated this whole track already had exactly that hook, working, before
svelte-build existed — so the blanket rejection was wrong, and the
guidance is now per-project rather than one-size-fits-all.
Coupling point two: a frontend-only change still needs the Go tests to run¶
embed_test.go (the synthetic-filesystem test asserting spaFromFS's
served-UI behaviour) and the served-SPA handler both depend on what's
actually inside web/embed/. But go-test's
default change-detection filter only watches Go
source and module files — a change confined entirely to pkg/studio/web/
wouldn't trip it. So a project with this embed pattern must extend
go-test's changes input with its frontend path (pkg/studio/web/**),
so a Svelte-only change still runs the Go tests that assert on the embedded
bundle.
This is the one place change-detection's "consumers extend the default" escape hatch is load-bearing rather than a nicety — skip it, and a frontend-only MR could merge having never exercised the Go code that serves it.
The security posture: three layers, all free and open-source¶
svelte-security was built to be
security-forward specifically because a web UI is a different, and in some
ways larger, attack surface than a Go CLI — XSS, dependency confusion, and
supply-chain risk in a way go-security doesn't need to cover. It
deliberately covers three layers with entirely free/open-source tooling
(a commercial behavioral scanner like Socket or Phylum was considered and
explicitly declined — paywalled, and no free equivalent does the same job):
- known vulnerabilities —
osv-scanner(which also ingests the OSVMAL-malicious-packages feed at no extra cost),npm-audit, andretire.js; - SAST/XSS —
semgrep, tuned specifically for the web-UI risk profile (p/javascript,p/typescript,p/xss,p/secrets); - provenance and integrity —
npm audit signatures(registry signatures) andlockfile-lint(every resolved dependency URL must be HTTPS and the official registry — a dependency-confusion guard).
Like every *-security component, this whole set is
always-on, not change-detected.
See also¶
- Reference:
svelte-build,svelte-lint,svelte-test,svelte-security,goreleaser,go-test - Change-detection, Security, always-on
- How-to: Embed a Svelte frontend in a Go binary
- Spec:
2026-06-23-svelte-frontend-track