Bear with me, this PR is a bit of an epic...
Following the discussion on https://github.com/mrdoob/three.js/issues/4776, this PR converts the Three.js codebase to ES2015 modules, and updates the build process to use Rollup (full disclosure – I'm the author of Rollup).
build/three.js file is functionally identical to the existing one – the examples continue to work* without modification – albeit slightly smaller when minified, because the various modules can now refer to e.g.
Mesh instead of
THREE.Mesh, meaning everything gets mangled properly by UglifyJS.
Apart from the many advantages discussed in https://github.com/mrdoob/three.js/issues/4776 (easier development, better linting, simpler build process, etc), a great thing about using ES modules is that people will be able to do this sort of thing in their apps...
import * as THREE from 'three';
const mesh = new THREE.Mesh();
...and rather than including the entire library in the resulting bundle, ES-module aware tools that can do tree-shaking will be able to discard unused parts of the library.
The nice thing about using modules is that you no longer have to specify the order in which files should be included. The frustrating thing about modules is that you lose control over the order in which files are included.
Specifically, when you have cyclical dependencies – which Three.js has a few of – you're at the mercy of the topological sorting algorithm (per the ECMAScript spec). If you have a file like
KeyframeTrack which depends on the various subclasses in
animation/tracks, each of which depend on
KeyframeTrack, you end up with a situation in which the prototypes of the subclasses don't inherit from the parent because the execution order is all wrong. The solution I've used here is to put the
KeyframeTrack prototype and constructor function in separate files, which allows for a stable sort. A similar thing was necessary with
I also moved all the constants like
THREE.CullFaceNone into a separate
constants.js module so that other files in the codebase can refer to them without introducing a cyclical dependency.
Similarly, the various cases of
object instanceof THREE.Mesh and so on have been replaced with
object && object.isMesh (and the relevant constructors have been augmented such that
true for all instances of
Mesh. This also eliminates some nasty cyclical dependencies.
THREE.AudioContext is a bit tricky because it's a getter: with modules,
THREE is just a namespace rather than an object, and while an object can have getters a namespace can't. So internally,
AudioListener etc do
this.context = getAudioContext() rather than
this.context = AudioContext. For the UMD build, the getter is added to the export, so as far as consumers of the library are concerned
THREE.AudioContext will continue to behave exactly as before.
If you merge this (and I hope you do, but will understand if it's a bit of a leap and needs some work first!), it will pave the way for further optimisations. For example, there are lots of places where IIFEs are used to avoid polluting the global namespace – with modules, that's not necessary (everything is local), so it's possible to get rid of those. I didn't do any of that stuff with this PR because most of the changes were generated by a script (in https://github.com/rollup/three-jsnext).
Eventually this should make it easier to move certain parts out of the core library and into separate plugin repos, if that's your intention.
Let me know what you think, and thanks for reading this far!
*actually that's not quite true – the mirror / nodes example is giving me a 'Shader couldn't compile' error... I wasn't quite able to figure out what's going on, but maybe it's obvious to someone else?