Skip to content

[v2] Extract WebGL into @satus/webgl #97

@arzafran

Description

@arzafran

Parent: Roadmap to v2 (#95)

Phase 1: Reduce | Effort: Medium | Depends on: #96 (monorepo decision)

Problem

27 files + 5 production dependencies (~17% of the codebase) for a feature most projects won't use. Even though it's lazy-loaded via OptionalFeatures, it's still:

  • In node_modules (inflating install time with Three.js + R3F + postprocessing)
  • In the mental model (developers see webgl/, GlobalCanvas, FlowMap, Fluid, PostProcessing)
  • Leaking dev tools into production:
// lib/webgl/utils/fluid/index.tsx:1-5 — Theatre.js imported unconditionally
import { types } from '@theatre/core'
import { useCurrentSheet } from '@/dev/theatre'
import { useTheatre } from '@/dev/theatre/hooks/use-theatre'
import { Fluid } from '@/webgl/utils/fluid/fluid-sim'

@theatre/core and @/dev/theatre are imported at the top level — not gated behind process.env.NODE_ENV and not dynamically imported. This means Theatre.js ships in production bundles for any page using the fluid simulation.

Additionally, fluid-sim.ts is 724 LOC (the largest single file in the codebase) with WebGL shader code as template literals and zero documentation on the algorithm or shader passes.

Proposal

Before:                                     After:
  lib/webgl/           # 27 files             @satus/webgl        # Separate package
  components/effects/  # 6 files (uses WebGL)   bun add @satus/webgl
  package.json:                               package.json:
    three                                       (none — 5 fewer prod deps)
    @react-three/fiber
    @react-three/drei
    postprocessing
    tunnel-rat

The OptionalFeatures component already handles lazy loading:

// lib/features/index.tsx — Already dynamic, just needs a package check
const LazyGlobalCanvas = dynamic(
  () => import('@/webgl/components/global-canvas').then((mod) => ({
    default: mod.LazyGlobalCanvas,
  })),
  { ssr: false }
)

This would become:

// After — only loads if @satus/webgl is installed
let LazyGlobalCanvas: ComponentType | null = null
try {
  LazyGlobalCanvas = dynamic(
    () => import('@satus/webgl/global-canvas'),
    { ssr: false }
  )
} catch { /* package not installed */ }

Also Fix: Theatre.js Production Leak

Regardless of extraction, the Theatre.js imports in fluid/index.tsx and flowmaps/index.tsx need to be gated:

// Before — always imported
import { types } from '@theatre/core'
import { useCurrentSheet } from '@/dev/theatre'

// After — development only
const theatreTypes = process.env.NODE_ENV === 'development'
  ? require('@theatre/core').types : null

Or better: extract Theatre.js integration into a separate hook that's only imported in dev.

Impact on Dev Time

Metric Before After
bun install on non-WebGL project ~15 sec slower (Three.js tree) No Three.js deps
Production bundle (non-WebGL) Theatre.js leaks in Zero WebGL code
Mental model for new developers Must understand webgl/ exists Doesn't exist unless added

Tasks

  • Fix Theatre.js unconditional imports (can be done independently)
  • Extract lib/webgl/ into @satus/webgl package
  • Extract components/effects/animated-gradient/ (depends on WebGL)
  • Update OptionalFeatures to conditionally load
  • Update Wrapper component to optionally support webgl prop
  • Remove 5 production dependencies from base package.json
  • Document @satus/webgl setup and usage

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureArchitectural decisioneffort:mediumModerate implementation effortnovusReact Router migration branchphase:reducePhase 1: Cut weight and confusionv2Roadmap to v2

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions