CDN es6 module import requires importmap (Vanilla JS)

Description

I am trying to use threejs in a vanilla page using es6 imports. But its weird that the latest version doesn’t work without importmap! Weirdly an old version’s importmap also works!

Check these examples:

  1. Old Version without importmap
  2. New Version that requires importmap

Also the same code produces starkly different renders, any suggestions?

Note: I am following a no-bundle workflow. I am wondering is it designed with the implicit assumption that it will be used with a bundling system?

The importmap with the trailing ‘,’ is invalid. Might be browser specific… ?

Versions of threejs after a certain point require importmaps to work.

To clarify:

<script type="importmap">
  {
    "imports": {
      "vanilla": "https://unpkg.com/@pmndrs/vanilla@1.19.0/",
    }
  }
</script>

is invalid because of the trailing comma… needs to be:

<script type="importmap">
  {
    "imports": {
      "vanilla": "https://unpkg.com/@pmndrs/vanilla@1.19.0/"
    }
  }
</script>

But isn’t that a weird requirement to need importmap for the library to work? I was hoping to have a specific setting set up in a single file that can be imported to get a scene to work. Wasn’t it supposed to do that. Something like:

import { ModelRender } from 'https://myhost.com/model-render.min.js'

So that three defined in with all settings will be just imported and I can simply using this function where ever.

@jikkuatwork just as a side note and if you end up using the latest three.js, you might need to adjust the light intensities:

spotLight.intensity *= 500; // or whatever positive number works for you
pointLight.intensity *= 500; // or whatever positive number works for you

Also, if you check the code of the current OrbitControls, you might notice that its own imports are looking for mapped three.

No, not if you’re using no bundler, the browser does not know what ‘three’ means without a import map definition to hold reference to the name space your app requires for importing ‘three’…

But how come other libraries don’t have similar requirements? And how come older versions of Three work without it? And recently a library called Zim wrapped three (~v0.155 or so) and it works so well without any of these import map. Things like orbit controls all work very well with very few lines of code. For instance:

Zim Three ; code: https://play.toolbomber.com/zim-three/app.js
Rapid: Petite Vue code: https://play.toolbomber.com/zim-three/2/app.js
3 also works, deleted the link as the site dosn’t allow more than 5 links

The beauty of this is that I can easily create a single file module that I can import and just use with minimal params that I care to modify.

Say:

  • A model loader with great lighting, orbit controls and a default HDR ModelLoader({ model: './model.glb' })

This would make three much more accessible for a lot of people.

The design decision here is that you must either use a bundler like Vite, or you must use an import map. It is, in general, not practical to have multiple dependencies interact with one another correctly without one of those two things. Consider: if you’re using 5 packages from CDNs, and some of those packages depend on each other, and some depend on other packages you didn’t load, there is nothing in a web browser that is going to resolve these dependency trees without an import map, and it’s very easy to end up with 2-3 copies of three.js on the page, which will cause major issues.

Personally my own recommendation is to use a tool like Vite. Without that, things get more and more complicated for every dependency, and many packages on NPM make no attempt at all to support installation from a CDN.

Also the same code produces starkly different renders, any suggestions?

See migration guide, in particular releases r152–r156. Migration Guide · mrdoob/three.js Wiki · GitHub

1 Like

Thanks for the kind reply. But I am on a workflow that doesn’t use any bundling and on the fly importing of modules. I think will stick with older version of three that supported this.

Also, apart from changelog/migration guide that explains the feature difference? Also, I see that r139 can be imported via CDN. Do you know when the support was dropped?

Older versions of three used a relative path reference to the internals of the library from build->three.module.js, you can see this in any of the jsm addons in older revisions…

Three is now name spaced (‘three’) and imported as such throughout the library, you can see this in later revisions of the same file…

Name spaced references are much more manageable and point towards one source of truth for any dependencies in your project, using 4 lines of code to include an import map in your case is trivial…

Great, thanks for clarifying. Is trust its in the issue of browser resolving dependencies?

Adding 4 lines isn’t the issue, I am pursuing a clean single line import of an es6 module. So that the app only has to deal with a single line import rather than having an API which requires modifing the page it runs on.

I wonder whether adding import maps to head dynamically could work?

I believe it was r137 that switched from relative paths to ‘three’, I’m not sure how r139 would work from a CDN without an import map. Another option, too, is to copy the files locally rather than using the CDN, and just replace the three import paths. Manual process, but if you want that full control it’s there.

1 Like

Maybe without modules?

