4.upgrade-guide.md 15 KB


title: Upgrade Guide description: Upgrade TresJS to the latest version. navigation:

icon: i-lucide-circle-arrow-up

Upgrading TresJS

Latest Release

::warning This document covers the upgrade process from version 4.x to version 5.x. It's currently on nightly builds so you expect possible breaking changes. ::

To upgrade TresJS to the latest next release, run the following command:

:::code-group

pnpm add tresjs@next
npm install tresjs@next
yarn add tresjs@next

:::

Major Version Upgrades

When upgrading to a new major version of TresJS, you may need to make changes to your code. Below are the breaking changes introduced in v5 and how to migrate your code.

Breaking Changes in v5

ESM-only Build

🚦 Impact Level: High

What Changed

TresJS v5 is now ESM (ES Module) only. The UMD build configuration has been removed.

Why We Changed It

  • Modern JavaScript ecosystem has moved towards ESM
  • Better tree-shaking and smaller bundle sizes
  • Improved TypeScript support
  • Aligns with Vue 3 and modern tooling

Migration Steps

  1. Update your import statements to use ESM syntax:

    // ❌ Old CommonJS (no longer supported)
    const { TresCanvas } = require('@tresjs/core')
    
    // ✅ New ESM syntax
    import { TresCanvas } from '@tresjs/core'
    
  2. Update your build configuration to support ESM:

    // package.json
    {
    "type": "module"
    }
    
  3. If using Node.js, ensure you're using Node.js 14+ and update your imports:

    // ❌ Old
    const Tres = require('@tresjs/core')
    
    // ✅ New
    import * as Tres from '@tresjs/core'
    

useLoader Composable refactor

🚦 Impact Level: High

What Changed

The useLoader composable has been completely refactored from a simple utility function wrapping Three.js loaders into a true Vue composable based on useAsyncData. It now returns a reactive state with loading, error handling, and progress tracking. It previously wasn't a true composable but a utility wrapping Three.js loaders. 😬

  • Is now based on useAsyncData for better Vue integration
  • Provides reactive state management with loading states
  • Includes progress tracking and error handling
  • Better TypeScript support and developer experience
  • Automatic cleanup and disposal of 3D objects

Migration Steps

  1. Update from promise-based to reactive state:

    // ❌ Old v4 syntax - returned a promise
    const gltf = await useLoader(GLTFLoader, '/models/duck.gltf')
    
    // ✅ New v5 syntax - returns reactive state
    const { state: gltf, isLoading, error } = useLoader(GLTFLoader, '/models/duck.gltf')
    
  2. Handle loading states reactively:

    <script setup>
    // ✅ New v5 with reactive loading states and progress
    const { state: model, isLoading, error, progress } = useLoader(
    GLTFLoader,
    '/models/duck.gltf'
    )
    
    // Watch for loading state changes
    watch(isLoading, (loading) => {
    if (loading) { console.log('Loading model...') }
    })
    
    // Watch for progress updates
    watch(progress, (prog) => {
    console.log(`Loading: ${prog.percentage}%`)
    })
    </script>
    
    <template>
    <primitive v-if="!isLoading && model?.scene" :object="model.scene" />
    </template>
    
  3. Add loader extensions (like DRACO):

    // ✅ New v5 syntax with extensions
    const { state: model, isLoading, progress } = useLoader(
    GLTFLoader,
    '/models/compressed.glb',
    {
    extensions: (loader) => {
      if (loader instanceof GLTFLoader) {
        loader.setDRACOLoader(dracoLoader)
      }
    }
    }
    )
    
  4. Dynamic path loading:

    // ✅ New v5 supports reactive paths
    const modelPath = ref('/models/duck.gltf')
    const { state: model, load } = useLoader(GLTFLoader, modelPath)
    
    // Change path reactively
    modelPath.value = '/models/fox.gltf' // Automatically reloads
    
    // Or load programmatically
    load('/models/another-model.gltf')
    
  5. Texture loading example:

    // ✅ New v5 texture loading
    const { state: texture, isLoading } = useLoader(
    TextureLoader,
    'https://example.com/texture.jpg'
    )
    

useTexture removal

🚦 Impact Level: Moderate

What Changed

The useTexture composable has been completely removed from the core package and moved to @tresjs/cientos.

Why We Changed It

  • Better separation of concerns
  • Reduced core bundle size
  • More specialized texture handling in cientos package

Migration Steps

  1. Install @tresjs/cientos:

    pnpm add @tresjs/cientos
    
  2. Update your imports:

    // ❌ Old v4 import
    import { useTexture } from '@tresjs/core'
    
    // ✅ New v5 import
    import { useTexture } from '@tresjs/cientos'
    
  3. Usage remains the same:

    // ✅ Same usage pattern
    const texture = await useTexture('/textures/brick.jpg')
    

Event System Changes

🚦 Impact Level: Moderate

What Changed

  • New event system based on the @pmndrs/pointer-events package.
  • Only the first intersected element will trigger pointer events.
  • useTresEventManager composable has been removed.
  • Pointer events now follow native DOM event names exactly as they are defined, '@pointer-down' --> '@pointerdown'. See MDN Web docs for more details.

