The manual recommends npm 2,036 with a bundler, Vite 25,978 in its examples: npm install three, then npx vite. The package is ES modules only (require('three') now prints a deprecation warning), and its exports map adds three/addons/*, three/webgpu and three/tsl. It ships no .d.ts files; add @types/three (github.com/three-types/three-ts-types (https://github.com/three-types/three-ts-types 301 )) with the same minor number, and TypeScript 7.0.2's tsc --noEmit catches misuse before the browser does:
import * as THREE from 'three';
const book = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.98, 0.12),
new THREE.MeshStandardMaterial({ color: '#1f5f8b' }));
book.position.set(0, '0.5', 0); // a string where a number belongs
book.material.roughnes = 0.6; // a misspelled propertyOutput
main.ts(4,22): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. main.ts(5,15): error TS2551: Property 'roughnes' does not exist on type 'MeshStandardMaterial'. Did you mean 'roughness'?
Plain JavaScript runs both lines silently: the string reaches the world matrix, and a later position.y += 0.1 makes the height '0.50.1'.
<!doctype html>
<style>
body { margin: 0; background: #f5f1e8; font: 13px system-ui, sans-serif; color: #263238; }
canvas { display: block; max-width: 100%; }
pre { margin: 6px 8px; font-size: 12px; background: #fff; padding: 6px; border-left: 4px solid #b5452f; max-width: 570px; white-space: pre-wrap; }
</style>
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<pre id="log"></pre>
<script type="module">
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 260);
document.body.prepend(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color('#f5f1e8');
const camera = new THREE.PerspectiveCamera(35, 600 / 260, 0.1, 50);
camera.position.set(0, 0.5, 3.4);
camera.lookAt(0, 0.4, 0);
scene.add(new THREE.HemisphereLight('#ffffff', '#8d6e63', 1.2));
const sun = new THREE.DirectionalLight('#ffffff', 2.5);
sun.position.set(2, 3, 4);
scene.add(sun);
const makeBook = (color, x) => {
const book = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.98, 0.12),
new THREE.MeshStandardMaterial({ color }));
book.position.x = x;
scene.add(book);
return book;
};
const good = makeBook('#1f5f8b', -0.5);
const bad = makeBook('#b5452f', 0.5);
good.position.set(-0.5, 0.5, 0);
bad.position.set(0.5, '0.5', 0); // a string where a number belongs: JavaScript accepts it
bad.material.roughnes = 0.6; // a misspelled property: silently ignored
// Lift both books by 0.1 m: for the string, += concatenates text
good.position.y += 0.1;
bad.position.y += 0.1;
const log = document.getElementById('log');
log.textContent =
`blue: position.y = ${good.position.y} (${typeof good.position.y})\n` +
`red: position.y = '${bad.position.y}' (${typeof bad.position.y}) → matrix NaN, book not drawn\n\n` +
`tsc --noEmit with @types/three would report:\n` +
` error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.\n` +
` error TS2551: Property 'roughnes' does not exist on type 'MeshStandardMaterial'. Did you mean 'roughness'?`;
// A marker where the red book should be
const ghost = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.98, 0.12),
new THREE.MeshBasicMaterial({ color: '#b5452f', wireframe: true }));
ghost.position.set(0.5, 0.6, 0);
scene.add(ghost);
renderer.setAnimationLoop((time) => {
good.rotation.y = ghost.rotation.y = Math.sin(time / 900) * 0.5;
renderer.render(scene, camera);
});
</script>