Documentation
¶
Index ¶
- Constants
- Variables
- func AffectedPatterns(pattern string) []string
- func BuildSSRBundle(opts BuildOptions) ([]byte, error)
- func DefaultPoolSize() int
- func DefaultSSRAliases(webDir, packagesDir string) map[string]string
- func ExpandPath(pattern string, params Params) (string, bool)
- func Handler(deps HandlerDeps) http.Handler
- func IndexNow(ctx context.Context, deps IndexNowDeps, urls []string) error
- func Invalidate(ctx context.Context, pattern string, params Params) error
- func PropsSHA256(propsJSON []byte) string
- func Reconcile(routes []RouteSpec) error
- func ReconcileEngine(ctx context.Context, r Renderer) error
- func Refresh(ctx context.Context, pattern string, params Params) error
- func RegisterDep(pagePattern, dependsOnPattern string)
- func RegisterLoader(pattern string, fn LoaderFn)
- func RegisterPaths(pattern string, fn PathsFn)
- func RegisterService(s *Service)
- func RenderDocument(doc Document) ([]byte, error)
- func ResetForTest()
- func RobotsHandler(opts RobotsOptions) http.Handler
- func SitemapHandler(routes []RouteSpec, baseURL string, opts SitemapOptions) http.Handler
- func ValidParamValue(v string) bool
- func ValidateParams(p Params) error
- func ViteBuildID(data []byte) string
- type BuildOptions
- type CacheConfig
- type CacheEntry
- type DevHandler
- type DevHandlerDeps
- type Directive
- type DirectiveSink
- type Document
- type EnginePool
- type HandlerDeps
- type HeadEntry
- type IndexNowDeps
- type LayeredCache
- func (c *LayeredCache) Delete(ctx context.Context, paths ...string) error
- func (c *LayeredCache) EvictMatching(ctx context.Context, match func(key string) bool) (int, error)
- func (c *LayeredCache) Get(ctx context.Context, path string) (CacheEntry, bool)
- func (c *LayeredCache) GetOrLoad(ctx context.Context, path string, ...) (CacheEntry, error)
- func (c *LayeredCache) GetOrLoadHit(ctx context.Context, path string, ...) (CacheEntry, bool, error)
- func (c *LayeredCache) GetStale(ctx context.Context, path string) (CacheEntry, bool)
- func (c *LayeredCache) Keys() []string
- func (c *LayeredCache) PurgeForeign(ctx context.Context, buildID string) error
- func (c *LayeredCache) Put(ctx context.Context, path string, ent CacheEntry) error
- type LoaderFn
- type Mode
- type PagePath
- type Params
- type PathsFn
- type PoolOptions
- type PrerenderDeps
- type PrerenderManifest
- type PrerenderManifestEntry
- type Registry
- func (r *Registry) AffectedPatterns(pattern string) []string
- func (r *Registry) LookupLoader(pattern string) (LoaderFn, error)
- func (r *Registry) LookupPaths(pattern string) (PathsFn, error)
- func (r *Registry) Reconcile(routes []RouteSpec) error
- func (r *Registry) RegisterDep(pagePattern, dependsOnPattern string)
- func (r *Registry) RegisterLoader(pattern string, fn LoaderFn)
- func (r *Registry) RegisterPaths(pattern string, fn PathsFn)
- type RenderResult
- type Renderer
- type RobotsOptions
- type RouteSpec
- type SEOSignals
- type Service
- type SitemapOptions
- type ViteAssets
Constants ¶
const ( DefaultMemPages = 1024 DefaultTTL = time.Hour )
const ( DefaultMemoryLimit = 512 << 20 // WasmShadowStackSize is the physical C shadow stack baked into the // bundled quickjs.wasm at build time (wasm-ld -z stack-size=8MiB; see // internal/qjs/csrc/README.md). Exhausting it traps the wasm module with // an uncatchable "out of bounds memory access" and leaves the instance // unusable — QuickJS's own catchable guard must trip first, which is why // MaxStackSize is validated against this ceiling minus headroom. WasmShadowStackSize = 8 << 20 // MaxLogicalStackSize is the largest accepted PoolOptions.MaxStackSize: // the shadow stack minus a 1MiB headroom, so a guard trip on the deepest // legal budget still leaves real stack below it and the physical wall // stays unreachable by recursion. MaxLogicalStackSize = WasmShadowStackSize - 1<<20 // DefaultMaxStackSize is the QuickJS execution-stack cap applied when // PoolOptions.MaxStackSize is unset. QuickJS checks it on every nested // call and raises a CATCHABLE "Maximum call stack size exceeded" that // leaves the instance healthy (measured capacities: ~3.1k frames of // simple recursion at this budget, ~8k at MaxLogicalStackSize). History: // the pre-v0.17.0 engine silently disabled this guard under wasi, so the // only visible wall was the binary's 64KiB default shadow stack — the // original #15/#16 "phantom depth ceiling". Rebuilt wasm (csrc/, pinned // quickjs-ng v0.17.0) restores the guard; see ADR 0015. DefaultMaxStackSize = 1 << 20 DefaultRenderTimeout = 3 * time.Second )
const ( DefaultCacheControl = "max-age=60, stale-while-revalidate=300" DefaultPoisonTTL = 10 * time.Minute )
const ( TierEmbed = "embed" TierCache = "cache" TierRender = "render" TierStale = "stale" TierSPA = "spa" TierAsset = "asset" TierError = "error" TierRobots = "robots" TierSitemap = "sitemap" )
Tier values, most-specific-first in the serving order: embedded prerender artifact, layered-cache hit, on-the-spot render (miss path), stale copy on render failure, SPA shell fallback (index.html routes incl. the 404 form), static asset passthrough, product error page. The SEO companions carry their own values so traffic middleware can count crawler fetches of them.
const DefaultIndexNowEndpoint = "https://api.indexnow.org/indexnow"
const DefaultPrelude = `` /* 5289-byte string literal not displayed */
const HeaderTier = "X-Renderx-Tier"
HeaderTier names the response header carrying which serving layer produced the response — one of the Tier* values below. It exists for traffic observation (metrics.TrafficConfig consumes it: "did the crawler get cache or SSR?") and for debugging ("why was this request rendered?"). Readers must keep in mind that a CDN caching the HTML replays the header of the origin response that first populated the cache.
const MaxPropsBytes = 512 << 10
const OutputVersion = "rv1"
Variables ¶
var ( ErrRenderTimeout = errors.New("renderx: render timeout") ErrPoolClosed = errors.New("renderx: engine pool closed") )
var ( ErrEmptyRenderOutput = errors.New("renderx: empty render output") ErrMissingTitle = errors.New("renderx: head directives emitted but no <title>") )
var ( ErrPatternNotFound = errors.New("renderx: pattern not registered") ErrPatternMismatch = errors.New("renderx: registered pattern not in route table") ErrMissingPaths = errors.New("renderx: static param route missing Paths") ErrInvalidParam = errors.New("renderx: invalid path param") ErrInvalidDirective = errors.New("renderx: invalid directive payload") ErrInvalidHeadTag = errors.New("renderx: invalid head tag or attr name") ErrInvalidCacheKey = errors.New("renderx: invalid cache key") ErrBuildIDMismatch = errors.New("renderx: cache entry buildId mismatch") ErrPropsTooLarge = errors.New("renderx: props exceed size limit") ErrPageNotFound = errors.New("renderx: page not found") )
Functions ¶
func AffectedPatterns ¶
func BuildSSRBundle ¶
func BuildSSRBundle(opts BuildOptions) ([]byte, error)
func DefaultPoolSize ¶
func DefaultPoolSize() int
func DefaultSSRAliases ¶
func Handler ¶
func Handler(deps HandlerDeps) http.Handler
func PropsSHA256 ¶
func RegisterDep ¶
func RegisterDep(pagePattern, dependsOnPattern string)
func RegisterLoader ¶
func RegisterPaths ¶
func RegisterService ¶
func RegisterService(s *Service)
func RenderDocument ¶
func ResetForTest ¶
func ResetForTest()
func RobotsHandler ¶ added in v0.7.2
func RobotsHandler(opts RobotsOptions) http.Handler
RobotsHandler serves /robots.txt: one "User-agent: *" group with the configured Disallow prefixes, the extra verbatim lines, and the Sitemap pointer. Options are validated and serialized once at construction — a malformed option panics at wiring time, never serving a silently wrong file.
func SitemapHandler ¶
func SitemapHandler(routes []RouteSpec, baseURL string, opts SitemapOptions) http.Handler
SitemapHandler serves /sitemap.xml from the route table + Paths registry. baseURL must be an absolute http(s) origin (e.g. "https://example.com") — a malformed value panics at construction time; mount the route only when an origin is actually known (see example wiring for the env-driven pattern).
func ValidParamValue ¶
func ValidateParams ¶
func ViteBuildID ¶
Types ¶
type BuildOptions ¶
type CacheConfig ¶
type CacheEntry ¶
type CacheEntry struct {
HTML []byte `json:"html"`
PropsSHA256 string `json:"props_sha256"`
BuiltAt time.Time `json:"built_at"`
BuildID string `json:"build_id"`
}
func RenderOnce ¶ added in v0.4.0
func RenderOnce(ctx context.Context, r Renderer, pageID, location string, props json.RawMessage, lang string, assets ViteAssets) (CacheEntry, error)
RenderOnce 把单个页面渲染成完整 HTML 文档——不经过缓存与 HTTP 层的 一次性渲染入口,预渲染 / handler 未命中 / 缓存回填共用此组装。 预览(草稿渲染)、邮件合成、调试等场景直接调用即可,语义与预渲染管线 完全一致(props 规范化、空输出校验、head 指令与 <title> 校验)。
type DevHandler ¶
type DevHandler struct {
// contains filtered or unexported fields
}
func NewDevHandler ¶
func NewDevHandler(deps DevHandlerDeps) (*DevHandler, error)
func (*DevHandler) Close ¶
func (d *DevHandler) Close()
func (*DevHandler) ServeHTTP ¶
func (d *DevHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type DevHandlerDeps ¶
type DevHandlerDeps struct {
BuildOpts BuildOptions
PoolOpts PoolOptions
NewRenderer func(bundle []byte, opts PoolOptions) (Renderer, error)
Cache *LayeredCache
Routes []RouteSpec
BuildID string
Assets ViteAssets
Lang string
SPA http.Handler
Registry *Registry
Interval time.Duration
Warn func(format string, args ...any)
}
type Directive ¶
type Directive struct {
Kind string `json:"kind"`
Payload json.RawMessage `json:"payload,omitempty"`
Seq int `json:"seq"`
}
type DirectiveSink ¶
type DirectiveSink struct {
// contains filtered or unexported fields
}
func NewSink ¶
func NewSink() *DirectiveSink
func (*DirectiveSink) Append ¶
func (s *DirectiveSink) Append(kind string, payload json.RawMessage) Directive
func (*DirectiveSink) Directives ¶
func (s *DirectiveSink) Directives() []Directive
func (*DirectiveSink) Truncated ¶
func (s *DirectiveSink) Truncated() bool
type EnginePool ¶
type EnginePool struct {
// contains filtered or unexported fields
}
func NewEnginePool ¶
func NewEnginePool(bundle []byte, opts PoolOptions) (*EnginePool, error)
func (*EnginePool) Close ¶
func (p *EnginePool) Close()
func (*EnginePool) Render ¶
func (p *EnginePool) Render(ctx context.Context, pageID, location string, props json.RawMessage) (RenderResult, error)
type HandlerDeps ¶
type HandlerDeps struct {
PrerenderFS fs.FS
SPA http.Handler
Cache *LayeredCache
Renderer Renderer
Routes []RouteSpec
BuildID string
CacheControl string
ErrorPage func(http.ResponseWriter, *http.Request, error)
Warn func(format string, args ...any)
Assets ViteAssets
Lang string
Registry *Registry
PoisonTTL time.Duration
}
type HeadEntry ¶
type HeadEntry struct {
Tag string `json:"tag"`
Attrs map[string]string `json:"attrs,omitempty"`
Children string `json:"children,omitempty"`
}
func HeadEntries ¶
type IndexNowDeps ¶
type LayeredCache ¶
type LayeredCache struct {
// contains filtered or unexported fields
}
func NewLayeredCache ¶
func NewLayeredCache(cfg CacheConfig) (*LayeredCache, error)
func (*LayeredCache) Delete ¶
func (c *LayeredCache) Delete(ctx context.Context, paths ...string) error
func (*LayeredCache) EvictMatching ¶
func (*LayeredCache) Get ¶
func (c *LayeredCache) Get(ctx context.Context, path string) (CacheEntry, bool)
func (*LayeredCache) GetOrLoad ¶
func (c *LayeredCache) GetOrLoad(ctx context.Context, path string, load func(context.Context) (CacheEntry, error)) (CacheEntry, error)
func (*LayeredCache) GetOrLoadHit ¶ added in v0.7.2
func (c *LayeredCache) GetOrLoadHit(ctx context.Context, path string, load func(context.Context) (CacheEntry, error)) (CacheEntry, bool, error)
GetOrLoadHit is GetOrLoad plus a hit report: hit=true when an existing cache entry (memory or disk) was served — including the singleflight replay path, where a concurrent Put landed after this request's first check and the load never ran. hit=false when the request was answered through the load path (this request's own load, or a coincident one it joined as a singleflight waiter). The X-Renderx-Tier response header is built on it.
func (*LayeredCache) GetStale ¶
func (c *LayeredCache) GetStale(ctx context.Context, path string) (CacheEntry, bool)
func (*LayeredCache) Keys ¶
func (c *LayeredCache) Keys() []string
func (*LayeredCache) PurgeForeign ¶
func (c *LayeredCache) PurgeForeign(ctx context.Context, buildID string) error
func (*LayeredCache) Put ¶
func (c *LayeredCache) Put(ctx context.Context, path string, ent CacheEntry) error
type PoolOptions ¶
type PoolOptions struct {
Size int
MemoryLimit int
// MaxStackSize caps the QuickJS execution stack in bytes. QuickJS's
// guard trips BEFORE the wasm module's physical shadow stack and raises
// a catchable "Maximum call stack size exceeded" — the instance stays
// healthy — so this knob trades depth capacity against per-render stack
// budget. Values above MaxLogicalStackSize are rejected at construction:
// beyond it recursion could reach the physical wall (uncatchable trap,
// instance retired). 0 keeps DefaultMaxStackSize (1MiB).
MaxStackSize int
RenderTimeout time.Duration
Prelude string
}
type PrerenderDeps ¶
type PrerenderManifest ¶
type PrerenderManifest struct {
BuildID string `json:"buildId"`
GeneratedAt time.Time `json:"generatedAt"`
Pages []PrerenderManifestEntry `json:"pages"`
}
func Prerender ¶
func Prerender(ctx context.Context, deps PrerenderDeps) (PrerenderManifest, error)
type PrerenderManifestEntry ¶
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
func NewRegistry ¶
func NewRegistry() *Registry
func (*Registry) AffectedPatterns ¶
func (*Registry) RegisterDep ¶
func (*Registry) RegisterLoader ¶
func (*Registry) RegisterPaths ¶
type RenderResult ¶
type RenderResult struct {
HTML string
Sink *DirectiveSink
}
type RobotsOptions ¶ added in v0.7.2
type RobotsOptions struct {
// SitemapURL is emitted as a "Sitemap:" line when non-empty (pass an
// absolute URL, e.g. "https://example.com/sitemap.xml").
SitemapURL string
// Disallow lists path prefixes emitted as "Disallow:" lines under the
// default "User-agent: *" group (e.g. "/api/", "/admin").
Disallow []string
// Extra holds verbatim lines appended after the default block, for more
// elaborate rules (further user-agent groups, Allow exceptions). Blank
// lines are skipped.
Extra []string
}
RobotsOptions configures RobotsHandler.
type SEOSignals ¶ added in v0.7.2
type SEOSignals struct {
Title string
Description string
Canonical string
// CanonicalCount counts link[rel=canonical] elements (>1 = the page emits
// duplicates). The SSR path does not dedupe head entries — one page, one
// canonical is a product-side rule, and build-time audits warn on it.
CanonicalCount int
}
SEOSignals is the head-level SEO signal set AuditSEO extracts from a rendered document. Empty strings mean the signal is absent.
func AuditSEO ¶ added in v0.7.2
func AuditSEO(doc []byte) SEOSignals
AuditSEO extracts the title / meta description / canonical link from a rendered document. It understands the serialization this package's template emits (lowercase tags, double-quoted attributes) — it audits our own output, not arbitrary HTML. Values come back HTML-unescaped. Build chains use it to report SEO coverage of prerendered pages (cmd/render does).
type Service ¶
type Service struct {
Cache *LayeredCache
Renderer Renderer
Routes []RouteSpec
BuildID string
Assets ViteAssets
Lang string
Registry *Registry
Warn func(format string, args ...any)
RefreshTimeout time.Duration
}
func (*Service) Invalidate ¶
type SitemapOptions ¶
type ViteAssets ¶
func ParseViteManifest ¶
func ParseViteManifest(data []byte, entryKey string) (ViteAssets, error)