refactor(web): code-split the three.js stack out of the main bundle

This commit is contained in:
2026-08-10 09:12:38 +08:00
parent 45bf362fbc
commit 6502fdae4f
10 changed files with 600 additions and 571 deletions
@@ -0,0 +1,103 @@
import { Suspense, useLayoutEffect } from 'react';
import { useLoader } from '@react-three/fiber';
import { useTexture } from '@react-three/drei';
import type { TTSObject } from '@tts/shared';
import * as THREE from 'three';
import type { Object3D } from 'three';
import { assetUrl } from '@tts/http';
import { FlexibleModelLoader } from './flexibleModelLoader';
import { objectTint, tintedColor } from './sharedResources';
/**
* The mesh content for a custom model, exported so the full-setup view can
* compose it into a shared scene. Renders the model from `CustomMesh.MeshURL`
* (or a neutral box placeholder when absent).
*/
export function CustomModelMesh({ object }: { object: TTSObject }) {
const meshUrl = object.CustomMesh?.MeshURL;
const tint = objectTint(object);
if (!meshUrl) {
return (
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={tintedColor(new THREE.Color('#52525b'), tint)} />
</mesh>
);
}
return (
<Suspense fallback={null}>
<Model
meshUrl={meshUrl}
diffuseUrl={object.CustomMesh?.DiffuseURL}
tint={tint}
/>
</Suspense>
);
}
function Model({
meshUrl,
diffuseUrl,
tint,
}: {
meshUrl: string;
diffuseUrl?: string;
tint: THREE.Color;
}) {
const root = useLoader(FlexibleModelLoader, assetUrl(meshUrl));
return (
<>
<primitive object={root} scale={0.5} />
{diffuseUrl && <DiffuseTexture root={root} url={diffuseUrl} />}
<Tint root={root} tint={tint} />
</>
);
}
// Apply the object's tint to every material on the loaded model, multiplying
// the existing color. Rendered after the model so it runs once the materials
// exist.
function Tint({ root, tint }: { root: Object3D; tint: THREE.Color }) {
useLayoutEffect(() => {
root.traverse((child) => {
const mesh = child as THREE.Mesh;
if (mesh.isMesh) {
const material = Array.isArray(mesh.material)
? mesh.material[0]
: mesh.material;
if (material && 'color' in material) {
(material as THREE.MeshStandardMaterial).color.multiply(tint);
material.needsUpdate = true;
}
}
});
}, [root, tint]);
return null;
}
// Rendered inside the Canvas so `useTexture` can access the R3F store. Only
// mounted when a diffuse URL exists, so the hook count stays consistent.
function DiffuseTexture({ root, url }: { root: Object3D; url: string }) {
const texture = useTexture(assetUrl(url));
// Apply the diffuse texture to every mesh material on the loaded model.
useLayoutEffect(() => {
root.traverse((child) => {
const mesh = child as THREE.Mesh;
if (mesh.isMesh) {
const material = Array.isArray(mesh.material)
? mesh.material[0]
: mesh.material;
if (material && 'map' in material) {
material.map = texture;
material.needsUpdate = true;
}
}
});
}, [root, texture]);
return null;
}