<script src="https://cdn.jsdelivr.net/npm/three@0.139.0/build/three.min.js"></script>

2 Likes

Ah, yeah that might be it! In that case r148 is the next relevant release, where examples/js/ (UMD / globals) is removed in favor of examples/jsm/ (ES Modules).

1 Like

Perhaps I can do a script to convert the dependencies to absolute links and host it at my end? But I wonder a switch/variable that set all relative links to absolute URLS would have been cool? Or wish there was a way to specify importmaps in modules.

There’s UMD-Version of three.js (V.161) - #5 by PavelBoytchev, perhaps. For a transition period three.js was maintaining builds of each individual examples/jsm import in both ESM and UMD/global versions, but that was not practical as a permanent solution.

But I wonder a switch/variable that set all relative links to absolute URLS would have been cool?

AFAIK import maps are the closest thing the JavaScript language allows. Or you could do this with a tool, but, then that’s a bundler if you squint.

(I mostly use Vite)

1 Like

This post would be intended for all who might need such a feature of converting jsm to js.

The link provided in this last post by @donmccurdy shows that there is the Demoduler by @PavelBoytchev and it appears to still be working properly.

I just gave it a try and converted the following r167 jsm to js for a test:

  • three.module.js
  • OrbitControls.js
  • 3MFLoader.js
  • BufferGeometryUtils.js
  • RoomEnvironment.js

then used them all, together with older version of fflate.min.js, in my 3MF Viewer and it all worked fine for loading 3MF models.

This is a rather lengthy and tedious process, especially considering that it might need to be done with every new revision unless one is capable of applying any new changes manually to already converted files.

1 Like

Indeed, its a bundler. But the difference is its need only once to bundle the library not for any downstream projects using it.

But, I can see why many don’t feel the need to go down this road. Its just me getting too tired running npm install after a few weeks/months to see a ton of dependency errors. I can’t stand it anymore, I am ready to spend the effort bundling the new version and use it for a bunch of projects and then occasionally update to new version through another bundling. (Say even at an annual frequency could work well)

I was wondering whether say a serverless function can do it for every new release for each popular cdn service?

It should solve this for every user of the package.

Just to point out how Zim that has a threejs dependency works so well without breaking a sweat, I believe the same DX should be possible with the original library.

Creating, maintaining and testing any js code in three.js does not sound like a viable option, this since js was dropped a while ago and all the coding efforts were directed towards jsm.

Unless you create something yourself, I highly doubt that anyone else is going to do it (apart from maybe those 3rd party libraries).

In general, it does not make a sense to do any significant conversion any longer, like converting the whole library.

Importmap and module do provide for easy revision update and selective importing (import only those parts that you want to use and when you want to use them).

Thanks for the perspective, but just to understand, something like this Demoduler; also won’t work?

Also: I understand this feels weird and daunting for nothing. But, imagine I don’t need all the cutting edge features but a super simple starting point that I can import for all models I am planning to work with. isn’t that still okay? Say, I will update the urls to absolute ones once every year? (Only if I need any new feature that dropped, if else the current version will work effortlessly).

The issue with importmap isn’t about number of lines. Its about a dangling config that I wish to avoid. Three is going to be a tool in a set of tools I am planning to wrap like this with my new bundleless workflow. So, having one less config helps me avoid things to remember.

I am also planning out a generative system for building UI with similar on the fly imported modules, which won’t work if I have to meddle with the markup.

I have a strong developmental need to have everything I use in self contained es6 modules; I am sacrificing performance for this and I know how it looks crazy. But the bet I am taking here is ability to do more trumps premature optimisation any day. I know it will be slow to load. I still do it because what I gain with this trade off is an amazing set of primitives that help me move a lot faster because of the uniform API in its application.

I know its hard to put what I have in my mind to words unless I show you something, so here are some examples (its not final, but the nugget of why I am doing this in here):

  1. SandWorms
  2. Confetti

(I did a bunch of examples, but Discourse won’t let me post more since new users have a limit for the number of links!)

These are all self contained components that can be imported with a single line of code with default configs that work out of the box. In the perspective of a person starting out, this is huge. For me, with a need to launch a ton of ideas in extreme speed this huge. The idea is to build a ton of primitives with the top 100 or so JS libraries that can be used as single line imports, with great flexibility to config if need be. Take a look at the ModelViewer’s code: https://rough.toolbomber.com/pv/ModelViewer.js.

If these are provided into the context of an LLM, bespoke components with on the fly generation & usage becomes super easy.