Summary
Upgrading from 0.1.10 to 2.1.0, our bundled Spark chunk grew from ~575 KB raw / 156 KB gzip
to ~5.02 MB raw / 1.78 MB gzip. Auditing the built chunk reveals that ~47% of it is the legacy sorter
worker (oldWorker-*), which the current (non-Old*) API never executes:
oldWorker-* inline string literal: 2,348,093 chars ≈ 46.8% of the chunk
(~816 KB of the chunk's 1.78 MB gzip on its own)
- current
worker-* literal: 2,116,988 chars ≈ 42.2%
- together the two inlined workers are ~89% of the shipped bytes
Why bundlers can't remove it
Both workers are inlined as top-level string constants, and both are wrapped in
new Blob([...]) constructed at module scope, so they materialize on chunk evaluation
regardless of which code path is used. The package also declares no "sideEffects" field,
so no bundler (we measured with Next.js/Turbopack production builds, but this is
bundler-agnostic) can tree-shake the legacy path even when only the new
SparkRenderer/SplatMesh API is imported.
Ask
Any of these would roughly halve the payload for apps on the current API:
- Move the
Old*/legacy path (and its worker string) behind a subpath export
(e.g. @sparkjsdev/spark/legacy), or
- Construct the worker
Blobs lazily on first use instead of at module scope, and add
"sideEffects": false (or an accurate list) to package.json so the unused branch is
shakeable, or
- Ship a secondary "modern-only" build.
Happy to provide the full chunk-analysis methodology/numbers if useful.
Thanks — the 2.x
renderer itself has been a clean upgrade for us otherwise :)
Summary
Upgrading from 0.1.10 to 2.1.0, our bundled Spark chunk grew from ~575 KB raw / 156 KB gzip
to ~5.02 MB raw / 1.78 MB gzip. Auditing the built chunk reveals that ~47% of it is the legacy sorter
worker (
oldWorker-*), which the current (non-Old*) API never executes:oldWorker-*inline string literal: 2,348,093 chars ≈ 46.8% of the chunk(~816 KB of the chunk's 1.78 MB gzip on its own)
worker-*literal: 2,116,988 chars ≈ 42.2%Why bundlers can't remove it
Both workers are inlined as top-level string constants, and both are wrapped in
new Blob([...])constructed at module scope, so they materialize on chunk evaluationregardless of which code path is used. The package also declares no
"sideEffects"field,so no bundler (we measured with Next.js/Turbopack production builds, but this is
bundler-agnostic) can tree-shake the legacy path even when only the new
SparkRenderer/SplatMeshAPI is imported.Ask
Any of these would roughly halve the payload for apps on the current API:
Old*/legacy path (and its worker string) behind a subpath export(e.g.
@sparkjsdev/spark/legacy), orBlobs lazily on first use instead of at module scope, and add"sideEffects": false(or an accurate list) to package.json so the unused branch isshakeable, or
Happy to provide the full chunk-analysis methodology/numbers if useful.
Thanks — the 2.x
renderer itself has been a clean upgrade for us otherwise :)