Tree Shaking Three.js

by comparing the bundle size with a 300 KB JPEG I actually just had a mere comparison between loading a bundle.js and loading some heavy image in mind, didn’t think of evaluation and decoding, you’re right.

Anyway, I checked our website regarding this and we get almost the same (a bit lower) bundle loading / evaluation time compared to loading the first image (PNG) which is about 30% (97KB) of the gzipped bundle size (281KB), that’s why taking a few more KB for the bundle into account doesn’t appear as a huge problem to us, see:

cream_gmbh_loading_bundle
cream_gmbh_evaluating_bundle
cream_gmbh_loading_first_image

With lower bandwidth settings (fast 3G) this looks a bit different:
2.17s for loading bundle (evaluation time unchanged) vs. 1.28s loading the image.

This doesn’t mean we wouldn’t like having more creativity with materials / shaders and even lower bundle sizes with node-based materials, just trying to clearly understand the main benefits of it.

What device are you testing with? That might be very different on a budget phone than a laptop.

Why you have WebXR? You actually using it?

1 Like

it’s part of webglrenderer, deeply wired in, like pretty much everything else: https://github.com/mrdoob/three.js/blob/59861dee1614ae5d49913a098d068638941b5d23/src/renderers/WebGLRenderer.js tree-shaking won’t be a reality until this file is made from scratch it seems, apart from the other things like prototypes to classes etc. the shaders are referenced in it as well.

for projects that absolutely cannot afford the extra weight i go into this file and rip out the things that i dont need.

3 Likes

Was also first wondering, no, not using it, see the modified simple svelte app, that’s all. On the other hand it’s logical since access to the XR API can only be enabled / disabled on the WebGLRenderer itself via WebGLRenderer.xr (WebXRManager docs)

plus :point_right: see @drcmda’s answer.

Well then let’s go and rewrite everything to classes as there seem to be no sane way around it ! :wink:

the above was on a desktop ws, just lowered the network bandwidth in dev tools.

I’ve now tested it on our mobile device (Newtwork set to fast3G) we use for budget phone testing: a HUAWEI Mate 20 Lite:

  • Loading time is the same 2.17s bundle vs. … 17.2… DAYS?! :scream:
    image_huawei_mate_20_lite_fast3G
    :joy: this must be a bug in dev tools or something, it should be roundabout the same, so 1.28s.

  • Bundle evaluation / compilation went to about 5x higher:
    evaluation_huawei_mate_20_lite_fast3G
    compilation_huawei_mate_20_lite_fast3G

I think this is really more than acceptable :man_shrugging: the bundle contains EVERYTHING: three, gsap, svelte compiled components code + the entire website engine (own modules / classes handling animations, generations etc. etc.)

1 Like

Would you get any benefits from say not including gsap in the first bundle? If i understand gsap doesn’t do anything with gpus? For example i’m thinking loading some basic three, compiling shaders or something, while the rest of the chunks are being fetched? While some of that size goes into most materials, i bet a lot of the chunks are used for MeshStandardMaterial which you don’t use for example if you’re making some video processor or something?

Am i reading the conclusion here right? Three is essentially tree shakeable, but it only goes as far as how the code is designed. No matter what you do with code, you will still have the shader chunks, which, because they are strings, are hard to import/separate etc. Bok imenjace :wink:

The bundle would be even smaller, but we would than have to load gsap via CDN. All of our custom code are TypeScript Classes / Modules, so it felt more natural to install gsap as npm dependecy and just import it + this way it also gets treeshaked.

Btw. as I’ve heard from some of my colleagues using React, they have problems with gsap (was it they’re not able to use it at all? :thinking:) and are not really happy about that. We’re able to use it seamlessly in our stack (Svelte + Three).

Well, Vatroslav (right? :wink:), I would say yes, e.g. you need a WebGLRenderer, Scene and a Cam as minimum and (as of today) you have to live with what that is importing, OR you rip out things by yourself like @drcmda (see)

P.S. for example WebGLRenderer itself is already importing ‘Scene’, so even if you don’t create one, it will be bundled.

1 Like

I created a quick repo (I really had to create it quick and therefore it’s messy and too many bad practices :wink: but better than nothing)

You can find the code here:

It’s just a proof-of-concept and the benefits are not too big (today’s JS engines are so optimized that they can parse a huge string very quickly) but in our case it shaved off some more milliseconds for low-end devices.

Also be aware that this hack will make most projects slower because you need to wait for the shaders.json to arrive before you can start the Three.js scene but in our case and in our project that’s not the bottleneck (because we can preload it efficiently).

The bottom line: this hack will make most projects slower :laughing: and for the other projects it has only a small benefit :rofl: but what I wanted to show is, that we should be creative and question if we have a more efficient way to transmit shaders.

I’m not a GPU developer nor I’m a 3D software engineer but maybe my little experiment kicks off some discussion about the handling of shaders. Maybe everything is already perfect but maybe there is also room for improvement :slightly_smiling_face:

1 Like

According to Cream screenshot, we can focus on improving WebXR and Math classes. Because it’s plain JS and refactoring there is much easier. Also here interesting issue related to loading speed: How to download textures before JS parsing?

As a relative novice, I’ve read this thread with great interest, fear and confusion. I thought I was using modules correctly, until I noticed my built js file was roughly the same size no matter what modules I reference.

What’s the best/simplest way to import/use three.module.js?

I’m using rollup. I would prefer to not bundle the entire library every time I build. I am using various jam examples (shaders, OrbitControls, GLTFLoader etc).

Can anyone explain a quick approach to get up and running?

there is nothing that you can do, threejs will not tree-shake. you will always end up with the full bundle. unless you start hacking around, and even that has limited results. best install the rollup bundle analyzer plugin so that you can see what’s going on, then try some of the hacks in here and see what works best for you.

Since we have finally got the go ahead to start adding es6 features to three.js, what you can do is open pull requests to start converting the library.

At the moment, we’re working on removing var in favor of let and const. Hopefully, the next step after that will be classes and then tree shaking should work. If you want to speed up this progress, I’m sure extra hands doing the work are welcome.

1 Like

^^The ES6 Class conversion (which is a requirement for tree shaking, because reasons) is dependent on removal of the examples/js directory, to my understanding. We don’t want to cause breaking changes to those files since they’re going to be removed soon anyway. So that’s all in progress, but there is a necessary delay of several months (until December 2020) to allow time for people to make the migration to ES Modules.

@munrocket — thanks for sharing the diagram! Would you be able to create a similar diagram with the gzipSize option enabled? I’m curious if the whole library gzips equally, or if specific parts (shaders?) are more or less compressible.

2 Likes

Super interested in this, but it looks like there might be a delay.

Breaking changes are always a pain but sometimes I think that the way Three.js handles releases and versions holds us back a little bit. There is a really interesting blog-post about this here: Working with different three.js versions | by Dusan Bosnjak | Medium. It reflects a lot of our experiences with working with Three.js since almost 3 years.

I think we could learn a lot from how the Ember.js project is organized and governed. They made it possible that Ember.js survived the framework wars and they managed to stay up to date with recent web standards without breaking their entire eco-system. I think it could be beneficial when the maintainers of Three.js speak with the Ember.js maintainers about those things. Because Ember.js had similar challenges and they solved them really well.

Yeah but that’s off-topic :slightly_smiling_face: now back to tree-shaking :wink:

We played around a lot with tree-shaking and the best we could get was around 300KB unzipped. But this involves lot of hacks so I’m not sure if this is the best idea for you as a relative novice. Maybe you could just start writing your app and maybe the Three.js codebase changed until you finished your work and then you can leverage the advantages when you put your app to production.

1 Like

Thanks, this is what I’ll be doing for now.

I’ll leave tree shaking until I become more familiar with ES modules workflow. Or maybe by then Three.js will have a working solution. Coming from a Unity3d background, I know what it’s like to be waiting on that next exciting new feature :wink: