Why does Three.js require a import map?

I am working with a project here:

My index.ejs looks like this: maze3d-world/index.ejs at ff17df5887a9444ed88b2e2b2ff0e151652b3683 · michaelnicol/maze3d-world · GitHub

<!DOCTYPE html>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="./styles.css">
    <script type="importmap">
            "imports": {
              "three": "https://unpkg.com/three@0.141.0/build/three.module.js"
    <div id="app"></div>
    <script type="module" src="world-init.js"></script>

My world.js looks like this at the top: maze3d-world/world.js at ff17df5887a9444ed88b2e2b2ff0e151652b3683 · michaelnicol/maze3d-world · GitHub

import * as three from "https://unpkg.com/three@0.141.0/build/three.module.js";
import { OrbitControls } from 'https://unpkg.com/three@0.128.0/examples/jsm/controls/OrbitControls.js';
import { GUI } from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.module.js';
import Maze3D from "./maze3d-es.js";
const { AmbientLight, BoxGeometry, MeshPhongMaterial } = three;

Despite my project importing three from a CDN, if I remove the import map it just stops, and cannot find three. Why is this? I don’t import * as three from "three" anywhere in my build.

Various parts of three.js depend on each other — for example, THREE.OrbitControls depends on the rest of the library. It uses import { ... } from 'three' to do so, which is necessary when installing the library from NPM. CDNs are (mostly) just hosting what they find on the NPM repository, and require the import maps to resolve those imports.

Note that you’ve imported different parts of the library from different CDNs and from different three.js versions in the example above — this can easily break, it’s best to stick with a single CDN and three.js version.

Personally I use a build tool instead, once you’ve got the hang of it things are much easier. Vite is a popular option.


this looks wrong to me to be honest. you seem to be running multiple threes. you normally import from “three” not from a cdn. relying on shaky remote services in the import map isn’t much better, not to mention that these are unfinished specs. unpkg is a remote bundler, like don i would also suggest to use a bundler directly, vite.

1 Like

I just took a look at vite and vue - wow. It looks like an amazing tool for me to use.

My understanding is that Vite is a smart bundler that can change the import and export types of files to absolute or relative imports. It can also hot-swap CSS (for example) to prevent reloading the whole app.

In the example, the import from loadash is changed into

import { <item> } from "/node_modules/<etc>

So does this mean that the node_modules folder is being sent to the client with all of the other files? Otherwise, it should require an absolute URL?

Where are the routes for the server being stored? In my project I linked, I used to express in order to define what files are sent depending on the URL.

However, in an example: Learn Vite with Evan You - YouTube

I don’t see where this server is within the files. Is this just a single-page application that only serves the index.html file?

Honestly I’m not really sure how Vite works in development mode. It won’t download all of node_modules, but it can download a fair bit. But when you build in ‘production’ mode, it produces a smaller number of optimized files in a folder that you can host and serve statically. You can add a server, or not, and can have multiple HTML pages, or not.

node modules is a local dev environment, it is never sent anywhere, not on the server, not to the client, not on github. you build: npm run build, and then you can send your client the /dist (or was it /build?) folder, or upload that to the server. dist then only contains what your app actually used, bundlers “tree shake”, it’s a more efficient dead code removal.