ES6 modules + other THREE libraries

This is a problem I’ve struggled with too. I’m not sure how to resolve it. ThreeMeshUI imports like this internally:

import { Object3D } from 'three/src/core/Object3D.js';

Arguably it should be:

import { Object3D } from 'three/build/three.module.js';

However, that wouldn’t help matters here.

I don’t know of any way to use this library (or any library that uses NPM style three.js imports) without using a build tool.

I’ve also struggled with this from the point of view of the person authoring the extension. How can we write an extension that imports three.js components in such a way that a user can use it with or without a build tool?

you can’t use additional libs unless they don’t have dependencies, plain and simple. it is still questionable if the browser esm spec will ever turn out to be a standard that people choose to employ, but right now it’s arguably not so useful because it cuts you off from javascripts eco system, which is npm. the reason you can use three/jsm at all is because three breaks node resolution.

just use tools that are easier instead. webpack is hard to set up. forget boilerplate. but something like parcel for instance can get you going in a second. there’s also codesandbox, which allows you to open fully working projects in the browser. you can download them and run them locally.

1 Like

Agreed with @drcmda — if you’re pulling libraries from npm, with very few exceptions you’ll need to use a bundler. three.js itself is an exception — because it has no dependencies — but packages depending on three.js are not. There are some bundlers that may be easier to set up than Webpack: https://www.snowpack.dev/ and https://parceljs.org/ come to mind. I think you’ll find it worthwhile to experiment and find a bundler that suits your needs.

1 Like

Damn, I was hoping someone had a magic solution to this that I couldn’t see.
Do you think import maps would work in the future?

And while we’re on the topic, am I right in thinking the way ThreeMeshUI currently imports from three/src is not best practice? Rather than this:

import { Object3D } from 'three/src/core/Object3D.js';

It should be:

import { Object3D } from 'three';

Otherwise people will end up with two copies of three.js in their bundle. /ping @felixmariotto

Do you think import maps would work in the future?

Maybe, but you need to include dependencies of dependencies of dependencies in your import maps. Which automated tooling could solve, but then you’re back to a bundler-shaped tool. :slight_smile:

1 Like

Good catch… yeah, that’s like to cause problems if (a) the user doesn’t do the same, or (b) the user includes anything from the examples/jsm/* folder. Perhaps it’s meant to enable tree-shaking, but the better solution there is to continue pushing toward a tree-shakeable ‘three’ package.

2 Likes

@looeee hanks for the heads up :blush:
As @donmccurdy pointed out, it was an (unsuccessful) attempt to enable tree-shaking, because ThreeMeshUI doesn’t use the renderer but still get the full three.js lib included in its bundle.

Anyway I’ve just reverted this to :

import { Object3D } from 'three';

@phxbf It’s unlikely the cause of your problem importing ThreeMeshUI though, as others wrote before you must use a bundler ( not necessarily Webpack ) to import it from npm. The other solution is to import three-mesh-ui/dist/three-mesh-ui.js, which can even be done with a script tag like so :

<script src='https://unpkg.com/three-mesh-ui/dist/three-mesh-ui.js'></script>

But beware that the latter option includes the whole three.js lib, so the package is fat ( ~650kb )

Thank you all for hints and suggestions.

So far I tried this (very rough) parcel setup using a single js file (“mybundle.js”) alongside node_modules/ folder:

import * as THREE from "three";

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as ThreeMeshUI from 'three-mesh-ui';
/* other imports here...*/

window.THREE = THREE;
window.OrbitControls = OrbitControls;
window.ThreeMeshUI = ThreeMeshUI;
/*...*/

then I performed:

parcel build mybundle.js -d dist/

that builds mybundle.js in dist output folder, containing all THREE related stuff, including in this case also functionalities from the ThreeMeshUI library. In the testing html page then I just added:

<script type="text/javascript" src="dist/mybundle.js"></script>

Is that correct?
From a quick test seems everything is found from THREE and ThreeMeshUI.
Are there cleaner approaches?

1 Like

I think it looks fine but bundling is an occult science so I will let better informed people tell you if it’s optimal.

it should be easier, and i don’t think it’s correct that you change stuff within /dist, it should be automatic. best you follow the instructions: https://parceljs.org/

never used parcel before but just tried it, i got it up and running quickly: https://github.com/drcmda/threeparcel

that’s a full dev environment with hot module reload, you can install dependencies (npm install three) so no more copying files around, and it’s production ready with minification and such. i think i’ll use parcel more often now. :slight_smile:

the commands are

parcel index.html which starts the dev server, you can now edit and save and it will automatically refresh.
parcel build index.html which creates a /dist folder.

you usually add these commands to your package.json (which you get once you type npm init inside your empty folder.

PS, the window.THREE = THREE; stuff is also curious. in a module environment you don’t need globals. if something relies on a global like that it points to a mistake.

One note on parcel is that you have to tell it what to do with static files like models: https://github.com/donmccurdy/three-gltf-viewer/blob/6ffca05fe6032c436606351c083e4cf7255b9994/package.json#L57-L63 … this uses the parcel-plugin-static-files-copy plugin.

1 Like

One thing I don’t like about Parcel is that it tries to control the whole app structure including assets when all I want is to bundle JS. I found myself fighting against it, which I think is what @phxbf is experiencing.

Recently I’ve started to use ESBuild which is more similar to Rollup but with the simplicity of Parcel. It’s a very new project but it seems to work well already, and it’s extremely fast.

1 Like

One thing I don’t like about Parcel is that it tries to control the whole app structure including assets when all I want is to bundle JS.

Yeah, if you only want to bundle JS then there are simpler options. I would use Rollup or Microbundle when writing a library, for instance. But if the thing I’m building is actually a web app, I tend to find that a bundler designed for building web apps has advantages (live reload, built-in web server, better sourcemap support, etc.). Definitely some personal preference in that.

You can use a public folder like this https://github.com/parcel-bundler/parcel/issues/1080#issuecomment-633119160 but generally it is better to always import, moving away from the sentiment that only JS should be imported. This allows the bundler to handle assets in a safe and managed way, gives you cache control and build time errors should a file not be present.

Like don said, there are tools for building libraries that you want to publish, rollup and I guess esbuild, and there are tools for building frontend applications that have different features and requirements. All frontend tools come with a dev server and hot module reload. Imagine if you had to create a dist and refresh the browser for each change while coding/debugging.

I’ve received some personal feedback from beginners (in JS and three.js) that Parcel is somewhat overwhelming. A simple setup based on rollup and serve was preferred by most of these users.

2 Likes

Thanks, I just used “dist” as sample output folder in my project to test out parcel with a sample web page. I’ll have a look to all options offered by parcel.
So far it does the job (bundling together a custom build of THREE including external libraries)

I’m in this scenario with a test page loading the generated (and minified) mybundle.js:

<script type="text/javascript" src="dist/mybundle.js"></script>
<script>
let v = new THREE.Vector3(0,0,1); // sample call
// do stuff using functions from mybundle.js
</script>

if I remove the window.THREE = THREE; from the parcel setup, when opening the test page it throws Uncaught ReferenceError: THREE is not defined - whenever you call any method/function from THREE (see second <script>).

I will also test other proposed solutions like rollup and esbuild. So far anyway parcel seems to do the job for my specific scenario.

this is because you edit index.html. normally you don’t touch html files, it is not common and most bundlers will auto-generate them for you. parcel is kind of a surprise, but still, you don’t need script tags, or scripts.

you have one entry point, that’s index.js, everything is normally done from there. you can import other local js files from index.js and the bundler will include them. window.TREE could make sense in legacy codebases, otherwise it looks to me like you’re hacking the module environment.

Helpful feedback, thanks!

Hi, three.js newbie here.

I found Parcel really easy after fighting with Webpack on Windows. I followed the instructions here: https://parceljs.org/getting_started.html up to point of viewing the demo in my browser.

I added the three,js build and examples folder to the new directory.

I then copied everything between the script tags in my existing page into the new index.js file. Added the existing HTML to the new index.html and saved both.

The new HTML and JS files appeared in dist and everything worked in the browser. Each time I save the index.js file the dist files are automatically updated.

Seems easy to me but maybe it’s just that I have fairly simple use case.