Skip to content

feat(export): self-extracting gzip standalone HTML via compress=True#233

Open
FarhanAliRaza wants to merge 1 commit into
mainfrom
feat/compress-html-export
Open

feat(export): self-extracting gzip standalone HTML via compress=True#233
FarhanAliRaza wants to merge 1 commit into
mainfrom
feat/compress-html-export

Conversation

@FarhanAliRaza

Copy link
Copy Markdown
Contributor

What

Opt-in compress=True on 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 native DecompressionStream("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

  • 10k-point scatter export: 413 KB → 215 KB (~49% smaller)
  • Headless-Chromium screenshot of the compressed export is byte-identical to the plain one (single figure and facet grid)
  • compress=False (default) output is byte-for-byte the pre-existing format
  • gzip pins mtime=0, so identical exports stay byte-identical (determinism contract, spec §4)

Notes

  • Browsers without DecompressionStream (pre-mid-2023) get a plain-text notice via textContent (never markup) instead of a chart.
  • CSP is unchanged: the inflated client is injected as an inline script element under the existing _STANDALONE_CSP.
  • A shared-external-client mode (plotly's 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 updated: 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.
  • Full local suite: 616 passed; 1 pre-existing failure on clean main (tests/reflex_adapter/test_assets.py::test_client_source_is_the_installed_bundle expects unminified markers in the vite-minified bundle — unrelated).
  • Hooks: pre-commit run --all-files, ruff check ., ruff format --check . all green.

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-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds opt-in gzip compression for standalone HTML exports. The main changes are:

  • Adds compress=True support to export.to_html, Figure, Chart, FacetGrid, and FacetChart HTML APIs.
  • Embeds the render client and data payload as deterministic gzip-compressed base64 chunks.
  • Inflates compressed exports in the browser with DecompressionStream before rendering.
  • Shows a plain-text compatibility notice when compressed exports run in browsers without DecompressionStream.
  • Updates export documentation and adds tests for roundtrips, determinism, fallback behavior, default output, and facet parity.

Confidence Score: 5/5

Safe to merge with low risk.

The change is localized to standalone HTML export paths, keeps compress=False as the default, validates the new option, and includes focused tests for single-chart and facet-grid compressed exports.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Recorded the uncompressed and compressed HTML payload sizes (465,743 bytes and 193,801 bytes) and the reported compression ratio (0.4161).
  • Verified the browser results for file:// exports: status 200 for both, with 3 canvases and 21 SVGs, and chart rects sized 1000x520.
  • Validated that the two exports produced identical screenshot SHA256 hashes, matching b0a333d6e162ec8a031b27255f82b869f8b6218c52433eb9824ca7bb053c1bad.
  • Collected and indexed artifacts including HTML payloads, render screenshots, browser logs, JSON metadata, and validation scripts to support independent inspection.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/xy/export.py Implements deterministic gzip chunking, compressed client embedding, browser-side inflation, and fallback text for single-figure standalone HTML exports.
python/xy/facets.py Extends facet grid HTML export to gzip panel payloads and the shared client when compress=True, while retaining the existing uncompressed path.
tests/test_html_export_modes.py Adds tests for compressed chunk roundtrips, loader and fallback presence, determinism, default invariance, validation, and facet parity.
python/xy/_figure.py Adds and forwards the compress flag from Figure.to_html and html to the shared export implementation without changing default behavior.
python/xy/components.py Adds compress passthroughs for chart and facet chart HTML helpers, preserving existing defaults.
spec/api/export.md Documents the standalone HTML compression mode, compatibility floor, determinism, and rationale.
spec/design-dossier.md Updates the static HTML transport row to mention the opt-in gzip self-extracting export path.

Reviews (1): Last reviewed commit: "feat(export): self-extracting gzip stand..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Jul 23, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing feat/compress-html-export (4e14e2f) with main (b6ab496)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@Alek99

Alek99 commented Jul 23, 2026

Copy link
Copy Markdown
Member
Screenshot 2026-07-23 at 3 26 01 PM

Perf gain doesn't seem to be enough to justify it

@Alek99 Alek99 added the revisit label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants