Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable tree-shaking both for the main and examples files #16301

Closed
wants to merge 4 commits into from
Closed

Enable tree-shaking both for the main and examples files #16301

wants to merge 4 commits into from

Conversation

marcofugaro
Copy link
Contributor

Happy easter everyone!

The aim of this PR is to optimize the repo in order to enable tree-shaking for the webpack users so all of the non-necessary three.js code isn't included in the final bundle.

All the heavy work is done by the property "sideEffects": false in the package.json, which is saying there isn't any shared "state" in three.js which is affected by other non-required files. This is true since three.js' core uses only import/exports and isn't using any non-imported module (correct me if I'm wrong).

More info here

With this, importing any component from the main Three.js file won't include all of the non-executed components in the bundle anymore, for example

import { Vector3 } from 'three/src/Three'
console.log(Vector3)

Before:

  Asset     Size  
main.js  569 KiB 

After:

  Asset    Size  
main.js  15 KiB

Even better importing three.js * as THREE will still be tree-shaked and include in the bundle only the properties which you use in the code:

import * as THREE from 'three/src/Three'
console.log(THREE.Vector3)

Becomes:

  Asset    Size  
main.js  15 KiB

Here is a test-repo if you want to test this out: three-test.zip


For the examples/jsm files instead, I updated the imports from "../../../build/three.module.js" to "../../../src/Three.js", the tree-shaking takes care of the rest. This is because the file three.module.js cannot be tree-shaken since all the code is in the same file. (If anyone has more insight on this please share)

Here is a test:

import { UVsDebug } from 'three/examples/jsm/utils/UVsDebug'
console.log(UVsDebug)

Before:

  Asset     Size  
main.js  543 KiB

After:

  Asset      Size 
main.js  6.54 KiB

Finally I added "prepublishOnly": "node utils/modularize.js" to the package.json just to make sure the jsmodules are always up-to-date.

@@ -7,6 +7,7 @@
"jsnext:main": "build/three.module.js",
"module": "build/three.module.js",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be a problem if the module entry is build/three.module.js but the JSM example files all import from src/Three.js. In this case...

import { Vector3 } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

... GLTFLoader will create Vector3 instances that are not the same as the Vector3 instances from three.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!

My objective was to find a solution for people who already import the main three.js from the src/ folder for the tree-shaking to take effect, like this:

import { Vector3 } from 'three/src/math/Vector3';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

Currently if they do that for the examples/jsm the whole bundle will be imported as well.

Is there a way we can achieve this in your opinion?

@looeee
Copy link
Collaborator

looeee commented Apr 22, 2019

In #16220 we're considering converting the examples to use <script type=module. There should be very little overhead when importing a couple of files, as will happen when we use three.module.js.

However if we do this while importing from src/, we'll be importing potentially hundreds of files, which means hundreds of extra network requests. We'll need to consider how much that will degrade performance.

@marcofugaro
Copy link
Contributor Author

@looeee I didn't know about those plans, you're right, three.js needs to be imported as a bundle in the esmodules examples.

So how would we go about importing

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

without importing the whole bundle like we do with

import { Vector3 } from 'three/src/math/Vector3'?

@looeee
Copy link
Collaborator

looeee commented Apr 23, 2019

Hmm, I can't think of any easy way around this right now. We either get easy tree-shaking (but Webpack only, I think?), or we get to convert the examples to use <script type="module".

I'm inclined to think that converting the examples is more important, and it's up to people to get tree-shaking working in their own apps. They would need to do this:

import { Vector3 } from 'three/src/math/Vector3'; // from NPM package
import { OrbitControls } from './local_dir/OrbitControls.js';

Then in the local copy of OrbitControls, they'll need to change

import {
	EventDispatcher,
	MOUSE,
	Quaternion,
	Spherical,
	Vector2,
	Vector3
} from "../../../build/three.module.js";

to import from the various three/src/... files in the NPM package. Which is going to be a bit annoying if they are using lots of examples. But we can at least make things easier by documenting this and adding "sideEffects": false in package.json.

@marcofugaro
Copy link
Contributor Author

You're right, webpack should tree-shake the three.module.js, but it doesn't and I found a bug about it. I guess let's leave it like this and wait for the tools to catch up.

I'll make a separate PR for the "sideEffects": false flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants