DEV Community

Cover image for Converting Three.js Journey Classic Techniques Module to TypeScript
Peter Riding
Peter Riding

Posted on

Converting Three.js Journey Classic Techniques Module to TypeScript

Why Add TypeScript to the Classic Techniques Module?

Bruno’s Classic Techniques module teaches the core building blocks every Three.js project needs. The plain JavaScript style works great for learning, but it’s easy to introduce silent bugs (wrong light properties, shadow typos, mismatched particle attributes, etc.).

With just a few targeted types you instantly get:

  • Errors caught in your editor before runtime
  • Full autocomplete
  • Self-documenting code
  • Safe refactoring and proper memory cleanup

Everything below is a direct port — the logic, math, and final visuals are 100% identical to the course lessons.

Quick Setup (for new readers)

  • Three.js (latest version) — official types are included
  • TypeScript 5+ with "strict": true in tsconfig.json
  • Recommended: Vite + @vitejs/plugin-react (or plain TS setup)
npm install three
npm install --save-dev typescript vite
Enter fullscreen mode Exit fullscreen mode

Part 1: Lights – Type Inference in Action

Original JS (course style):

const light = new THREE.AmbientLight(0xffffff, 0.4);
light.intensityyyy = 0.5; // silent failure
Enter fullscreen mode Exit fullscreen mode

Typed:

const light = new THREE.AmbientLight(0xffffff, 0.4);
light.intensityyyy = 0.5; // ❌ Property does not exist on type 'AmbientLight'
Enter fullscreen mode Exit fullscreen mode

TypeScript respects each light’s unique API:

const ambient = new THREE.AmbientLight();
ambient.castShadow = true; // ❌ Error

const directional = new THREE.DirectionalLight();
directional.target = mesh;     // ✅
directional.castShadow = true; // ✅
Enter fullscreen mode Exit fullscreen mode

Grouped lights (most useful pattern):

interface LightingSetup {
  ambient: THREE.AmbientLight;
  directional: THREE.DirectionalLight;
  accent?: THREE.PointLight;
}

function setupLighting(): LightingSetup { ... } // exact course logic
Enter fullscreen mode Exit fullscreen mode

Key takeaway: One interface = autocomplete everywhere.


Part 2: Shadows – Typed Config Objects

Shadow settings are nested and typo-prone. Interfaces fix that.

interface ShadowQuality {
  mapSize: number;
  cameraTop: number;
  cameraBottom: number;
  radius?: number;
  cameraLeft?: number;
  cameraRight?: number;
  cameraNear?: number;
  cameraFar?: number;
}

function configureShadows(light: THREE.DirectionalLight, quality: ShadowQuality) {
  light.shadow.mapSize.width = quality.mapSize;
  // ... rest of the course settings
}
Enter fullscreen mode Exit fullscreen mode

The classic 3-step setup (renderer → meshes → light) stays exactly the same.


Part 3: Particles & Galaxy Generator

The famous galaxy lesson, now typed and safe to regenerate.

interface GalaxyParameters {
  count: number;
  size: number;
  radius: number;
  branches: number;
  spin: number;
  randomness: number;
  randomnessPower: number;
}

interface GeneratedGalaxy {
  particles: THREE.Points;
  geometry: THREE.BufferGeometry;
  material: THREE.PointsMaterial;
  dispose(): void;
}

function generateGalaxy(params: GalaxyParameters): GeneratedGalaxy {
  // exact position + color gradient logic from the course
  const geometry = new THREE.BufferGeometry();
  const positions = new Float32Array(params.count * 3);
  const colors = new Float32Array(params.count * 3);

  // ... course math (radius, branchAngle, randomness, lerp)

  // material + Points setup (identical to lesson)
  return { particles, geometry, material, dispose() { ... } };
}
Enter fullscreen mode Exit fullscreen mode

Regeneration (course challenge) becomes trivial and safe:

let currentGalaxy = generateGalaxy(defaultParams);
// later
currentGalaxy.dispose();
scene.remove(currentGalaxy.particles);
currentGalaxy = generateGalaxy(newParams);
Enter fullscreen mode Exit fullscreen mode

Key takeaway: Typed parameters + explicit dispose() = no memory leaks when tweaking values live.


Part 4: Scroll State & Mouse Parallax

Simple state objects become self-documenting:

interface ScrollState {
  scrollY: number;
  lastScrollY: number;
  velocity: number;
}

const scrollState: ScrollState = { scrollY: 0, lastScrollY: 0, velocity: 0 };
Enter fullscreen mode Exit fullscreen mode

Mouse cursor object from the parallax lesson uses the exact same pattern.


Part 5: Procedural Scene Composition (House, Graves, Ghosts)

interface HouseStructure {
  group: THREE.Group;
  walls: THREE.Mesh;
  roof: THREE.Mesh;
  door: THREE.Mesh;
}

function createHouse(...) : HouseStructure { ... }
Enter fullscreen mode Exit fullscreen mode

Same clean interface pattern for graves (array of meshes) and the ghost system (with update() method).


Part 6: Asset Management & Cleanup

interface LoadedAssets {
  textures: Map<string, THREE.Texture>;
  geometries: Map<string, THREE.BufferGeometry>;
  materials: Map<string, THREE.Material>;
  dispose(): void;
}

function createAssetManager(): LoadedAssets { ... }
Enter fullscreen mode Exit fullscreen mode

Summary

Concept Used in the Course What TypeScript Adds
Type Inference Lights, BufferAttributes Instant errors + autocomplete
Interfaces Shadows, galaxy params, structures Clear contracts for every system
Typed Configs Shadow settings, particles No silent typos
Dispose Methods Galaxy, assets Reliable memory management

These small type additions turn the entire Classic Techniques module into production-ready code while preserving Bruno’s original teaching style and logic.

Have you converted any Three.js Journey lessons to TypeScript yet? Drop your experience (or questions) in the comments — happy to help!

Top comments (0)