Without a bundler, an import map (in every major browser since March 2023) says what 'three' means, and an entry ending in / maps every add-on path. Pin one version from one CDN in both: a second copy of the core logs WARNING: Multiple instances of Three.js 30,815 being imported. This complete program spins a book:
<style>body { margin: 0; }</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>
<script type="module">
import * as THREE from 'three';
import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
document.body.append(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, innerWidth / innerHeight, 0.1, 20);
camera.position.z = 2;
const book = new THREE.Mesh(new RoundedBoxGeometry(0.42, 0.98, 0.12, 4, 0.02),
new THREE.MeshNormalMaterial()); // colored by its normals: no lights needed
scene.add(book);
renderer.setAnimationLoop((time) => { // Section 5.2.4
book.rotation.set(0.4, time / 1000, 0.2);
renderer.render(scene, camera);
});
</script>The core is 2.1 MB, 400 KB with Brotli. Serve pages over HTTP (npx serve .), not file://. The chapter's figures load three-map.js from demos/ch05, which inserts this map, and set window.__done after drawing.