Tree Shaking Three.js

@looeee Sure, I use webpack3 that takes three.module.js (in my case it is found automatically as it is described in package.json, so no need to make alias).
It really depends on plugins (babel) you use. For example - you’re forced to use harmony modules (es6 modules) to have tree-shaking working. That means that you need to exclude node_modules/ from babel scope.

EDIT: It. actually, doesn’t work (see Tree Shaking Three.js)

1 Like

Hey, just a tip about importing constants:

import * as THREE from ‘three/src/constants’;

Constants are not that large to require tree-shaking for them, and this way it is easier to work with them

Ah OK, I’m using Rollup, maybe it only works with the new webpack. What
kind of size reduction are you getting?

I’ve got my three build down to 257kb with extra libaries on top. I have stripped out all the shaders apart from ones I need.

As mentioned above embedded imports get tree shaked. It’s hard to not include those. Editing is not desirable.

I am still trying to work out a flexible way to import. So it can used in a third party module with three as global. And then reference the sources to bundle within three itself.

To bundle with three, hard links to the sources is needed. And so not portable.

I demonstrated an approach with Rollup, I posted the link in my first post. You can find it there (I can not relink it because the discourse software does not allow it)

I got a reduction in file size but I’m not 100% satisfied. I could include metrics into the Github repo but I think we have to compare minified (and also gzipped). But I would need to set this up. If someone is interested I could implement this (or someone could create a PR :wink:)

1 Like

Can you please provide a minimal reproducing example?
Since just setting modules: false is pretty much default practice, because it’s necessary for tree shaking.

My .babelrc for example:

{
  "presets": [
    [
      "@babel/env",
      {
        "loose": true,
        "modules": false,
        "targets": {
          "browsers": ["last 2 versions"]
        }
      }
    ],
    "@babel/react",
    "@babel/stage-2"
  ],
  "env": {
    "development": {
      "plugins": ["react-hot-loader/babel"]
    }
  }
}

But it would not work in case of three.

I’m not sure if babel is the right tool for tree shaking but I’m not an expert in the React ecosystem (since you load something like react-hot-loader). My example uses Rollup but some other users reported better results with webpack

@tschoartschi I was talking to @sasha240100, reply button is not working as expected :smiley:

Nothing magic about react, there’s no such a kind of any special ecosystem required. babel is just transpiling your code. And neither babel is not doing any kind of tree-shaking, nor webpack. It’s all about uglifyjs to eliminate dead code after all.

https://github.com/webpack/webpack/issues/2867

@nulltails I added uglify to my repo (check the link in the first post, I can not repost it here). The results are as follows:

File size not uglified: 0.573217 MB
File size uglified: 0.410594 MB
File uglified is 28.370% smaller!

So I’m not sure if Uglify can do a lot of magic. Maybe it’s because I only used the default parameters but this includes dead code elimination. Maybe some other settings are better or maybe some minifier like closure could do better. I don’t know. But 410KB for showing a rotating cube seems still quite big. The main problem is that tree shaking is not very efficient (and uglification is also not).

@tschoartschi errm, where did I say uglifyjs will just do all the magic itself?

You should take a look on that webpack issue I’ve sent above, since I mentioned uglifyjs just in case of this issue - https://github.com/mishoo/UglifyJS2/issues/1261

Also, I finally have found some free time and tested the whole process myself.

I am using webpack 4.0.0-alpha.1 in production mode and three 89 installed from npm.

Importing from three.module.js (default)

import { Color } from 'three'

Importing from three/src (custom)

import { Color } from 'three/src/math/Color'

Results:

D:\playground\testing\3js-treeshaking>ls build -ghs
total 520K
508K -rw-r--r-- 1 None 507K Dec 21 16:32 from-module.5ee237e47d.js
 12K -rw-r--r-- 1 None 8.9K Dec 21 16:32 from-source.662ff426c2.js

Conclusion:
Importing from source gives us ability to tree-shake our bundle. I haven’t tested it with rollup, but I think everything should be fine.

Caveats:
Importing from source also requires shader files proccessing, so take care of supporting glsl file format with additional loaders or methods.

Real world usage note:
three is an powerful library and it’s size fairly huge compared to other things in webdev. Tree-shaking is not an silver bullet, but one of the availble methods. We’re better should focus in what condition (using minification and code-splitting) and how is our code is sent (using http2 and gzip or br content compression) to end-user. Stack and methods may vary, as always it depends of project.

For example, in an dead-simple application that is additionally using GLTFLoader (and it imports a better half of the whole library) and loading single model the difference beetwen bundles after minification was 96KB and just 18KB after gzip.

3 Likes

Tested it again… And it seems that I was wrong - it gives size reduction from ~500 Kb to ~485Kb while I’m importing only MeshBasicMaterial:

import {MeshBasicMaterial} from 'three';

Used webpack3 with uglifyjs plugin

1 Like

There was a suggestion above that you’ll get better results by doing

import {MeshBasicMaterial} from 'three/src/materials/MeshBasicMaterial.js';

Have you tried that?

I was playing with this a few months back and got some decent results with rollup and uglify.

Related: https://github.com/mattdesl/threejs-tree-shake

@donmccurdy thanks for the link. I was thinking about building exactly the same thing but it turned out to be rather complex and I didn’t have the time to finish it. Cool that others are also trying to do the same :slightly_smiling_face: The benchmarks which are shown in this repo are very similar to the numbers I got in my example. It would be great if the three.js code base could be more optimized for tree shaking but I think this is also a very complex task and I think it’s not the top priority of the three.js maintainers.

I’ve just tried it, works well. Tested with Parcel.

Thanks :slight_smile:

@tiborsaas great to see more people experimenting with tree-shaking for Three.js. What were the results with Parcel? I would be really interested in seeing the results :slight_smile:

I think the main problem with tree-shaking in Three.js is the ShaderLib. If you need one Shader all of the ShaderLib ends up in your bundle (@looeee please correct me if this is not true for the most recent versions of Three.js since I didn’t try it with the latest versions. I last tried it with r92)

I was trying with webpack v4.29, following the tree shaking guide.

:heavy_check_mark:︎ webpack uses three.module.js which uses the import/export syntax
:heavy_check_mark:︎ disabled module transformation (@babel/preset-env config: {modules: false})
:heavy_check_mark:︎ added sideEffects: false to three’s package.json
:heavy_check_mark:︎ set mode: 'production'

…but still the whole lib gets bundled.

Any idea why?

now that the es6 modules are implemeted in threejs ( jsm folder ), with everything well configured in webpack, the tree shake still doesnt work, is this a pb with webpack or threejs ? we are almost there !

1 Like

Wonder if there has been an update with this. I converted a lot es6 and its all working nicely but still full sized bundle