feat(export): self-extracting gzip standalone HTML via compress=True#233
feat(export): self-extracting gzip standalone HTML via compress=True#233FarhanAliRaza wants to merge 1 commit into
Conversation
At small point counts the inlined render client dominates a standalone export (~304 KB of a ~413 KB file at 10k scatter points). compress=True embeds the client and the data blob as base64 of gzip bytes (pinned mtime, so exports stay deterministic) and inflates them in-browser with DecompressionStream — no bundled inflate code, no server-side compression needed. Measured: 413 KB -> 215 KB at 10k points, with the Chromium screenshot pixel-identical to the plain export (single figure and facet grid). Browsers without DecompressionStream (pre-mid-2023) get a plain-text notice; the default output stays byte-identical. An external shared-client mode (plotly's include_plotlyjs='directory' shape) was evaluated and deliberately not shipped — the reflex adapter already serves the multi-chart economics, and it would widen the CSP. Documented in spec/api/export.md §9 and the §29 static-export row.
Greptile SummaryThis PR adds opt-in gzip compression for standalone HTML exports. The main changes are:
Confidence Score: 5/5Safe to merge with low risk. The change is localized to standalone HTML export paths, keeps No files require special attention.
What T-Rex did
Important Files Changed
Reviews (1): Last reviewed commit: "feat(export): self-extracting gzip stand..." | Re-trigger Greptile |
Merging this PR will not alter performance
Comparing Footnotes
|

What
Opt-in
compress=Trueon the standalone HTML export surfaces (export.to_html,Figure.to_html/html,Chart.to_html/html,FacetGrid.to_html,FacetChart.to_html): the embedded render client and data blob ride as base64-of-gzip chunks and are inflated in-browser via nativeDecompressionStream("gzip")before first render.Why
At small N the inlined client dominates the file — measured 73.6% of a 10k-point export (304 KB client / 413 KB file). Entry-trimmed bundle experiments showed client surgery can't fix this (severable widget-comm code is ~16 KB; the core renderer every path needs is ~187 KB), so the lever is compression at the container level.
Measured
compress=False(default) output is byte-for-byte the pre-existing formatmtime=0, so identical exports stay byte-identical (determinism contract, spec §4)Notes
DecompressionStream(pre-mid-2023) get a plain-text notice viatextContent(never markup) instead of a chart._STANDALONE_CSP.include_plotlyjs='directory'shape) was evaluated, validated, and deliberately dropped — the reflex adapter already provides cached-shared-client economics, and it would widen the CSP and add sibling-file lifecycle.spec/api/export.md§9 (new) and the §29 static-export transport row.Tests
tests/test_html_export_modes.py: loader/feature-check pinning, exact-bytes Python-side gzip roundtrip of client + data chunks, determinism, default-output invariance, facet parity.tests/reflex_adapter/test_assets.py::test_client_source_is_the_installed_bundleexpects unminified markers in the vite-minified bundle — unrelated).pre-commit run --all-files,ruff check .,ruff format --check .all green.