Why We Changed It

  • Better performance with complex scenes
  • More predictable event handling
  • Consistent with web standards and pmndrs ecosystem
  • Prevents event bubbling issues
  • Leverages battle-tested pointer event handling from the pmndrs community
  • Keeps the API consistent with the web standard.

Migration Steps

  1. Update event handling expectations:

    // ❌ Old behavior: multiple overlapping objects could trigger events
    <TresMesh @click="handleClick">
    <TresBoxGeometry />
    <TresMeshBasicMaterial />
    </TresMesh>
    <TresMesh @click="handleClick"> <!-- This might not trigger if behind first mesh -->
    <TresBoxGeometry />
    <TresMeshBasicMaterial />
    </TresMesh>
    
    // ✅ New behavior: only first intersected object triggers event
    // If you need multiple objects to handle events, ensure they don't overlap
    // or handle the event at a parent level
    
  2. Restructure overlapping interactive elements:

    // ✅ Use a single parent handler for overlapping elements
    <TresGroup @click="handleGroupClick">
    <TresMesh>
    <TresBoxGeometry />
    <TresMeshBasicMaterial />
    </TresMesh>
    <TresMesh>
    <TresBoxGeometry />
    <TresMeshBasicMaterial />
    </TresMesh>
    </TresGroup>
    
  3. Replace dash-case pointer events:

    // ❌ Old behavior: dash-case events
    <TresMesh @pointer-down="handlePointerDown">
    
    // ✅ New behavior: native DOM event names
    <TresMesh @pointerdown="handlePointerDown">
    

Camera Context Changes

🚦 Impact Level: Low

What Changed

Camera context is now a state object instead of the active camera instance.

Why We Changed It

  • Better camera management
  • Support for multiple cameras
  • Improved camera switching

Migration Steps

Easy Migration Path: For most use cases it is probably sufficient to change useTresContext to useTres:

// ❌ Old v4 syntax
const { camera } = useTresContext()

// ✅ Easy v5 migration
const { camera } = useTres()
// camera works the same as before

Advanced Usage: If you need the full context (mainly for module authors), use useTresContext:

// ✅ For module authors - full context access
const { camera } = useTresContext()
// camera is now an object with camera management methods
const activeCamera = camera.value.current

::read-more Learn more about the context system in our internal documentation. ::

Renderer and Context Changes

🚦 Impact Level: Moderate

What Changed

  • Performance state removed from context
  • Renderer instance is now readonly
  • invalidate, advance, canBeInvalidated and renderer instance now accessed through context

Why We Changed It

  • Better encapsulation
  • Improved performance monitoring
  • More consistent API

Migration Steps

Easy Migration Path: For most use cases it is probably sufficient to change useTresContext to useTres:

// ❌ Old v4 syntax
const { renderer, invalidate, advance } = useTresContext()

// ✅ Easy v5 migration
const { renderer, invalidate, advance } = useTres()
// Works the same as before for common use cases

Advanced Usage: If you need the full context (mainly for module authors), use useTresContext:

// ✅ For module authors - full context access
const { invalidate, advance, canBeInvalidated, renderer } = useTresContext()
// renderer is now readonly
// Use context methods for renderer operations

Performance monitoring changes:

// ❌ Old v4 performance access
const { performance } = useTresContext()

// ✅ New v5 - use renderer stats or custom performance monitoring
const { renderer } = useTres()
const stats = renderer.info

::read-more Learn more about the context system in our internal documentation. ::

Deprecated Composables Removal

🚦 Impact Level: Moderate

What has been removed

  • useTresReady
  • useSeek
  • useTresEventManager
  • useRaycaster
  • useRenderLoop
  • useLogger
  • useCamera

Why We Changed It

  • Simplified API surface
  • Better state management patterns
  • Replaced with more robust alternatives

Migration Steps

  1. Replace useTresReady:

    // ❌ Old v4 syntax
    const { isReady } = useTresReady()
    
    <script setup>
    // ✅ New v5 alternative - use @ready event on TresCanvas
    // Option 1: Template event (recommended for most users)
    
    const onReady = (context) => {
    console.log('Renderer is ready:', context.renderer.instance)
    // Your ready logic here
    }
    </script>
    
    <template>
    <TresCanvas @ready="onReady">
    <!-- Your 3D scene -->
    </TresCanvas>
    </template>
    
    // Option 2: Composable approach (advanced users)
    const { renderer } = useTresContext()
    
    renderer.onReady((rendererInstance) => {
    console.log('Renderer ready:', rendererInstance)
    // Your ready logic here
    })
    
  2. Replace useSeek (if you were using it):

    // ❌ Old v4 syntax
    const { seek, seekByName, seekAll, seekAllByName } = useSeek()
    const body = seek(car, 'name', 'Octane_Octane_Body_0')
    const bones = seekAll(character, 'type', 'Bone')
    
    // ✅ New v5 alternative - use useGraph or manual traversal
    import { useGraph } from '@tresjs/core'
    
    // Option 1: Use useGraph for structured access
    const { state: model } = useLoader(GLTFLoader, '/path/to/model.glb')
    const scene = computed(() => model.value?.scene)
    const { nodes, materials } = useGraph(scene)
    
    // Access objects by name directly
    const body = computed(() => nodes.value?.Octane_Octane_Body_0)
    
    // Option 2: Manual traversal function
    function seek(object, property, value) {
    if (!object) return null
    
    if (object[property] === value) return object
    
    for (const child of object.children) {
    const found = seek(child, property, value)
    if (found) return found
    }
    
    return null
    }
    
    // Usage
    const body = seek(car.value, 'name', 'Octane_Octane_Body_0')
    
  3. Replace useRenderLoop:

    // ❌ Old v4 syntax
    const { onLoop } = useRenderLoop()
    
    onLoop(({ delta, elapsedTime }) => {
    // Your loop logic here
    })
    
    // ✅ New v5 alternative - use context methods
    
    const { onBeforeRender } = useLoop()
    
    onBeforeRender(({ delta, elapsedTime }) => {
    // Your loop logic here
    })
    

::warning useLoop can only be used in child components of a TresCanvas component, as its data is provided by TresCanvas. ::

::read-more{TO="/api/composables/use-loop"} Learn more about the useLoop composable. ::

If the composable was used on a SFC (single file component), you can use the loop event on the TresCanvas component:

<script setup>
onLoop(({ delta, elapsedTime }) => {
  // Your loop logic here
})
</script>

<template>
  <TresCanvas @loop="onLoop">
    <!-- Your 3D scene -->
  </TresCanvas>
</template>
  1. Replace useCamera:

    // ❌ Old v4 syntax
    const { camera } = useCamera()
    
    // ✅ New v5 alternative - use context methods
    const { camera } = useTres()
    

TresCanvas Props Reactivity Changes

🚦 Impact Level: Low to Moderate

What Changed

Several TresCanvas props are no longer reactive and are marked as @readonly. These props were used to initialize the WebGL context and cannot be changed after the renderer is created without recreating the entire renderer and replacing the canvas element.

Props that lost reactivity:

  • alpha - WebGL context alpha buffer setting
  • depth - Depth buffer configuration
  • stencil - Stencil buffer configuration
  • antialias - Anti-aliasing setting
  • logarithmicDepthBuffer - Logarithmic depth buffer setting
  • preserveDrawingBuffer - Drawing buffer preservation
  • powerPreference - GPU power preference (default, high-performance, low-power)
  • failIfMajorPerformanceCaveat - Performance caveat handling

Props that remain reactive:

  • shadows - Shadow rendering
  • clearColor - Background clear color
  • clearAlpha - Clear color opacity
  • toneMapping - Tone mapping technique
  • shadowMapType - Shadow map type
  • toneMappingExposure - Tone mapping exposure
  • outputColorSpace - Output color space
  • useLegacyLights - Legacy lights system
  • renderMode - Render mode setting
  • dpr - Device pixel ratio

Why We Changed It

WebGL context initialization parameters must be set when the WebGL context is created. These settings are passed directly to the WebGL renderer constructor and cannot be modified without recreating the entire renderer, which would be expensive and disruptive.

Migration Steps

  1. Set context initialization props as static values:

    <script setup>
    // ✅ These props should be set once and not changed
    const rendererSettings = {
    antialias: true,
    alpha: false,
    powerPreference: 'high-performance'
    }
    </script>
    
    <template>
    <!-- ✅ Use static values for WebGL context props -->
    <TresCanvas
    :antialias="rendererSettings.antialias"
    :alpha="rendererSettings.alpha"
    :power-preference="rendererSettings.powerPreference"
    >
    <!-- Your scene -->
    </TresCanvas>
    </template>
    
  2. For conditional rendering based on device capabilities:

    <script setup>
    // ✅ Determine settings once based on device capabilities
    const isHighPerformanceDevice = () => {
    // Your device detection logic
    return navigator.hardwareConcurrency > 4
    }
    
    const contextSettings = {
    antialias: isHighPerformanceDevice(),
    powerPreference: isHighPerformanceDevice() ? 'high-performance' : 'default'
    }
    </script>
    
    <template>
    <TresCanvas v-bind="contextSettings">
    <!-- Your scene -->
    </TresCanvas>
    </template>
    
  3. Update dynamic renderer options:

    <script setup>
    // ✅ These props can still be changed dynamically
    const shadowsEnabled = ref(true)
    const currentToneMapping = ref(ACESFilmicToneMapping)
    
    const toggleShadows = () => {
    shadowsEnabled.value = !shadowsEnabled.value
    }
    </script>
    
    <template>
    <TresCanvas
    :shadows="shadowsEnabled"
    :tone-mapping="currentToneMapping"
    >
    <!-- Your scene -->
    </TresCanvas>
    </template>
    

Summary

These breaking changes represent a major architectural improvement in TresJS v5, focusing on:

  • Modern ESM standards
  • Better TypeScript support
  • Improved performance
  • More predictable behavior
  • Cleaner API surface

Take your time migrating and test thoroughly. The new APIs provide better developer experience and performance once migrated.