Documentation
¶
Overview ¶
Package devserver is the Līdza dev server: the reverse proxy that puts the Go control plane and the frontend dev server behind one port, the static file server used in production, and the coordinator that keeps the app binary rebuilt while `lidza dev` runs.
Index ¶
- Constants
- Variables
- func AgentFiles(next http.Handler) http.Handler
- func Compressible(ext string) bool
- func Dev(ctx context.Context, cfg *config.Config, opt Options) error
- func EnsureNodeModules(ctx context.Context, dir string, out io.Writer) error
- func HasSidecar(dist fs.FS) bool
- func InjectHead(page []byte, h Head) []byte
- func KeepDist(dir, dist string) error
- func NewProxy(target string, opts ...Option) (http.Handler, error)
- func NewestSource(dir string) time.Time
- func RemovePID(dir string)
- func RequestRestart(dir string) error
- func RunningDev(dir string) (pid int, version string, ok bool)
- func Split(apiPrefix string, api, frontend http.Handler) http.Handler
- func Static(dist fs.FS, opts ...Option) http.Handler
- func WritePID(dir, version string) error
- type Head
- type HeadFunc
- type LocaleFunc
- type Meta
- type Option
- type Options
- type Sidecar
Constants ¶
const ( EnvMode = "LIDZA_MODE" // "dev" or unset EnvAddr = "LIDZA_ADDR" // listen address EnvFrontendURL = "LIDZA_FRONTEND_URL" // dev only: the dev server to proxy to )
Environment the app binary reads, in dev (set by `lidza dev`) and in production.
const BuildDir = ".lidza"
BuildDir holds the dev build of the app binary, inside the project.
const EnvSSR = "LIDZA_SSR"
EnvSSR enables per-request rendering: "1" starts the sidecar.
const LocalesDir = ".locales"
LocalesDir is the directory inside dist where the prerender writes each page once per locale when the app has more than one (locales/*.json): .locales/<lang>/<path>/index.html, .locales/<lang>/shell.html for the paths it did not render, and .locales/manifest.json naming the locales and the default.
const MinCompress = 1024
MinCompress is the smallest file or page worth compressing: below it the headers outweigh the saving.
const SSRDir = ".server"
SSRDir is the directory inside dist that holds the server bundle and the sidecar script, written by the react template's build.
const ShellFile = ".server/index.html"
ShellFile is the unrendered index.html the react template's prerender keeps next to the server bundle; it serves every path that has no file of its own.
Variables ¶
var PIDFile = filepath.Join(BuildDir, "dev.pid")
PIDFile, under the project, records the running `lidza dev`: its pid and the CLI version, so `lidza update` can say when one still runs the old CLI.
var RestartFile = filepath.Join(BuildDir, "restart")
RestartFile, under the project, restarts the app under `lidza dev` without a rebuild when its modification time changes. `lidza db migrate` and `rollback` touch it (RequestRestart), so the app does not keep running against the schema it started with.
Functions ¶
func AgentFiles ¶
AgentFiles serves the files `lidza dev` keeps under .lidza for agents (the app's llms.txt, llms-full.txt and openapi.json) and passes every other request to next. Used in dev mode only.
They are always at /_lidza/llms.txt, /_lidza/llms-full.txt and /_lidza/openapi.json. At the root (/llms.txt ...) they are served only when the app has no file of its own there: a product's own /llms.txt for crawlers wins, and the framework's copy stays under /_lidza/.
func Compressible ¶
Compressible reports whether files with this extension are stored compressed and served by Accept-Encoding.
func Dev ¶
Dev runs the project in cfg.Dir until ctx is cancelled: starts the frontend dev server if the template has one, builds the app binary, runs it in dev mode, and rebuilds and restarts it whenever a Go source file changes.
func EnsureNodeModules ¶
EnsureNodeModules runs `npm install` in dir when it has a package.json whose dependencies are not all in node_modules: a fresh clone, or a pull that added packages (another session's, a teammate's).
func HasSidecar ¶
HasSidecar reports whether dist carries the sidecar files.
func InjectHead ¶
InjectHead writes h into page's <head>: the title replaced, the meta tags it sets replacing the page's of the same name or property, the canonical link replaced, the JSON-LD added. A page without a <head> is returned as it is.
func KeepDist ¶
KeepDist restores dist/.gitkeep after a frontend build: the bundler empties the directory, and the Go embed of dist needs it to exist in a fresh clone, so the placeholder stays tracked.
func NewProxy ¶
NewProxy returns a handler that forwards every request to the frontend dev server at target, including WebSocket upgrades (Vite HMR). The Host header is rewritten to the target so Vite's allowed-hosts check passes. WithHead sets the head of the HTML pages it passes on, as Static does in production, and their inline scripts are hashed into the response's Content-Security-Policy.
func NewestSource ¶
NewestSource is the latest modification time of the Go sources in dir (*.go, go.mod, go.sum), skipping what the dev watcher skips. `lidza mcp` compares it with its app build to rebuild a stale one.
func RequestRestart ¶
RequestRestart asks a `lidza dev` running in dir to restart the app. It is a no-op in effect when none runs.
func RunningDev ¶
RunningDev reports the project's running dev server: its pid and CLI version. ok is false when none is recorded or its process is gone.
func Split ¶
Split routes requests under apiPrefix to api and everything else to frontend. apiPrefix is matched as a path prefix, "/api/" also matching "/api" itself.
func Static ¶
Static serves a built single-page app from dist: files by path (the .br or .gz copy lidza build stored when the request accepts it), hashed assets under /assets/ with a long cache lifetime, prerendered pages at <path>/index.html, and the shell for every other path (client-side routing). When the build has a page per locale (dist/.locales), pages and the shell come in the request's locale, negotiated as the i18n pack does (WithLocale passes the pack's own negotiation). WithHead sets each page's <head> per request.
Types ¶
type Head ¶
type Head struct {
// Title replaces the page's <title>; og:title and twitter:title
// follow it.
Title string
// Description replaces the page's meta description; og:description
// and twitter:description follow it.
Description string
// Canonical is the page's absolute address: <link rel="canonical">
// and og:url.
Canonical string
// Image is an absolute image address for og:image and twitter:image;
// with one, twitter:card is summary_large_image, else summary.
Image string
// Type is og:type; "website" when empty.
Type string
// SiteName is og:site_name.
SiteName string
// NoIndex asks search engines not to index the page (robots
// noindex): a draft, a private page.
NoIndex bool
// Meta are further <meta> tags (og:locale, article:published_time,
// twitter:site); each replaces a tag of the page with the same name
// or property.
Meta []Meta
// JSONLD are structured data objects (a map, a struct, a
// json.RawMessage), each written as its own
// <script type="application/ld+json">.
JSONLD []any
// Status is the response status when not zero: 404 for an address
// whose record does not exist, so a crawler does not index an empty
// page.
Status int
}
Head is what a page's <head> says about it, decided on the server per request: the title, the description, the canonical address, the social cards (Open Graph and Twitter) and structured data (JSON-LD). A page with a path parameter is one shell for every address until the head hook (WithHead) names it; search engines and link previews read this, not what the browser renders later.
Every value is text: it is escaped where it is written, so a title taken from a database row cannot close a tag.
type HeadFunc ¶
HeadFunc decides the head of the page r asks for; false leaves the page as built. It runs for every page request (not for assets), with the request's context carrying the app's services, so it may read the database; keep it to one query.
type LocaleFunc ¶
LocaleFunc names the locale a request asks for, such as the i18n pack's negotiation. An empty result leaves the choice to the built-in one.
type Meta ¶
Meta is one <meta> tag: Name (name="...") or Property (property="...", the Open Graph form), and Content.
type Option ¶
type Option func(*options)
Option configures Static, NewSidecar and NewProxy.
func WithHead ¶
WithHead sets the head of every page Static, the sidecar and the dev proxy serve with f: the prerendered pages and the shell alike.
func WithLocale ¶
func WithLocale(f LocaleFunc) Option
WithLocale picks the page variant with f instead of the built-in negotiation (?lang, the lang cookie, Accept-Language, the default).
type Options ¶
type Options struct {
// Addr is where the app listens; the frontend is proxied behind it.
Addr string
// Out receives the interleaved, prefixed output of every process.
Out io.Writer
// Poll is how often the Go sources are checked for changes.
Poll time.Duration
// BeforeBuild runs before every build. `lidza dev` uses it to run the
// schema generators; an error is logged and the build still runs.
BeforeBuild func() error
// AfterBuild runs after every successful build of the app, before the
// restart. `lidza dev` uses it to refresh the agent context files and
// the generated client.
AfterBuild func()
// Watch lists extra files or directories, relative to the project
// root, whose change triggers a rebuild besides the Go sources.
Watch []string
// Rewatch, when set, is called after every build for the new list of
// extra files (lidza.json may have gained a pack whose sources are
// now watched); it replaces Watch.
Rewatch func() []string
}
Options configures a `lidza dev` run.
type Sidecar ¶
type Sidecar struct {
// contains filtered or unexported fields
}
Sidecar renders pages per request through a Node process running the app's SSR bundle, reached over a Unix socket. It is optional: without it, or when it fails, the prerendered or static page is served.
func NewSidecar ¶
NewSidecar prepares a sidecar for dist. apiBase is the app's own address, where loaders reach the API during a render. When the build has a page per locale (dist/.locales), each render is asked for the request's locale, negotiated as Static does.
func (*Sidecar) Handler ¶
Handler renders HTML page requests through the sidecar and passes everything else, and every failure, to next.