2

I am trying to use threejs and three-globe together, using only the CDN distributions from unpkg, but I am getting tripped up by modules. I am hoping to avoid using a build tool but perhaps that's not doable.

I started with the very basic example right from the top of the three.js README. I have an index.html:

<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="utf-8">
                <title>Threejs basic app</title>
                <style>
                        body { margin: 0; }
                </style>

<script type="importmap">
        {
                "imports": {
                        "three": "https://unpkg.com/three/build/three.module.js"
                }
        }
</script>
        </head>
        <body>
                <script type="module" src="./main.js"></script>

        </body>
</html>

and a main.js, copied from the jsfiddle:

import * as THREE from 'three';

const width = window.innerWidth, height = window.innerHeight;

// init

const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 );
camera.position.z = 1;

const scene = new THREE.Scene();

const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
const material = new THREE.MeshNormalMaterial();

const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );

const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height );
renderer.setAnimationLoop( animation );
document.body.appendChild( renderer.domElement );

// animation

function animation( time ) {

        mesh.rotation.x = time / 2000;
        mesh.rotation.y = time / 1000;

        renderer.render( scene, camera );

}

And all works fine.

Where I get tripped up is when I want to include three-globe. If I try to import it like so, as the first line of the three-globe README suggests:

import * as THREE from 'three';
import ThreeGlobe from 'https://unpkg.com/[email protected]/dist/three-globe.js';


const width = window.innerWidth, height = window.innerHeight;

// rest of the code is the same

This fails, with the javascript console telling me

Uncaught SyntaxError: The requested module 'https://unpkg.com/[email protected]/dist/three-globe.js' does not provide an export named 'default' (at main.js:2:8)

No problem, it's not a module, let me try what I think is the module version:

import * as THREE from 'three';
import ThreeGlobe from 'https://unpkg.com/[email protected]/dist/three-globe.mjs';


const width = window.innerWidth, height = window.innerHeight;

// init
//rest of the code is the same

This fails, because I apparently to help it find dependencies:

Uncaught TypeError: Failed to resolve module specifier "kapsule". Relative references must start with either "/", "./", or "../".

So I tried with a namespace import:

import * as THREE from 'three';
import * as ThreeGlobe from 'https://unpkg.com/[email protected]/dist/three-globe.js';

const width = window.innerWidth, height = window.innerHeight;

// init
// the rest of the code is the same

But now it blows up trying to mess with three.js:

Uncaught TypeError: Cannot read properties of undefined (reading 'BufferGeometry')
    at three-geojson-geometry.mjs:235:19
    at three-globe.js:5:98
    at three-globe.js:6:3

Looking at this code in three-geojson-geometry, it's rooting around and looking for window.THREE - is it not there yet?

I can make this work by ditching the import of threejs and three-globe and just using them as script tags and not modules: the new index.html:

<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="utf-8">
                <title>Threejs basic app</title>
                <style>
                        body { margin: 0; }
                </style>

<script type="importmap">
        {
                "imports": {
                        "three": "https://unpkg.com/three/build/three.module.js"
                }
        }
</script>
  <script src="//unpkg.com/three"></script>
  <script src="//unpkg.com/three-globe"></script>
        </head>
        <body>
<script type="module" src="main.js"></script>
        </body>
</html>

and the an updated main.js

//import * as THREE from 'three';
//import * as ThreeGlobe from 'https://unpkg.com/[email protected]/dist/three-globe.js';

const width = window.innerWidth, height = window.innerHeight;

// init

const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 );
camera.position.z = 1;

const scene = new THREE.Scene();
//rest of the code is the same

And that works. But now when I want to import something in main.js, I get warnings about pulling in multiple versions of three.js

an updated index.html:

<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="utf-8">
                <title>Threejs basic app</title>
                <style>
                        body { margin: 0; }
                </style>

<script type="importmap">
        {
                "imports": {
      "three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
                }
        }
</script>
  <script src="//unpkg.com/three"></script>
  <script src="//unpkg.com/three-globe"></script>
        </head>
        <body>
<script type="module" src="main.js"></script>
        </body>
</html>

and an updated main.js

//import * as THREE from 'three';
//import * as ThreeGlobe from 'https://unpkg.com/[email protected]/dist/three-globe.js';

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const width = window.innerWidth, height = window.innerHeight;

// init
// all of the rest of the code is the same

That works, but now it's upset that there are multiple three.js instances:

WARNING: Multiple instances of Three.js being imported.
(anonymous) @ three.module.js:53034

Following along in the network tab, https://unpkg.com/three 302s to https://unpkg.com/[email protected] which 302s to https://unpkg.com/[email protected]/build/three.js. The glTF module also looks like it's creating a load for https://unpkg.com/[email protected]/build/three.module.js which presumably is the 2nd copy of Three.js

