Skip to content

Reduce bundle size of @luma.gl/core, @luma.gl/webgl and @luma.gl/webgpu #2852

Description

@ibgreen-openai

Motivation

The production bundles for the core, WebGL and WebGPU modules contain several large optional or diagnostic code paths that are currently part of the default runtime. A focused set of changes should materially reduce application transfer size, especially for WebGPU.

Status update — 2026-08-03

Landed

  • Bundle-size fixtures, gzip/Brotli reporting and regression ceilings: #2853.
  • Lightweight WGSL interface scanning, engine consumption of assembled layouts and removal of the default wgsl_reflect dependency: #2854, #2855 and #2856. This was the largest true-transfer win, saving about 34.5 KB gzip in the representative WebGPU application.
  • Optional WebGL debug/Spector entry: #2857, saving about 9.1 KB gzip in the representative WebGL application.
  • Decoupled luma from the complete Device defaults: #2859.
  • Reworked the developer bundling guide: #2860.
  • Emitted Spector declaration-only types: #2870.
  • Closed the ten residual bundle-size drafts after assessment; their links and measurements are retained below.

Current tracked fixtures

All regression ceilings pass. The goal column remains the aspirational target rather than the current CI failure threshold.

Fixture gzip Brotli gzip goal Gap to goal
@luma.gl/core 27,121 B 23,955 B 24,000 B +3,121 B
@luma.gl/webgl backend 37,319 B 32,081 B 30,000 B +7,319 B
@luma.gl/webgpu backend 22,388 B 19,823 B 22,000 B +388 B
core + WebGL 64,440 B 56,036 B 54,000 B +10,440 B
core + WebGPU 49,509 B 43,778 B 46,000 B +3,509 B
luma + webgl2Adapter 50,385 B 42,912 B 50,000 B +385 B
luma + webgpuAdapter 43,669 B 37,609 B 45,000 B 1,331 B under

Closed follow-up PRs, ranked by largest published gzip effect

The figures are incremental measurements published on each PR. These drafts were closed on 2026-08-03 after the major wins landed; their links and measurements are retained for future reference.

Rank PR Largest gzip / Brotli effect Assessment
1 #2864 Narrow package entry points 54,953 / 46,259 B Initial WebGPU load deferred, not eventual transfer saved. Additive idea retained as an initial-load reference.
2 #2871 Split canvas APIs from device entry 2,518 / 2,213 B True saving for compute/headless consumers of the new /device entry; stacked on #2864.
3 #2865 Optional WebGPU mipmaps 2,289 / 1,979 B Representative application saves 1,909 / 1,607 B, but mipmaps become an explicit v10 capability.
4 #2873 Optional Probe logging 2,193 / 1,981 B Representative application saves 1,985 / 1,771 B, but formatted logging becomes opt-in; stacked on #2866.
5 #2861 Lightweight buffer usage flags 2,011 / 1,826 B Narrow UniformStore fixture; isolated and behavior-compatible, but closed with the remaining drafts.
6 #2858 Forward-only GL constants 1,215 / 1,029 B Representative WebGL application; intentionally removes numeric enum reverse lookup.
7 #2863 Resource instrumentation hook 877 / 813 B Narrow Resource fixture; full core regresses by 49 B gzip. Primarily enables #2866 and #2873.
8 #2866 Optional resource diagnostics 679 / 546 B Makes resource statistics instrumentation opt-in; stacked on #2863.
9 #2862 Parse shader type metadata 500 / 192 B Small but isolated and behavior-compatible, but closed with the remaining drafts.
10 #2868 Optional WebGL1 compatibility 469 / 445 B Small application saving for a v10 compatibility/preload change.

Assessment and disposition

The dominant true-transfer wins have landed. Apart from #2864's adoption-dependent initial-load deferral, every remaining PR saves at most about 2.5 KB gzip on its best fixture, and representative default-application savings are generally 0.5–2.0 KB per PR.

The lowest-risk candidates were:

  1. perf(core): extract lightweight buffer usage flags #2861 — internal constant extraction, existing Buffer.* aliases preserved.
  2. perf(core): parse shader type metadata #2862 — small, isolated parser/table simplification with no intended public behavior change.
  3. feat: add narrow package entry points #2864feat(core): split canvas APIs from device entry #2871 — additive narrow entries and the only large remaining initial-load payoff.

They were nevertheless closed with the rest of the residual drafts to keep the bundle-size tranche focused on the landed high-impact work. The archived PRs remain useful as design and measurement references.

The remaining ideas should be evaluated as explicit v10 capability or compatibility decisions rather than pure bundle-size wins. In particular, #2865 changes mipmap setup; #2863#2866#2873 changes diagnostics/logging behavior for roughly 2.6 KB cumulative core gzip savings; #2858 changes enum semantics; and #2868 adds a preload/global-hook contract for less than 0.5 KB application gzip.

Decision: close the remaining draft PRs and preserve their links and measurements in this tracker. Revisit an individual idea only if future bundle regressions or product requirements justify its semantic and maintenance cost; continue enforcing the landed regression ceilings in CI.

Measurements below were taken from the current checkout using the repository's production targets and minification, with gzip-9 for compressed size.

Current baseline

Artifact Minified gzip
@luma.gl/core 98.7 KB 27.8 KB
@luma.gl/webgl backend 137.2 KB 38.7 KB
@luma.gl/webgpu backend 275.0 KB 57.1 KB

The backend prebundles expect core separately. When served separately, current totals are approximately:

  • core + WebGL: 235.9 KB minified / 66.5 KB gzip
  • core + WebGPU: 373.7 KB minified / 84.9 KB gzip

Representative tree-shaken application fixtures were:

  • luma + webgl2Adapter: 211.9 KB minified / 59.7 KB gzip
  • luma + webgpuAdapter: 353.5 KB minified / 78.4 KB gzip

Highest-impact findings

1. Replace general WGSL reflection in the default WebGPU runtime

wgsl_reflect contributes approximately 195 KB minified / 36.7 KB gzip, over 60% of the WebGPU backend. It is imported by modules/webgpu/src/wgsl/get-shader-layout-wgsl.ts and made mandatory through WebGPUDevice.getShaderLayout().

Most of the lightweight replacement already exists in modules/shadertools/src/lib/shader-assembly/wgsl-binding-debug.ts. It extracts binding names and locations, resource kinds, access modes and texture properties for about 1.3 KB gzip. Shadertools also already contains selected-entry-point and vertex-interface scanning.

