bootfallback

package
v5.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package bootfallback owns every byte of JavaScript the Atlas document ships, and every byte of markup that JavaScript reveals when the client cannot start.

WHY A GO PACKAGE FOR THIS

GoWebComponents' claim is that a browser application can be written in Go. The honest version of that claim has to name the exception, because there is exactly one and it is not zero:

<script src="/assets/script/wasm_exec.js"></script>   (575 lines, from $GOROOT/lib/wasm)
<script> ...the snippet in BootScript()... </script>  (~20 lines, generated here)

That is the whole JavaScript surface of Atlas. Everything else — the fallback copy, the fallback markup, the inline styling of that markup, the <noscript> content, the decision of which failure message applies — is Go, in this file. The snippet's only jobs are the two things Go provably cannot do from inside a wasm module that has not started yet:

  1. Instantiate the module. Go code cannot instantiate the Go runtime that would run it. Something outside the module must call WebAssembly.instantiate* and hand the import object over, and in a browser the only language that can do that is JavaScript.
  2. Report that step failing. If instantiation fails there is no Go heap, no syscall/js bridge, and no reconciler; a failure surface written in Go would need the very thing that just failed. (The client's own richer surface, client/bootsurface.go, is plain JS for the same reason one layer up: a Go panic calls exit(2), after which every js.FuncOf callback throws "bad callback: Go program has already exited".)

WHY wasm_exec.js CANNOT BE REPLACED

wasm_exec.js is not a convenience wrapper. It is the other half of the Go wasm runtime's ABI: it implements the ~40 host functions the compiler emits imports for (runtime.wasmWrite, runtime.scheduleTimeoutEvent, syscall/js.valueCall, syscall/js.valueNew, the whole JS value reference table), plus argv/environ setup and the memory-growth dance. It ships with the toolchain in $GOROOT/lib/wasm and is version-locked to the compiler that built the module. Hand-writing or trimming it means reimplementing an unstable internal ABI, and a mismatch surfaces as an "invalid import" at instantiate time or, worse, as memory corruption at runtime. Copy it, do not author it. (GOOS=wasip1 avoids it, but wasip1 has no DOM access, so it is not an option for a UI.)

THE FAILURE HISTORY THIS EXISTS TO FIX

Atlas's boot snippet used to be one line ending in `.catch(err => console.error('Failed to hydrate Atlas WASM:', err))`, against a document whose body is `<div id="app"></div>`. Every failure — 404 on the binary, wrong Content-Type, corrupt build, WebAssembly switched off by policy, scripting disabled — produced the identical result: a blank page and one line in a console nobody had open. Atlas shipped four months in that state (the client called atlas.App() outside a render pass and the runtime panicked) partly because a broken boot and a slow boot were pixel-identical.

So the rule this package encodes: no boot failure is allowed to be silent. Each one names what failed, whether it is the browser's fault or the deployment's, and what to do next.

Index

Constants

View Source
const (
	// HostID is the wrapper that holds every reason block. The snippet unhides
	// it; the server renders it already visible for the missing-binary case,
	// which needs no JavaScript at all because the server knows at render time.
	HostID = "atlas-boot-fallback"

	// ReasonIDPrefix + kind is the id of the block for one specific failure.
	// The snippet builds the id by concatenation, which is why the kinds below
	// are the single source of truth for both sides.
	ReasonIDPrefix = HostID + "-"

	// DetailID is the <pre> that receives the browser's own error text. It is a
	// separate node so the snippet can use textContent: the string can contain a
	// URL or a server message, i.e. content this page does not control, and an
	// error surface that uses innerHTML turns a blank page into an XSS sink.
	DetailID = HostID + "-detail"

	// KindAttr on the host records which failure was revealed, for tests and for
	// anyone reading the DOM after the fact.
	KindAttr = "data-atlas-boot-fallback"

	// NoScriptAttr marks the <noscript> payload.
	NoScriptAttr = "data-atlas-noscript"

	// StateAttr / StateFailed mirror client/bootsurface.go's boot-state contract
	// so "did this page fail?" is one selector regardless of which layer noticed.
	StateAttr   = "data-atlas-boot-state"
	StateFailed = "failed"
)

DOM contract. These are the assertion hooks for browser tests and the shared vocabulary between the generated JS and the generated markup. Copy is localized and gets rewritten; attribute values do not, so tests should assert on these rather than on sentences.

View Source
const (
	// KindIdle is the host's attribute value before anything has failed.
	KindIdle = "idle"

	// KindUnsupported: no WebAssembly runtime in this browser. Not a fault —
	// an environment limit. Nothing the operator can fix.
	KindUnsupported = "wasm-unsupported"

	// KindLoaderMissing: wasm_exec.js did not load, so `Go` is undefined. A
	// deployment fault: static assets are not being served.
	KindLoaderMissing = "loader-missing"

	// KindBootFailed: fetch or compile of the module failed. Deployment fault
	// (missing, truncated, or mis-typed binary) or a dropped request.
	KindBootFailed = "boot-failed"

	// KindBinaryMissing: the server checked its own disk and the binary is not
	// there. Known before the response is written, so this one is rendered
	// visible with no script involved.
	KindBinaryMissing = "wasm-missing"
)

Failure kinds. Each is a genuinely different cause with a genuinely different remedy, which is why there are four blocks instead of one generic message.

Variables

This section is empty.

Functions

func BootScript

func BootScript(parseOptions Options) string

BootScript returns the inline boot snippet. It is only emitted when the module is actually on disk; with the module missing, Markup() has already rendered a visible explanation and there is nothing for a script to add.

The ids and kinds are substituted from the constants above rather than written twice, so a rename cannot leave the JS reaching for an element the Go stopped emitting — a drift that would fail silently, at exactly the moment the page is already broken.

func Markup

func Markup(parseOptions Options, isBinaryPresent bool) string

Markup renders the fallback host, its reason blocks, and the <noscript> payload. It is emitted AFTER <div id="app"></div> and never inside it: markup inside the mount point is diffed against the client tree during hydration and reported as a mismatch (see client/bootsurface.go's note on the boot placeholder). Anything the page ships for the pre-hydration or failed-boot case has to be a sibling of the mount, not a child.

Types

type Options

type Options struct {
	// WASMURL is the browser-visible URL of the client module.
	WASMURL string
	// LoaderURL is the browser-visible URL of wasm_exec.js.
	LoaderURL string
	// BuildCommand is the command that produces WASMURL's file.
	BuildCommand string
}

Options carries the deployment facts the copy needs to be specific. Vague failure text ("Something went wrong") is the thing this package exists to prevent, and a message can only be specific if it can name the URL it wanted and the command that produces it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL