How am I supposed to use the DefinitelyTyped type definitions with three.module.js?

Since the non-module version is sadly going to be deprecated, I have been looking into using the module version. I rarely use modules because they are not supported in what I do normally.
I’ve just spent hours trying to get the type definitions from DefinitelyTyped to work. And failed. It is a major headache.

I have three.module.js from the downloadable three.js-master.zip archive, and have been using that successfully with plain javascript.
It’s one file that just works when imported: import * as THREE from "./three.module.js".

I wish the type definitions were like that, but they’re hundreds of files.
I have been able to successfully use the non-module version with typescript. It works by dropping the type definition files in the project, since the compiler will pretty much just see it and apply it globally.

But for the module version, in the end I was not able to convince the typescript compiler to use the type definition files:
Could not find a declaration file for module './three.module.js'

This is the import statement that I believe should work, given that I do not use an import map (even if I did, tsc wouldn’t know about it), and am just using “three.module.js” directly:

import * as THREE from "./three.module.js";

This is the folder structure that I believe should work:

/
    tsconfig.json
    three.module.js
    my_script.ts
   (my_script.js)
    @types/
        three/
            index.d.ts
            package.json
            ...
            build/
                three.d.cts
                three.d.ts
                three.module.d.ts
            src/
                ...
            node_modules/
                ...
            examples/
                ...

Summary of tsconfig.json:

"target": "es2020"
"typeRoots": [ "./@types" ]
"esModuleInterop": true
"forceConsistentCasingInFileNames": true
"strict": true
"skipLibCheck": true

I’ve tried debugging it with tsc --traceResolution, but that spits out over 7000 lines. I don’t see how a normal user can be expected to be analyzing all of that.

Among the last lines:

======== Resolving module '../../../src/Three' from '<REDACTED>/@types/three/examples/jsm/webxr/XRHandPrimitiveModel.d.ts'. ========
Resolution for module '../../../src/Three' was found in cache from location '<REDACTED>/@types/three/examples/jsm/webxr'.
======== Module name '../../../src/Three' was successfully resolved to '<REDACTED>/@types/three/src/Three.d.ts'. ========

It looks like it’s finding something at least, so I assume the .d.ts files are in the right place.

I don’t really know where to look. What’s the file structure supposed to be? What’s a minimal working example?

You install like this

npm install three
npm install @types/three

Then in your script you import like this

import * as THREE from 'three'

I have a boilerplate that you can start from if you want to quickly see something that works.

git clone https://github.com/Sean-Bradley/Three.js-TypeScript-Boilerplate.git
cd Three.js-TypeScript-Boilerplate
git checkout statsgui
npm install
npm run dev

Then visit http://localhost:8080/

1 Like

The reason why I wanted to avoid doing it that way is this: It’s like when I have milk, and want to add pudding powder to make pudding, but I can’t quite figure out how. And I’m being told to order and install an entire new kitchen into my house.

Without this, both development and deployment are as simple as can be - drop a single file in and reference that. Or, use a single CDN link if you’re feeling risky. It’s like the three.js team did everything they could to make this as painless as possible.

It seems insane to me to add such overhead with additional points of failure, which can even affect and potentially break production, just because I want typings during development.

Fascinatingly though, import * as THREE from "three" now works (without actually installing three itself), and it’s correctly using the type definition files.

The difference to yesterday is that since I reinstalled it using the most naïve possible way, the “@types” directory now ended up as a subdirectory in a “node_modules” directory. I did not expect that - it seems "typeRoots" did not quite do what it says on the tin.

I then use an import map in the HTML to make it actually run.

2 Likes

The reason why I wanted to avoid doing it that way is

you normally install dependencies with npm, you consume npm with a tool, you use import ... from "three" because the tool will resolve the correct target. there is so much that can go wrong by going against that. typescript is a node tool, both three and @types/three are npm packages — what you have there above won’t give you freedom, it will just put a fork into each and every thing you’re attempting to do, or make stuff accidental like the types that suddenly started to work, you’ll have that tomorrow with something else. :man_shrugging:

i know threejs was quite stubborn, but even their docs will point you to vite and npm now.

1 Like