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:
- 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.
- 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 ¶
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.
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 ¶
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 ¶
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.