TypeScript + Three.js без кроку збірки — Import Maps, @types/three та типові пастки

Інструменти збірки корисні, але не обов'язкові. Із сучасними import maps та ES-модулями можна отримати повну типобезпеку Three.js — автодоповнення, помилки типів, перехід до визначення — прямо в VS Code без жодної команди npm run build.

1. Import Maps для ES-модулів без збірки

Import map — це блок <script type="importmap"> у вашому HTML, який зіставляє «голі» специфікатори модулів (наприклад 'three') з реальними URL-адресами. Усі сучасні браузери підтримують це нативно:

<script type="importmap">
{
  "imports": {
    "three":          "https://cdn.jsdelivr.net/npm/three@0.160/build/three.module.js",
    "three/addons/":  "https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/"
  }
}
</script>

<script type="module">
// ✅ Працює прямо в браузері — бандлер не потрібен
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const renderer = new THREE.WebGLRenderer({ antialias: true });
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
// ...
</script>

2. Типи TypeScript без tsc

Мовний сервер TypeScript у VS Code (Pylance / tsserver) читає визначення типів навіть для звичайних файлів .js, якщо додати jsconfig.json або коментар // @ts-check. Встановіть пакет типів локально (лише для редактора — він не постачається користувачам) і вкажіть на нього у своєму конфігу:

# У директорії вашого проєкту:
npm install --save-dev three @types/three

# Це встановлює лише в node_modules — нічого не пакується й не постачається
// jsconfig.json — вмикає типи у VS Code для файлів .js
{
  "compilerOptions": {
    "checkJs": true,
    "strict": false,
    "moduleResolution": "bundler",
    "types": ["three"]
  },
  "include": ["**/*.js"],
  "exclude": ["node_modules"]
}

Тепер VS Code знає повне дерево типів Three.js. Ви отримуєте автодоповнення, документацію при наведенні та помилки типів у файлах JavaScript — без tsconfig.json і без кроку компіляції.

3. JSDoc як альтернатива типізованому JavaScript

Якщо потрібна нульова залежність від Node.js (жодного package.json), можна додати типи через анотації JSDoc і посилатися на визначення типів із CDN:

// @ts-check
/// <reference types="https://cdn.jsdelivr.net/npm/@types/three/index.d.ts" />

/** @type {import('three').PerspectiveCamera} */
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);

/**
 * @param {import('three').Mesh} mesh
 * @param {import('three').Vector3} direction
 * @returns {void}
 */
function moveMesh(mesh, direction) {
  mesh.position.add(direction);
}

Посилання на типи через CDN працюють у VS Code 1.88+ із рушієм "typescript.preferences.useAliasesForRenames". VS Code завантажує файл .d.ts один раз і кешує його. Саме такий підхід використовується в усіх симуляціях на цьому сайті — жодних інструментів збірки, повний IntelliSense.

4. Типові пастки з типами в проєктах на Three.js

Порожнє посилання на canvas після DOMContentLoaded
document.getElementById('canvas') повертає HTMLElement | null. Конструктор WebGLRenderer у Three.js очікує HTMLCanvasElement, а не HTMLElement | null. Явно приведіть тип: canvas as HTMLCanvasElement або використайте перевірку-охоронець: if (!(el instanceof HTMLCanvasElement)) return;
Object3D.getObjectByName повертає невідомий тип
scene.getObjectByName('mesh') повертає Object3D | undefined. Якщо ви знаєте, що це Mesh, використайте приведення типу: scene.getObjectByName('mesh') as THREE.Mesh | undefined і обробіть випадок undefined перед доступом до геометрії чи матеріалу.
Властивості матеріалу відсутні в базовому типі
mesh.material.color не компілюється, бо базовий тип Material не має властивості color. Потрібно звузити до конкретного типу: (mesh.material as THREE.MeshStandardMaterial).color.set('#ff0000') або одразу оголосити mesh як THREE.Mesh<THREE.BufferGeometry, THREE.MeshStandardMaterial>.
event.target в обробнику подій — не об'єкт Three.js
При використанні Raycaster властивість object перетину типізована як Object3D, а не як конкретний підтип mesh. Використайте intersection.object instanceof THREE.Mesh як перевірку типу перед доступом до властивостей, специфічних для mesh.
Uniforms у ShaderMaterial типізовані занадто широко
ShaderMaterial.uniforms типізовано як { [uniform: string]: IUniform } — дуже широко. Визначте типізовану константу і розгорніть її в матеріал, щоб отримати автодоповнення для конкретних імен ваших uniform-ів: const uniforms = { uTime: { value: 0 } } as const.

5. Рекомендації щодо строгого режиму

Навіть у налаштуванні лише з JSDoc увімкнення "strict": true у jsconfig.json ловить вищезгадані пастки з null/undefined ще на етапі редагування. Для проєктів на Three.js найкорисніші саме такі підопції строгого режиму: