Kokodoko
Kokodoko

Reputation: 28128

How can I use ThreeJS ES6 native modules with Typescript?

I am using ThreeJS ES6 native modules directly in the browser. It's a really cool feature where you can just import ThreeJS from your javascript files without any module bundler.

The following works in javascript:

import * as THREE from './lib/three.module.js'
import { OrbitControls } from './lib/OrbitControls.js'

Now I want to do this in Typescript, I installed the typescript definitions using

npm install --save @types/three 

But VS Code still can't find the type declaration files:

⚠️ Could not find a declaration file for module '../lib/three.module.js'

⚠️ Could not find a declaration file for module '../lib/OrbitControls.js'.

As suggested in the comments I changed the imports to

import * as THREE from 'three'
import { OrbitControls } from three/examples/jsm/controls/OrbitControls'

Now the declarations are found, but typescript can't find the modules!

⚠️ An accessor cannot be declared in an ambient context and Failed to resolve module specifier "three".

⚠️ Relative references must start with either "/", "./", or "../".

How can I use ES6 native modules from ThreeJs in my Typescript project without a module bundler?

Upvotes: 3

Views: 3308

Answers (4)

Peter Ehrlich
Peter Ehrlich

Reputation: 7115

Another possible solution from Jamesernator over at the TypeScript Github Issues: just add this to your html:

<script type="importmap">
            {
                "imports": {
                    "three": "./js/lib/three.module.js"
                 }
            }    
 </script>

And the browser will do the remapping. Neat!

https://github.com/microsoft/TypeScript/issues/50600

Upvotes: 1

Coderer
Coderer

Reputation: 27244

You just have to create a .d.ts file with the same name as the library -- in your case, ./lib/three.module.d.ts. When you import * as Three from "./lib/three.module.js", Typescript will look for a sibling typings file as part of the module resolution strategy. In this file, you can simply export types that describe the shape of the module. In your case, you should be fine with just

import * as Three from "three";
export = Three;

Upvotes: 2

Peter Ehrlich
Peter Ehrlich

Reputation: 7115

So it looks like you have to use an alias for declaration files, and aliases are not supported by es6 native import statements. This is not addressed at all in the docs: https://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html

Some people resolve this by adding a whole honkin build pipeline, but that level of complexity was not for me: Typescript declaration file created with alias instead of relative path

Instead I used tsc-watch and a minimal bash script: https://www.npmjs.com/package/tsc-watch

$ tsc-watch --onSuccess "./mysed"

$ cat ./mysed
#!/bin/bash

# use *js to capture .mts as well as .js
sed -i "" "s/import \* as THREE from 'three';/import \* as THREE from '.\/lib\/three\.module\.js';/" js/*js;

This replaces import * as THREE from 'three'; in my .ts with import * as THREE from './lib/three.module.js' in my JS, allowing me to reference the typed version in dev and the non typed version in prod. Whew!

Upvotes: 0

Mugen87
Mugen87

Reputation: 31026

Try to organize your imports like so:

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

Upvotes: 2

Related Questions