@octanejs/three 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -16
- package/UPSTREAM.md +52 -3
- package/package.json +15 -8
- package/src/config.ts +5 -0
- package/src/core/catalogue.ts +31 -2
- package/src/core/driver.ts +299 -15
- package/src/core/hooks.ts +4 -1
- package/src/core/index.ts +16 -0
- package/src/core/portal.ts +187 -82
- package/src/core/root.ts +180 -10
- package/src/core/store.ts +34 -2
- package/src/index.ts +25 -0
- package/src/intrinsics.ts +20 -0
- package/src/scheduling.ts +83 -0
- package/src/web/DOMRegion.ts +58 -0
- package/src/web/dom-region.ts +104 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ An experimental React Three Fiber 9-compatible web renderer for Octane. Octane
|
|
|
4
4
|
keeps ownership of component execution, hooks, context, Suspense, refs, and
|
|
5
5
|
effects; this package supplies the Three-specific host layer.
|
|
6
6
|
|
|
7
|
-
Milestones 0
|
|
7
|
+
Milestones 0–10 are implemented across this package and Octane's renderer SDK
|
|
8
8
|
foundation. The current technical preview includes:
|
|
9
9
|
|
|
10
10
|
- the serializable compiler preset, renderer entry point, renderer-local Three
|
|
@@ -30,12 +30,28 @@ foundation. The current technical preview includes:
|
|
|
30
30
|
client-side Three-to-DOM pending/error projection;
|
|
31
31
|
- same-renderer `createPortal` placement into borrowed `Object3D` targets,
|
|
32
32
|
R3F-shaped state/event enclaves, nested context retention, one shared frame
|
|
33
|
-
loop, physical Three event bubbling, and root-scoped target teardown;
|
|
33
|
+
loop, physical Three event bubbling, and root-scoped target teardown;
|
|
34
|
+
- client-only Canvas SSR that streams and hydrates the existing DOM shell and
|
|
35
|
+
native canvas fallback without executing Three scene setup, constructors, or
|
|
36
|
+
loaders on the server;
|
|
37
|
+
- direct `HTMLCanvasElement` and `OffscreenCanvas` roots, public Octane
|
|
38
|
+
`act`/`flushSync` scheduling, callback-aware `unmountComponentAtNode`, and
|
|
39
|
+
demand-loop invalidation after WebGL context restoration;
|
|
40
|
+
- controlled WebXR session-loop handoff and teardown, plus compatible HMR that
|
|
41
|
+
retains live objects and incompatible `args` edits that reconstruct without
|
|
42
|
+
stale refs, handlers, or resources;
|
|
43
|
+
- the low-level `DOMRegion` Three-to-DOM boundary with an explicit target and
|
|
44
|
+
deterministic DOM ownership; and
|
|
34
45
|
- public behavior, prepared-driver, and same-source compiled scene evidence
|
|
35
|
-
against R3F 9.6.1 with the exact Three r172 oracle
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
against R3F 9.6.1 with the exact Three r172 oracle;
|
|
47
|
+
- a real asynchronous `MessageChannel` renderer proof with structured-cloned
|
|
48
|
+
batches and values, root-scoped resource/portal handles, listener IDs,
|
|
49
|
+
acknowledgement-gated refs/layout, rejection/fault semantics, teardown, and
|
|
50
|
+
native-event delivery; and
|
|
51
|
+
- a checked public export/subpath type matrix, Three r156/current compatibility
|
|
52
|
+
lanes, a packed external consumer, real WebGL creation-failure and context-
|
|
53
|
+
recovery coverage, and semantic-checksummed renderer and shipped-size
|
|
54
|
+
benchmarks.
|
|
39
55
|
|
|
40
56
|
Three deliberate correctness fixes differ from R3F 9.6.1:
|
|
41
57
|
|
|
@@ -66,13 +82,15 @@ export default defineConfig({
|
|
|
66
82
|
```
|
|
67
83
|
|
|
68
84
|
The same serializable `threeRenderers` value can be supplied to the Rsbuild and
|
|
69
|
-
low-level Rspack integrations. Vite and Rsbuild
|
|
70
|
-
SSR
|
|
85
|
+
low-level Rspack integrations. Vite and Rsbuild own the production application
|
|
86
|
+
SSR/hydration lifecycle; the Rspack plugin owns the equivalent client/server
|
|
87
|
+
graph split, compilation, and HMR transforms rather than an application server.
|
|
71
88
|
|
|
72
89
|
The preset selects `@octanejs/three/renderer`, keeps Three scene modules
|
|
73
90
|
client-only on the server, ignores authored text inside scenes, exposes a
|
|
74
91
|
renderer-local intrinsic catalogue without merging Three tags into DOM JSX,
|
|
75
|
-
and declares `Canvas.children` as the DOM-to-Three renderer boundary
|
|
92
|
+
and declares both `Canvas.children` as the DOM-to-Three renderer boundary and
|
|
93
|
+
`DOMRegion.children` as the explicit Three-to-DOM boundary.
|
|
76
94
|
|
|
77
95
|
## Canvas and scene modules
|
|
78
96
|
|
|
@@ -106,9 +124,9 @@ export function Scene() @{
|
|
|
106
124
|
}
|
|
107
125
|
```
|
|
108
126
|
|
|
109
|
-
The low-level API follows Octane's component-plus-props root convention
|
|
110
|
-
|
|
111
|
-
can execute:
|
|
127
|
+
The low-level API follows Octane's component-plus-props root convention and
|
|
128
|
+
accepts either an `HTMLCanvasElement` or an `OffscreenCanvas`. Both synchronous
|
|
129
|
+
and asynchronous renderer factories settle before the component can execute:
|
|
112
130
|
|
|
113
131
|
```ts
|
|
114
132
|
import { createRoot } from '@octanejs/three';
|
|
@@ -119,6 +137,13 @@ root.render(Scene, { color: 'hotpink' });
|
|
|
119
137
|
root.store.getState().advance(1 / 60);
|
|
120
138
|
```
|
|
121
139
|
|
|
140
|
+
Direct-root scheduling uses Octane's public `act` and `flushSync` semantics.
|
|
141
|
+
Call `root.unmount()` directly, or use
|
|
142
|
+
`unmountComponentAtNode(canvas, optionalCallback)` to remove the root registered
|
|
143
|
+
for either canvas kind. The optional callback runs synchronously only after
|
|
144
|
+
teardown completes successfully. Teardown disconnects events and XR, clears the
|
|
145
|
+
animation loop, releases scene resources, and disposes the renderer.
|
|
146
|
+
|
|
122
147
|
Tests can inject the WebGL-free deterministic harness from
|
|
123
148
|
`@octanejs/three/testing`; it drives the same root, host commits, hooks, and
|
|
124
149
|
public `advance()` loop as an application. Its awaitable `fireEvent()` helper
|
|
@@ -136,6 +161,50 @@ that call its own lexical hook slot; keep that compatibility form unconditional
|
|
|
136
161
|
and in stable order. Prefer `useStore(selector, equality?)` or
|
|
137
162
|
`useThree(selector, equality?)` when using Octane's conditional-hook semantics.
|
|
138
163
|
|
|
164
|
+
## Root lifecycle, XR, and HMR
|
|
165
|
+
|
|
166
|
+
When the configured renderer exposes Three's XR event surface, the root listens
|
|
167
|
+
for `sessionstart` and `sessionend`. A presenting session uses
|
|
168
|
+
`renderer.xr.setAnimationLoop`; `frameloop="never"` remains manual, and ending
|
|
169
|
+
the session invalidates the configured non-XR loop. Unmount removes both
|
|
170
|
+
listeners, clears the XR callback, and makes any retained callback inert.
|
|
171
|
+
|
|
172
|
+
WebGL context loss is prevented while the root is live. Context restoration
|
|
173
|
+
invalidates the root so an `always` or `demand` root renders again; teardown
|
|
174
|
+
removes both context listeners before forcing renderer context loss.
|
|
175
|
+
|
|
176
|
+
Universal HMR preserves component state and Three object identity for compatible
|
|
177
|
+
edits. A constructor `args` change reconstructs the affected object, detaches
|
|
178
|
+
and reattaches stable refs, retires old handlers, and disposes the old owned
|
|
179
|
+
resource once. Vite and the Rspack/Rsbuild path emit the same universal HMR
|
|
180
|
+
wrapper behavior.
|
|
181
|
+
|
|
182
|
+
## DOM regions
|
|
183
|
+
|
|
184
|
+
`DOMRegion` is a low-level reverse-renderer boundary for mounting ordinary
|
|
185
|
+
Octane DOM content from a Three scene into an explicit DOM target:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// Scene.three.tsrx
|
|
189
|
+
import { DOMRegion } from '@octanejs/three';
|
|
190
|
+
|
|
191
|
+
export function Scene(props) @{
|
|
192
|
+
<DOMRegion target={props.overlayTarget}>
|
|
193
|
+
<button onClick={props.onClick}>Inspect object</button>
|
|
194
|
+
</DOMRegion>
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Each region owns one child container and one DOM root. Updating or moving its
|
|
199
|
+
target preserves that container, DOM state, and node identity; deleting the
|
|
200
|
+
region removes its owned DOM deterministically. The target may be an
|
|
201
|
+
`HTMLElement` or an object ref whose `current` value is an `HTMLElement` or
|
|
202
|
+
`null`.
|
|
203
|
+
|
|
204
|
+
`DOMRegion` is not Drei `Html` and is not the WebXR DOM Overlay API. It provides
|
|
205
|
+
no positioning, occlusion, transforms, styling, or layout contract. Those
|
|
206
|
+
policies belong to future higher-level packages.
|
|
207
|
+
|
|
139
208
|
## Portals
|
|
140
209
|
|
|
141
210
|
`createPortal` keeps its children in the authored Octane owner/context tree but
|
|
@@ -201,15 +270,30 @@ useLoader.clear(GLTFLoader, '/model.glb');
|
|
|
201
270
|
the resolved asset. Declarative Three resources remain owned by their mounted
|
|
202
271
|
host tree, while objects passed through `primitive` remain caller-owned. A
|
|
203
272
|
root-level Three suspension or render error is projected through `Canvas` to
|
|
204
|
-
the nearest client DOM `@pending` or `@catch` arm.
|
|
205
|
-
and
|
|
273
|
+
the nearest client DOM `@pending` or `@catch` arm. On the server, `Canvas`
|
|
274
|
+
streams its DOM shell and native `<canvas>` fallback without evaluating the
|
|
275
|
+
client-only scene. Hydration adopts that shell before measurement creates one
|
|
276
|
+
fresh Three root on the client.
|
|
206
277
|
|
|
207
278
|
## Compatibility target
|
|
208
279
|
|
|
209
280
|
The compatibility baseline is `@react-three/fiber@9.6.1` at commit
|
|
210
281
|
`2a528745e9aa7c9e6cca41e404b59d45cf0d0cc7`, with `three@0.172.0` as the exact
|
|
211
|
-
behavioral oracle.
|
|
212
|
-
|
|
282
|
+
behavioral and differential oracle. The published peer range is
|
|
283
|
+
`three >=0.156.0`, guarded by minimum-r156 and current-release CI lanes; the
|
|
284
|
+
r172 lane remains immutable so a moving current dependency cannot change the
|
|
285
|
+
parity oracle. TypeScript consumers must install `@types/three` from the same
|
|
286
|
+
Three release line explicitly; its patch revision may differ from the runtime.
|
|
287
|
+
It is an optional peer so a package manager cannot silently auto-install current
|
|
288
|
+
declarations beside an older supported runtime:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
pnpm add three@0.156.0
|
|
292
|
+
pnpm add -D @types/three@0.156.0
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
React Native/Expo, R3F 10's WebGPU/TSL APIs, and Drei are separate follow-on
|
|
296
|
+
efforts.
|
|
213
297
|
|
|
214
298
|
See [`docs/three-port-plan.md`](../../docs/three-port-plan.md) for the delivery
|
|
215
299
|
phases and [`UPSTREAM.md`](./UPSTREAM.md) for source and license provenance.
|
package/UPSTREAM.md
CHANGED
|
@@ -8,9 +8,15 @@ This port targets the immutable React Three Fiber release `v9.6.1`:
|
|
|
8
8
|
- exact Three behavior and type oracle: `three@0.172.0` and
|
|
9
9
|
`@types/three@0.172.0`.
|
|
10
10
|
|
|
11
|
-
The
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
The port advertises the upstream `three >=0.156` range. Its immutable
|
|
12
|
+
differential oracle stays on r172, while separate CI jobs typecheck and run the
|
|
13
|
+
Octane-owned compatibility suite against the minimum r156 pair and the current
|
|
14
|
+
registry release. This prevents either a moving latest dependency or the pinned
|
|
15
|
+
oracle from standing in for the other compatibility claim. `@types/three` is an
|
|
16
|
+
optional peer that TypeScript consumers install from the same Three release line;
|
|
17
|
+
its patch revision may differ from the runtime. Making it optional prevents
|
|
18
|
+
package-manager peer auto-install from silently pairing current declarations with
|
|
19
|
+
an older supported runtime.
|
|
14
20
|
|
|
15
21
|
## Source boundary
|
|
16
22
|
|
|
@@ -51,6 +57,49 @@ events bubble through physical Three ancestry. Octane's universal portal range
|
|
|
51
57
|
retains logical context, error, effect, and scheduling ownership without
|
|
52
58
|
embedding React Reconciler or representing the target as a fake host child.
|
|
53
59
|
|
|
60
|
+
Milestone 7 follows the pinned web `Canvas` contract: the server emits the
|
|
61
|
+
ordinary DOM wrapper, canvas, and native fallback while omitting Three children.
|
|
62
|
+
Hydration adopts those DOM nodes, then positive client measurement creates one
|
|
63
|
+
fresh Three root. Scene setup, constructors, and loaders remain outside the
|
|
64
|
+
server execution path; client Three suspension and errors project through the
|
|
65
|
+
owning DOM boundary, and deleting the Canvas tears down the nested root.
|
|
66
|
+
|
|
67
|
+
Milestone 8 follows the pinned direct-root, HMR, and XR lifecycle contracts while
|
|
68
|
+
keeping scheduling under Octane. Direct roots accept `HTMLCanvasElement` and
|
|
69
|
+
`OffscreenCanvas`, expose Octane's `act` and `flushSync`, and support
|
|
70
|
+
callback-aware `unmountComponentAtNode`. Octane completes that teardown and
|
|
71
|
+
callback synchronously instead of retaining R3F's 500-millisecond delay.
|
|
72
|
+
Compatible universal HMR edits retain Three objects; constructor `args` edits
|
|
73
|
+
reconstruct them with ref, handler, and owned-resource cleanup. A controlled WebXR session hands rendering to
|
|
74
|
+
`setAnimationLoop`, respects manual `frameloop="never"`, restores the configured
|
|
75
|
+
loop on session end, and disconnects without leaving a live callback. WebGL
|
|
76
|
+
context restoration invalidates the live root before teardown removes its
|
|
77
|
+
listeners.
|
|
78
|
+
|
|
79
|
+
Milestone 9 validates the renderer vocabulary independently of an in-process
|
|
80
|
+
Three host. A real asynchronous `MessageChannel` carries structured-cloned host
|
|
81
|
+
batches to a separate object driver and returns versioned acknowledgement,
|
|
82
|
+
completion, rejection, fault, and event messages. Logical topology, lifecycle,
|
|
83
|
+
refs, layout effects, and teardown publish only after acknowledgement; pre-ack
|
|
84
|
+
rejection remains retryable, while post-ack faults retain the accepted commit.
|
|
85
|
+
The existing portal proof separately verifies that only a root-scoped portal
|
|
86
|
+
handle, never the raw target, enters a cloned batch.
|
|
87
|
+
|
|
88
|
+
Milestone 10 hardens the release claim with an existence-checked 90-export/
|
|
89
|
+
157-test crosswalk, public export and package-subpath type matrices, r156 and
|
|
90
|
+
current Three compatibility lanes, and an external packed consumer that builds
|
|
91
|
+
and executes the published package without React. Chromium coverage exercises
|
|
92
|
+
real WebGL construction failure and `WEBGL_lose_context` loss/restoration.
|
|
93
|
+
Renderer lifecycle/event benchmarks and minimal/full-catalogue shipped-size
|
|
94
|
+
benchmarks compare Octane Three, R3F 9.6.1, and direct Three behind semantic
|
|
95
|
+
checksums and committed ratio guards.
|
|
96
|
+
|
|
97
|
+
`DOMRegion` is an Octane-specific proof of the already-compiled Three-to-DOM
|
|
98
|
+
renderer boundary, so it is not part of the pinned R3F public-export inventory.
|
|
99
|
+
It mounts one DOM root under an explicit target and preserves that root while
|
|
100
|
+
the target moves. It is not Drei `Html`, is not the WebXR DOM Overlay API, and
|
|
101
|
+
defines no positioning, occlusion, styling, or layout behavior.
|
|
102
|
+
|
|
54
103
|
## License provenance
|
|
55
104
|
|
|
56
105
|
React Three Fiber is MIT-licensed, Copyright 2019–2025 Poimandres. Adapted
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/three",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -45,18 +45,24 @@
|
|
|
45
45
|
"./intrinsics/jsx-runtime": "./src/intrinsics.ts"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@types/three": "0.172.0",
|
|
49
48
|
"zustand": "^5.0.0"
|
|
50
49
|
},
|
|
51
50
|
"peerDependencies": {
|
|
52
|
-
"three": ">=0.
|
|
53
|
-
"
|
|
51
|
+
"@types/three": ">=0.156.0",
|
|
52
|
+
"three": ">=0.156.0",
|
|
53
|
+
"octane": "0.1.9"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@types/three": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
54
59
|
},
|
|
55
60
|
"devDependencies": {
|
|
56
61
|
"@react-three/fiber": "9.6.1",
|
|
57
62
|
"@rsbuild/core": "^2.1.5",
|
|
58
63
|
"@rspack/core": "^2.1.3",
|
|
59
64
|
"@tsrx/react": "^0.2.37",
|
|
65
|
+
"@types/three": "0.172.0",
|
|
60
66
|
"esbuild": "^0.28.1",
|
|
61
67
|
"playwright": "^1.61.0",
|
|
62
68
|
"react": "^19.2.0",
|
|
@@ -65,13 +71,14 @@
|
|
|
65
71
|
"typescript": "^5.9.3",
|
|
66
72
|
"vite": "^8.0.16",
|
|
67
73
|
"vitest": "^4.1.9",
|
|
68
|
-
"@octanejs/rsbuild-plugin": "0.1.
|
|
69
|
-
"@octanejs/rspack-plugin": "0.1.
|
|
70
|
-
"
|
|
71
|
-
"
|
|
74
|
+
"@octanejs/rsbuild-plugin": "0.1.4",
|
|
75
|
+
"@octanejs/rspack-plugin": "0.1.4",
|
|
76
|
+
"@octanejs/vite-plugin": "0.1.9",
|
|
77
|
+
"octane": "0.1.9"
|
|
72
78
|
},
|
|
73
79
|
"scripts": {
|
|
74
80
|
"test": "pnpm upstream:check && vitest run --root ../.. --project three",
|
|
81
|
+
"test:compat": "pnpm upstream:check && vitest run --root ../.. --project three --exclude 'packages/three/tests/**/*differential.test.ts' --exclude 'packages/three/tests/browser/**/*.test.ts'",
|
|
75
82
|
"upstream:check": "node scripts/check-upstream-crosswalk.mjs"
|
|
76
83
|
}
|
|
77
84
|
}
|
package/src/config.ts
CHANGED
package/src/core/catalogue.ts
CHANGED
|
@@ -41,6 +41,10 @@ type ArgsProp<T extends ConstructorRepresentation> =
|
|
|
41
41
|
export type ThreeAttachFunction<T = unknown> = (parent: unknown, self: T) => void | (() => void);
|
|
42
42
|
|
|
43
43
|
export type Attach<T = unknown> = string | ThreeAttachFunction<T>;
|
|
44
|
+
/** R3F-compatible name for a function attachment, adapted to Octane cleanup callbacks. */
|
|
45
|
+
export type AttachFnType<T = unknown> = ThreeAttachFunction<T>;
|
|
46
|
+
/** R3F-compatible name for the public attachment union. */
|
|
47
|
+
export type AttachType<T = unknown> = Attach<T>;
|
|
44
48
|
export type ThreeKey = string | number | symbol | bigint;
|
|
45
49
|
|
|
46
50
|
export type ThreeRef<T> =
|
|
@@ -100,15 +104,40 @@ export type MathType<T extends MathTypes> = T extends THREE.Color
|
|
|
100
104
|
? T | MutableOrReadonlyParameters<T['set']> | number
|
|
101
105
|
: T | MutableOrReadonlyParameters<T['set']>;
|
|
102
106
|
|
|
103
|
-
type MathProps<T> = {
|
|
107
|
+
export type MathProps<T> = {
|
|
104
108
|
[K in keyof T as T[K] extends MathTypes ? K : never]: T[K] extends MathTypes
|
|
105
109
|
? MathType<T[K]>
|
|
106
110
|
: never;
|
|
107
111
|
};
|
|
108
112
|
|
|
113
|
+
export type Vector2 = MathType<THREE.Vector2>;
|
|
114
|
+
export type Vector3 = MathType<THREE.Vector3>;
|
|
115
|
+
export type Vector4 = MathType<THREE.Vector4>;
|
|
116
|
+
export type Color = MathType<THREE.Color>;
|
|
117
|
+
export type Layers = MathType<THREE.Layers>;
|
|
118
|
+
export type Quaternion = MathType<THREE.Quaternion>;
|
|
119
|
+
export type Euler = MathType<THREE.Euler>;
|
|
120
|
+
export type Matrix3 = MathType<THREE.Matrix3>;
|
|
121
|
+
export type Matrix4 = MathType<THREE.Matrix4>;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* R3F-compatible authored-node props with Octane refs and renderable children.
|
|
125
|
+
* The historical name is retained for source compatibility without importing
|
|
126
|
+
* React or exposing React ownership semantics.
|
|
127
|
+
*/
|
|
128
|
+
export interface ReactProps<T> {
|
|
129
|
+
children?: unknown;
|
|
130
|
+
ref?: ThreeRef<T>;
|
|
131
|
+
key?: ThreeKey;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type ElementProps<T extends ConstructorRepresentation, P = InstanceType<T>> = Partial<
|
|
135
|
+
Overwrite<P, MathProps<P> & ReactProps<P> & EventProps<P>>
|
|
136
|
+
>;
|
|
137
|
+
|
|
109
138
|
export type ThreeElement<T extends ConstructorRepresentation> = Mutable<
|
|
110
139
|
Overwrite<
|
|
111
|
-
|
|
140
|
+
ElementProps<T, Properties<InstanceType<T>>>,
|
|
112
141
|
ArgsProp<T> & ThreeInstanceProps<InstanceType<T>> & EventProps<InstanceType<T>>
|
|
113
142
|
>
|
|
114
143
|
>;
|