So trying to head that off at the beginning, and loading it index.html:

<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="utf-8">
                <title>Threejs basic app</title>
                <style>
                        body { margin: 0; }
                </style>

<script type="importmap">
        {
                "imports": {
      "three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
                }
        }
</script>
  <script type="module" src="https://unpkg.com/[email protected]/build/three.module.js"></script>
  <script src="//unpkg.com/three-globe"></script>
        </head>
        <body>
<script type="module" src="main.js"></script>
        </body>
</html>

I'm now stuck with two errors in the console:

Uncaught TypeError: Cannot read properties of undefined (reading 'BufferGeometry')
    at three-globe:2:58460
    at three-globe:2:227
    at three-globe:2:238
(anonymous) @ three-globe:2
(anonymous) @ three-globe:2
(anonymous) @ three-globe:2

Uncaught ReferenceError: THREE is not defined
    at main.js:10:16

So I guess I'm just kinda stuck. Is there really no way to just do an import ThreeGlobe from main.js? I think my trouble is that ThreeGlobe is looking to hook onto the global THREE object, but I don't understand when that is actually created if I only import it in main.js. This is kind of a longshot, but is there some await magic I have to do with these loads to make sure that any sideeffects of the module loads have happened?

0

1 Answer 1

3

FWIW, I was able to reproduce the WARNING: Multiple instances of Three.js being imported. issue. My example uses the CDN https://esm.sh and inserts the example script from the three README inline because stackoverflow snippets don't support es modules.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="importmap">
      {
        "imports": {
          "three": "https://esm.sh/[email protected]",
          "three-globe": "https://esm.sh/[email protected]"
        }
      }
    </script>
    <title>three.js CDN</title>
  </head>
  <body>
    <script type="module">
      import { PerspectiveCamera, Scene, BoxGeometry, MeshNormalMaterial, Mesh, WebGLRenderer } from 'three'
      import ThreeGlobe from 'three-globe'

      // console.log(ThreeGlobe)

      const width = window.innerWidth, height = window.innerHeight;

      // init

      const camera = new PerspectiveCamera( 70, width / height, 0.01, 10 );
      camera.position.z = 1;

      const scene = new Scene();

      const geometry = new BoxGeometry( 0.2, 0.2, 0.2 );
      const material = new MeshNormalMaterial();

      const mesh = new Mesh( geometry, material );
      scene.add( mesh );

      const renderer = new WebGLRenderer( { antialias: true } );
      renderer.setSize( width, height );
      renderer.setAnimationLoop( animation );
      document.body.appendChild( renderer.domElement );

      // animation

      function animation( time ) {

        mesh.rotation.x = time / 2000;
        mesh.rotation.y = time / 1000;

        renderer.render( scene, camera );

      }
    </script>
  </body>
</html>

I noticed from my browser's network panel, that three-globe was initiating the additional request for three, but at a different version (v0.158.0) than the one listed in the importmap. Basically, this mismatch in version between [email protected] and [email protected] caused the duplicate instances.

A potential solution is to explicitly request [email protected] in your importmap.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="importmap">
      {
        "imports": {
          "three": "https://esm.sh/[email protected]",
          "three-globe": "https://esm.sh/[email protected]"
        }
      }
    </script>
    <title>three.js CDN</title>
  </head>
  <body>
    <script type="module">
      import { PerspectiveCamera, Scene, BoxGeometry, MeshNormalMaterial, Mesh, WebGLRenderer } from 'three'
      import ThreeGlobe from 'three-globe'

      // console.log(ThreeGlobe)

      const width = window.innerWidth, height = window.innerHeight;

      // init

      const camera = new PerspectiveCamera( 70, width / height, 0.01, 10 );
      camera.position.z = 1;

      const scene = new Scene();

      const geometry = new BoxGeometry( 0.2, 0.2, 0.2 );
      const material = new MeshNormalMaterial();

      const mesh = new Mesh( geometry, material );
      scene.add( mesh );

      const renderer = new WebGLRenderer( { antialias: true } );
      renderer.setSize( width, height );
      renderer.setAnimationLoop( animation );
      document.body.appendChild( renderer.domElement );

      // animation

      function animation( time ) {

        mesh.rotation.x = time / 2000;
        mesh.rotation.y = time / 1000;

        renderer.render( scene, camera );

      }
    </script>
  </body>
</html>

I still haven't tracked down why three-globe is depending on that specific version of three. I see that three is listed as a peerDependency, but haven't looked at the build or deployment details.

Sign up to request clarification or add additional context in comments.

1 Comment

Good job, I was playing with that compatibility a little bit, but I could't figure out this...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.