perf(admin): stop wrapping baked assets in a 7500-literal + chain

webui/generate.mjs split each admin-panel asset's base64 into 120-char
Go string literals joined with `+` (~7,550 literals, ~7,538 `+` ops
across index.html + the JS/CSS bundle). A compiled Go binary folds that
at compile time for free, but this plugin loads under Yaegi
(github.com/traefik/yaegi), which folds a chain of N literal `+`
operations at import time by repeatedly reallocating and copying the
whole already-folded prefix: O(N^2). Measured: ~186ms of a ~223-240ms
package import was this folding alone, versus ~3-4ms to decode the
asset bytes.

Replace the 120-char LINE_WIDTH with a 1,000,000-char CHUNK_CHARS cap.
Every asset this panel ships today (largest ~516,000 base64 chars)
fits in a single literal, so goBase64Expr now emits one decodeAdminAsset
call with zero `+` folds per asset. The cap still bounds a future,
much larger bundle to a small number of large chunks rather than one
unbounded line; single-line Go string literals in this size class were
verified against gofmt/go vet/go build before picking the constant.

No behavior change: decoded asset bytes, contentType strings, and the
asset map keys are unaffected by how the base64 is chunked into source
literals — see the following regen commit for the byte-identity proof.
This commit is contained in:
2026-08-22 13:32:04 +01:00
parent e06d8bf5cd
commit e5c3cd4b97
+26 -6
View File
@@ -111,16 +111,36 @@ function goStringLiteral(s) {
/**
* goBase64Expr renders base64Body (a base64 string) as a Go expression
* calling decodeAdminAsset, split into LINE_WIDTH-character chunks joined
* with `+` — a single 400KB-wide line is valid Go but painful for humans
* and diff tools alike; chunking keeps every line reviewable.
* calling decodeAdminAsset, split into at most CHUNK_CHARS-character
* literals joined with `+`.
*
* This used to split at a human-readable LINE_WIDTH of 120 chars —
* ~7,550 literals / ~7,538 `+` operations for this panel's ~680KB of
* base64. That's fine for a compiled Go binary (constant string
* concatenation folds at compile time), but this plugin is loaded by
* Yaegi (github.com/traefik/yaegi — Traefik's plugin interpreter), which
* folds a chain of N literal `+` operations at *import* time, by
* repeatedly reallocating and copying the whole already-folded prefix:
* O(N^2). Measured: that alone cost ~186ms of a ~223-240ms package
* import — the dominant share of Traefik's plugin-boot latency for this
* repo, versus ~3-4ms for decoding the asset bytes themselves.
*
* CHUNK_CHARS is set well above this panel's current largest asset
* (~516,000 base64 chars for the main JS bundle), so every asset today
* emits exactly one literal — zero `+` folds, zero O(N^2) cost. It still
* caps chunk size (rather than emitting one unbounded literal) so a
* future, much larger bundle degrades to a handful of large chunks
* instead of one arbitrarily long source line; gofmt/go vet/go build
* were verified against single-line string literals in this size class
* (see the perf-fix commit that introduced this constant), so this cap
* sits well inside that verified range.
*/
const LINE_WIDTH = 120
const CHUNK_CHARS = 1_000_000
function goBase64Expr(base64Body) {
if (base64Body.length === 0) return 'decodeAdminAsset("")'
const chunks = []
for (let i = 0; i < base64Body.length; i += LINE_WIDTH) {
chunks.push(goStringLiteral(base64Body.slice(i, i + LINE_WIDTH)))
for (let i = 0; i < base64Body.length; i += CHUNK_CHARS) {
chunks.push(goStringLiteral(base64Body.slice(i, i + CHUNK_CHARS)))
}
return `decodeAdminAsset(\n\t\t${chunks.join(' +\n\t\t')},\n\t)`
}