visgl / visgl/deck.gl

[Feat] Improve deck.gl build speed and memory usage

Open
#10,344 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature
Dominant language
TypeScript
Stars
14.6k
Forks
2.3k
Avg merge
2d 9h
Merged PRs (30d)
42

Description

Target Use Case

Improve deck.gl's JavaScript/TypeScript build speed and memory profile for local development and CI, while preserving the current published package output.

The current full module build is slow enough to interrupt common development workflows. It also deletes TypeScript incremental state on every root build and compiles modules serially even where the dependency graph allows parallelism.

Current Build Shape

The root build script currently runs:

npm run clean && ocular-build && lerna run build

ocular-clean deletes every module dist directory and removes every tsconfig.tsbuildinfo file. ocular-build then walks modules in dependency order and runs roughly this in each package:

npx tspc --declaration --declarationMap --sourceMap --outDir dist --project tsconfig.json

After TypeScript emit, @vis.gl/dev-tools runs esbuild-based CJS conversion for package entry points. lerna run build then runs additional package scripts for packages such as @deck.gl/widgets, deck.gl, and @deck.gl/jupyter-widget.

The TypeScript build currently depends on ts-patch/tspc because deck.gl uses custom TypeScript transforms:

  • ts-transform-version-inline
  • ts-transform-remove-glsl-comments
  • ts-transform-inline-webgl-constants
  • ts-transform-append-extension

Those transforms are part of the current output contract and are an important constraint for any compiler migration.

Local Baseline

Measured locally on May 27, 2026 with Node 24.10.0 and TypeScript 5.5.4:

Command Time
npm run build 32.0s
cold per-module tspc pass only 35.3s
esbuild CJS conversion for all modules 1.9s

Largest cold tspc slices:

Module Time
@deck.gl/widgets 5.2s
@deck.gl/geo-layers 3.1s
@deck.gl/core 3.0s
@deck.gl/carto 2.9s
@deck.gl/aggregation-layers 2.3s

Extended TypeScript diagnostics on representative module checks showed each TypeScript process loading hundreds of files and using roughly 300MB of memory. The current serial build keeps peak memory bounded, but total CPU and memory churn is high.

Observed Problem: Warm Builds Are Not Reliable Today

The default root build deletes incremental state, so local builds never benefit from TypeScript incremental compilation.

Running ocular-build without a clean currently fails in modules/jupyter-widget because src/plugin.js imports ../dist/index, causing generated dist/*.d.ts files to become compiler inputs and then be overwritten.

Example failure:

error TS5055: Cannot write file 'modules/jupyter-widget/dist/index.d.ts' because it would overwrite input file.

This output/input boundary likely needs to be fixed before warm builds, affected builds, or task caching can be trusted.

Proposed Direction

I would like maintainer feedback on a staged modernization path instead of jumping straight to a single tool replacement.

  1. Make warm builds valid

    • Fix generated output being consumed as source input, starting with modules/jupyter-widget.
    • Verify ocular-build can run twice without ocular-clean.
  2. Separate release and development builds

    • Keep a clean release/publish build.
    • Add a warm local command such as build:dev = ocular-build && lerna run build.
    • Question: should npm run build remain clean-first, or should release workflows call a more explicit build:clean?
  3. Add affected-module builds

    • Read modules/*/tsconfig.json references.
    • Build changed modules plus downstream dependents.
    • Reuse existing ocular-build module-a,module-b behavior where possible.
  4. Parallelize by dependency level

    • Run independent modules in parallel after their dependencies complete.
    • Keep a conservative worker limit because TypeScript processes use significant memory.
    • Example levels from the current graph:
core
arcgis, extensions, google-maps, json, layers, mapbox, mesh-layers, test-utils, widgets
aggregation-layers, geo-layers, react
carto
jupyter-widget, main
  1. Revisit TypeScript project build mode

    • Prototype a root tsconfig.build.json and tspc -b tsconfig.build.json.
    • Compare clean build time, warm build time, memory use, and emitted JS/declaration/source map diffs.
    • If tspc -b cannot preserve transformed output, a custom DAG runner around per-module tspc may still be useful.
  2. Prepare for TypeScript 6

    • TypeScript 6 is now latest on npm.
    • Current config hits TS6 deprecation errors for baseUrl and moduleResolution: "node"/node10 unless suppressed.
    • Treat TS6 as a compatibility migration, not the primary speed win.
  3. Track TypeScript 7 / tsgo for future checks

    • TypeScript 7 native compiler is promising for parallel project checking/building.
    • Current config blocks tsgo because TS7 removes options still used by deck.gl.
    • Also, TS7 should not become the publishing compiler until custom transform and declaration-output needs are solved.
    • A possible first step is an experimental typecheck:tsgo once config blockers are removed.
Modern Patterns Worth Evaluating
  • TypeScript project references with valid warm incremental builds.
  • Affected-package task selection based on the module dependency graph.
  • Dependency-level parallel execution with a memory-aware worker limit.
  • Optional local and CI task caching through Lerna/Nx or similar.
  • TypeScript 7 native compiler checks once deck.gl's config is compatible.
  • Keeping release-grade clean builds separate from fast local rebuilds.
Related History
  • #6381 introduced the TypeScript monorepo setup and explicitly called out incremental builds as a benefit.
  • #6802 enabled declaration file emit.
  • #7546 switched standalone bundling from webpack to esbuild.
  • #8040 cleaned TypeScript output before builds to avoid stale files being published.
  • #8366 upgraded TypeScript from 4.x to 5.2.2 via ocular-devtools.
  • #8494 clarified that TypeScript build output and static asset copying are separate responsibilities.
  • #8531 and #8563 added/restored build transforms that affect generated output.
  • #9832 showed that accidental src/dist import boundaries can cause downstream TypeScript compile problems.
Maintainer Feedback Requested
  1. Should deck.gl keep npm run build as the clean release build and add a separate warm local build, or should the default build become warm?
  2. Should affected builds live in deck.gl, @vis.gl/dev-tools, or Lerna/Nx task graph configuration?
  3. What worker count is safe for CI given TypeScript's memory use?
  4. Should the custom TypeScript transforms remain in the compiler path, or should we investigate moving them into post-processing to unblock future TypeScript 7 native builds?
  5. Should TypeScript 6 migration be handled before parallel/incremental build work, or can these proceed independently?
  6. How strict should output equivalence be for non-release local builds?
References

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the root build scripts, ocular-clean, ocular-build, and modules/jupyter-widget/src/plugin.js, then inspect the modules/*/tsconfig.json references and existing ocular-build module selection. Reproduce the warm-build TS5055 failure and establish the maintainer-approved staged scope. Done should include a reliable warm-build path, measured clean and warm timings, memory comparisons, and confirmation that emitted output remains equivalent where required.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, typescript
Domain
build-system, developer-experience, performance
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.