npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@octanejs/three

v0.1.27

Published

React Three Fiber-compatible web bindings for Octane's universal renderer.

Readme

@octanejs/three

An experimental React Three Fiber 9-compatible web renderer for Octane. Octane keeps ownership of component execution, hooks, context, Suspense, refs, and effects; this package supplies the Three-specific host layer.

Milestones 0–10 are implemented across this package and Octane's renderer SDK foundation. The current technical preview includes:

  • the serializable compiler preset, renderer entry point, renderer-local Three intrinsic types, and pinned upstream export/test crosswalk;
  • the Three constructor catalogue, object and constructor-component extend forms, primitive, and constructor args;
  • real Three host objects with prop diff/application, automatic, string, and function attachments, ordered placement and moves, reconstruction, retained visibility, lifecycle/ref delivery, and ownership-aware disposal; and
  • the DOM Canvas boundary, programmatic roots, promise-returning renderer configuration, the callable root store, camera/scene/raycaster setup, resize/DPR/viewport state, shadows and color configuration;
  • one shared always/demand/never frame loop, global frame effects, useStore, useThree, useFrame, useGraph, managed-instance helpers, and the deterministic @octanejs/three/testing harness;
  • R3F-compatible ray and pointer events, including 3D bubbling, hit ordering, propagation, hover transitions, missed clicks, pointer capture, custom event managers, external DOM sources, and coordinate prefixes;
  • a Suspense-aware useLoader cache with constructor and instance loaders, scalar and array inputs, extensions, progress, GLTF graph augmentation, preload, clear, and cached error routing;
  • retained Three Suspense and Activity visibility, ownership-safe teardown, and client-side Three-to-DOM pending/error projection;
  • same-renderer createPortal placement into borrowed Object3D targets, R3F-shaped state/event enclaves, nested context retention, one shared frame loop, physical Three event bubbling, and root-scoped target teardown;
  • client-only Canvas SSR that streams and hydrates the existing DOM shell and native canvas fallback without executing Three scene setup, constructors, or loaders on the server;
  • direct HTMLCanvasElement and OffscreenCanvas roots, public Octane act/flushSync scheduling, callback-aware unmountComponentAtNode, and demand-loop invalidation after WebGL context restoration;
  • controlled WebXR session-loop handoff and teardown, plus compatible HMR that retains live objects and incompatible args edits that reconstruct without stale refs, handlers, or resources;
  • the low-level DOMRegion Three-to-DOM boundary with an explicit target and deterministic DOM ownership; and
  • public behavior, prepared-driver, and same-source compiled scene evidence against R3F 9.6.1 with the exact Three r172 oracle;
  • a real asynchronous MessageChannel renderer proof with structured-cloned batches and values, root-scoped resource/portal handles, listener IDs, acknowledgement-gated refs/layout, rejection/fault semantics, teardown, and native-event delivery; and
  • a checked public export/subpath type matrix, Three r156/current compatibility lanes, a packed external consumer, real WebGL creation-failure and context- recovery coverage, and semantic-checksummed renderer and shipped-size benchmarks.

Three deliberate correctness fixes differ from R3F 9.6.1:

  • removing a pierced prop such as material-color resets the nested material property, instead of writing the default to a same-named leaf on the root object; and
  • reconstructing a captured or hovered object rewrites every stored intersection to the replacement, so subsequent captured events reach the live handler instead of retaining the retired object; and
  • retained Activity subtrees are excluded from recursive raycasts while hidden, rather than allowing an interactive visible ancestor to pierce an invisible descendant.

Compiler configuration

DOM remains the application renderer. Scene modules opt into Three through the shared renderer preset and the *.three.tsrx convention:

import { defineConfig } from '@octanejs/vite-plugin';
import { threeRenderers } from '@octanejs/three/config';

export default defineConfig({
	compiler: {
		renderers: threeRenderers,
	},
});

The same serializable threeRenderers value can be supplied to the Rsbuild and low-level Rspack integrations. Vite and Rsbuild own the production application SSR/hydration lifecycle; the Rspack plugin owns the equivalent client/server graph split, compilation, and HMR transforms rather than an application server.

The preset selects @octanejs/three/renderer, keeps Three scene modules client-only on the server, ignores authored text inside scenes, exposes a renderer-local intrinsic catalogue without merging Three tags into DOM JSX, and declares both Canvas.children as the DOM-to-Three renderer boundary and DOMRegion.children as the explicit Three-to-DOM boundary.

Canvas and scene modules

The application remains a normal DOM Octane app. Only scene modules use the .three.tsrx convention:

// App.tsrx
import { Canvas } from '@octanejs/three';
import { Scene } from './Scene.three.tsrx';

export function App() @{
	<Canvas frameloop="demand">
		<Scene />
	</Canvas>
}
// Scene.three.tsrx
import { useFrame } from '@octanejs/three';
import { useRef } from '@octanejs/three/renderer';

export function Scene() @{
	const mesh = useRef(null);
	useFrame((_state, delta) => (mesh.current.rotation.x += delta));
	<mesh ref={mesh}>
		<boxGeometry args={[1, 1, 1]} />
		<meshBasicMaterial color="hotpink" />
	</mesh>
}

The low-level API follows Octane's component-plus-props root convention and accepts either an HTMLCanvasElement or an OffscreenCanvas. Both synchronous and asynchronous renderer factories settle before the component can execute:

import { createRoot } from '@octanejs/three';

const root = createRoot(canvas);
await root.configure({ frameloop: 'never', dpr: 1 });
root.render(Scene, { color: 'hotpink' });
root.store.getState().advance(1 / 60);

Compiled Three scene modules import and register only the built-in constructors used by their authored intrinsic tags, so direct roots do not retain the entire Three namespace. Both extension forms remain selective: extend({ CustomMesh }) registers the named constructor, while extend(CustomMesh) returns a stable callable component without registering unrelated built-ins. An explicit extension always takes precedence over a compiler-registered constructor.

Canvas registers the complete Three namespace when it first renders, retaining the familiar R3F-compatible catalogue for dynamic or otherwise uncompiled scene content. Code that constructs low-level universal host plans by hand instead of compiling a .three.tsrx scene must register each constructor itself before rendering, for example extend({ Mesh }) for a manually authored mesh plan.

Direct-root scheduling uses Octane's public act and flushSync semantics. Call root.unmount() directly, or use unmountComponentAtNode(canvas, optionalCallback) to remove the root registered for either canvas kind. The optional callback runs synchronously only after teardown completes successfully. Teardown disconnects events and XR, clears the animation loop, releases scene resources, and disposes the renderer.

Tests can inject the WebGL-free deterministic harness from @octanejs/three/testing; it drives the same root, host commits, hooks, and public advance() loop as an application. Its awaitable fireEvent() helper directly invokes the latest committed handler and settles scheduled work when raycasting itself is not under test.

Canvas installs the default web event manager. Use eventSource to subscribe through another element and eventPrefix (offset, client, page, layer, or screen) to choose the coordinate pair. Programmatic roots can supply a custom events(store) manager factory and update it through state.setEvents().

useStore() returns the upstream-compatible callable store. Because a later store(selector) call is a dynamic function call, the compiler cannot assign that call its own lexical hook slot; keep that compatibility form unconditional and in stable order. Prefer useStore(selector, equality?) or useThree(selector, equality?) when using Octane's conditional-hook semantics.

Root lifecycle, XR, and HMR

When the configured renderer exposes Three's XR event surface, the root listens for sessionstart and sessionend. A presenting session uses renderer.xr.setAnimationLoop; frameloop="never" remains manual, and ending the session invalidates the configured non-XR loop. Unmount removes both listeners, clears the XR callback, and makes any retained callback inert.

WebGL context loss is prevented while the root is live. Context restoration invalidates the root so an always or demand root renders again; teardown removes both context listeners before forcing renderer context loss.

Universal HMR preserves component state and Three object identity for compatible edits. A constructor args change reconstructs the affected object, detaches and reattaches stable refs, retires old handlers, and disposes the old owned resource once. Vite and the Rspack/Rsbuild path emit the same universal HMR wrapper behavior.

DOM regions

DOMRegion is a low-level reverse-renderer boundary for mounting ordinary Octane DOM content from a Three scene into an explicit DOM target:

// Scene.three.tsrx
import { DOMRegion } from '@octanejs/three';

export function Scene(props) @{
	<DOMRegion target={props.overlayTarget}>
		<button onClick={props.onClick}>Inspect object</button>
	</DOMRegion>
}

Each region owns one child container and one DOM root. Updating or moving its target preserves that container, DOM state, and node identity; deleting the region removes its owned DOM deterministically. The target may be an HTMLElement or an object ref whose current value is an HTMLElement or null.

DOMRegion is not Drei Html and is not the WebXR DOM Overlay API. It provides no positioning, occlusion, transforms, styling, or layout contract. Those policies belong to future higher-level packages.

Portals

createPortal keeps its children in the authored Octane owner/context tree but places their Three hosts below a borrowed Object3D. The optional state layer matches R3F's portal model: it has its own scene, raycaster, pointer, and event priority while sharing the outer root's interaction registry and frame loop.

import { createPortal, useThree } from '@octanejs/three';

function Overlay() @{
	const scene = useThree((state) => state.scene);
	<group name={scene.name + '-overlay'} />
}

export function Scene(props) @{
	<>
		{createPortal(<Overlay />, props.overlayTarget, {
			events: { priority: 2 },
		})}
	</>
}

Portal targets are borrowed and never disposed by Octane. A managed target must belong to the same root; a local Object3D target also cannot cross a commit transport. Pointer hits use the portal layer's camera/raycaster, then bubble through physical Object3D.parent ancestry as they do in R3F. Component errors, effects, context, and scheduling continue to follow logical Octane ownership.

Assets, Suspense, and errors

useLoader follows the R3F v9 cache contract. A loader constructor is instantiated once, while an existing loader instance is used directly. The loader identity and normalized input form the cache key; extensions and progress callbacks configure the first request for that key.

import { useLoader } from '@octanejs/three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

function Model() @{
	const gltf = useLoader(GLTFLoader, '/model.glb');
	<primitive object={gltf.scene} />
}

export function Scene() @{
	@try {
		<Model />
	} @pending {
		<group name="loading-model" />
	} @catch (error) {
		<group name={'model-error:' + error.message} />
	}
}

useLoader.preload(GLTFLoader, '/model.glb');
// Later, when the next read must issue a fresh request:
useLoader.clear(GLTFLoader, '/model.glb');

clear evicts the exact cache entry; it does not abort a request or dispose the resolved asset. Declarative Three resources remain owned by their mounted host tree, while objects passed through primitive remain caller-owned. A root-level Three suspension or render error is projected through Canvas to the nearest client DOM @pending or @catch arm. On the server, Canvas streams its DOM shell and native <canvas> fallback without evaluating the client-only scene. Hydration adopts that shell before measurement creates one fresh Three root on the client.

Compatibility target

The compatibility baseline is @react-three/[email protected] at commit 2a528745e9aa7c9e6cca41e404b59d45cf0d0cc7, with [email protected] as the exact behavioral and differential oracle. The published peer range is three >=0.156.0, guarded by minimum-r156 and current-release CI lanes; the r172 lane remains immutable so a moving current dependency cannot change the parity oracle. TypeScript consumers must install @types/three from the same Three release line explicitly; its patch revision may differ from the runtime. It is an optional peer so a package manager cannot silently auto-install current declarations beside an older supported runtime:

pnpm add [email protected]
pnpm add -D @types/[email protected]

React Native/Expo, R3F 10's WebGPU/TSL APIs, and Drei are separate follow-on efforts.

See docs/three-port-plan.md for the delivery phases and UPSTREAM.md for source and license provenance.