Proposed work:

  • Promote the existing WGSL scanners into a delimiter-aware scanWGSLInterface(source, {vertexEntryPoint}) that produces a ShaderLayout. (#2854)
  • Have assembleWGSLShader() return this layout. (#2854)
  • Update Model and Computation to consume assembler metadata instead of reflecting assembled WGSL again. (#2855)
  • Require an explicit layout or use the lightweight scanner for raw low-level pipelines. (#2856)
  • Remove wgsl_reflect from the default WebGPU dependency graph. (#2856)
  • If compatibility requires it, expose the current full implementation through an optional @luma.gl/webgpu/wgsl-reflect entry.

Expected saving: more than 185 KB minified and approximately 30–35 KB compressed. A representative WebGPU application should move from 78.4 KB toward 42–45 KB gzip.

The scanner should cover aliases, nested generic types, reordered attributes, comments, multiple entry points and struct inputs, with a clear fallback requiring an explicit layout when inference is not safe.

2. Move WebGL debugging and the full GL enum off the normal adapter path

modules/webgl/src/context/debug/webgl-developer-tools.ts deliberately imports the complete reverse-mapped GL enum for debug strings. The adapter and device import WebGLDeveloperTools and Spector statically even though their use is conditional.

Proposed work:

  • Move WebGLDeveloperTools and Spector into @luma.gl/webgl/debug. (#2857)
  • Load/register them through the explicit optional debug entry. (#2857)
  • Define synchronous debug behavior through an explicit preload. (#2857)
  • Remove GL from the main root in the next major while retaining @luma.gl/webgl/constants.
  • If root compatibility is required, replace the numeric enum with a generated forward-only as const object.
  • Fix the WebGL constant-inlining transform so it removes imports whose member accesses have all been inlined.
  • Add tree-shaking coverage proving the default adapter path excludes optional debug code. (#2857)

Measured adapter saving from separating debug and Spector: approximately 35.6 KB minified / 9.1 KB gzip. A forward-only constant object saves another approximately 2.6 KB gzip versus the reverse-mapped enum when the constants must remain exported.

3. Make core runtime links and diagnostics optional

Several small imports defeat tree shaking:

  • Luma.defaultProps imports the complete Device class only to spread Device.defaultProps.
  • Device imports Buffer for usage constants.
  • UniformStore imports the Buffer hierarchy for usage constants.
  • Resource always includes stats bookkeeping, memory accounting, profiling and debug state.

Proposed work:

  • Extract shared default props so Luma no longer retains the complete Device class. (#2859)
  • Extract buffer usage flags into a small shared runtime module while preserving existing aliases.
  • Introduce an optional ResourceInstrumentation hook for resource lifecycle/allocation events.
  • Move rich probe logging, stats ordering, legacy stat mirroring, profiling and debug-data snapshots into @luma.gl/core/diagnostics.
  • Preserve luma.log and luma.stats through a compatibility facade.
  • Replace the exhaustive shader-type lookup table with a parser plus a small cache, validated against every current table entry.

Measured effects:

  • luma named import: 12.1 KB -> 5.2 KB gzip
  • Device named import: approximately 1.6 KB gzip smaller
  • UniformStore: approximately 2.0 KB gzip smaller
  • Diagnostics/logger extraction: approximately 13.9 KB minified / 4.0 KB gzip
  • Shader-type parser: approximately 3.1 KB minified / 0.6 KB gzip

A credible complete-core target is approximately 23–24 KB gzip.

Entrypoints and secondary work

  • Add narrow entries such as @luma.gl/webgl/adapter, /classes, /debug, /legacy.
  • Add @luma.gl/webgpu/adapter, /classes, /mipmaps, /wgsl-reflect.
  • Add core entries for /device, /resources, /uniforms, /diagnostics and test utilities.
  • In the next major, make backend roots adapter-oriented and keep concrete runtime classes under explicit subpaths.
  • Optionalize WebGPU mipmap generation: measured opportunity approximately 8.6 KB minified / 2.35 KB gzip.
  • Move WebGL1 enforcement and legacy parameter interception out of the normal WebGL runtime.
  • Consider splitting DOM canvas observation from compute/headless core entrypoints.
  • Convert declaration-like runtime enums/classes in spector-types.ts into actual declarations/interfaces to reduce published package size. (#2870)

An adapter-only WebGPU entry currently defers approximately 55 KB gzip on unsupported-WebGPU or WebGL-fallback paths. This is initial-load deferral rather than an eventual WebGPU transfer reduction.

Conditional lean build

deck.gl PR #10504 demonstrates a smaller supporting technique: a TypeScript transform plus custom export condition selecting a WebGL-only artifact.

luma.gl already separates WebGL and WebGPU packages, so a backend-only condition is not the main solution here. However, an ecosystem-scoped condition such as visgl:lean could help stage or deliver builds with diagnostics and debugging stripped while retaining normal import paths. Semantically different capabilities such as full WGSL reflection should remain explicit subpaths.

The long-term goal should be for the default architecture to become lean, rather than indefinitely maintaining two complete builds.

Proposed bundle budgets

Add CI coverage for full package bundles and representative named-import/application fixtures.

Initial target budgets:

Fixture gzip target
@luma.gl/core <= 24 KB
@luma.gl/webgl backend <= 30 KB
@luma.gl/webgpu backend <= 22 KB
core + WebGL approximately 52–54 KB
core + WebGPU approximately 42–46 KB
luma + webgl2Adapter <= 50 KB
luma + webgpuAdapter <= 45 KB

CI should record minified, gzip and Brotli sizes and fail on material regressions.

Suggested implementation order

  1. Add bundle fixtures and budgets.
  2. Replace wgsl_reflect with assembler-produced lightweight layout metadata.
  3. Lazy-load WebGL debug/Spector and remove the full GL enum from adapter imports.
  4. Extract shared core defaults/constants and optional diagnostics.
  5. Add narrow adapter/capability entrypoints.
  6. Optionalize mipmaps and retire legacy compatibility surfaces.

Related architectural discussion: #2634.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions