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

-- size, ++ speed #8824

Closed
wants to merge 9 commits into from
Closed

-- size, ++ speed #8824

wants to merge 9 commits into from

Conversation

tschw
Copy link
Contributor

@tschw tschw commented May 6, 2016

This PR adds a custom build step that replaces THREE & WebGL symbolic constants with numerals. It also contains some minor cleanup based on diagnostics from the JS build.

The build step is implemented both in Python and in JavaScript for both build variants. It has a debug mode that logs the replacements.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

Related: #8822

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

The package.json in build should be deprecated and replaced with the one in the root. It is no longer useful to have it in there.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@gero3

The package.json in build should be deprecated and replaced with the one in the root. It is no longer useful to have it in there.

Ah! What is a smooth way to deprecate it?

Is build.min a good name for the minifying build? Is there some established standard we can follow, maybe?

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@gero3
Does the root package.json run on Windows? Seeing cd ./utils/build && node ... I guess it might assume a UNIX-style shell (although I'm not sure what Windows can handle these days).

If so, can we just invoke the build from the root directory, maybe?

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

I like the idea of the constifier but not the implementation right now. Mostly because it requires us to keep track of constants in 2 places now.

@gero3

The package.json in build should be deprecated and replaced with the one in the root. It is no longer useful to have it in there.
Ah! What is a smooth way to deprecate it?

Just delete the file. It is just a relic now. Nobody should be using it anyway. It isn't kept up to date anyways.

Is build.min a good name for the minifying build? Is there some established standard we can follow, maybe?

I actually would add the following actually. That way it is similar to the python version.

script: {
  build: "npm run build.debug && npm run build.release",
  build.debug: "cd ./utils/build && node build.js --include common --include extras --output ../../build/three.js",
  build.release: "cd ./utils/build && node build.js --include common --include extras --minify --output ../../build/three.min.js"
}

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

@gero3
Does the root package.json run on Windows? Seeing cd ./utils/build && node ... I guess it might assume a UNIX-style shell (although I'm not sure what Windows can handle these days).

If so, can we just invoke the build from the root directory, maybe?

Yes, it also runs on WIndows. The build script is made to run specifically from the directory, similar to the python script.

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

@tschw
I don't get why you changed MaterialIdCount to materialIdCount.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

I don't get why you changed MaterialIdCount to materialIdCount.

I like the idea of the constifier but not the implementation right now. Mostly because it requires us to keep track of constants in 2 places now.

Because it doesn't.

See utils/build/constifier/dumper.html extracts all uppercase names on THREE and a GL context that have the type number. It then outputs the JSON file. To update simply copy&paste.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@gero3

I don't get why you changed MaterialIdCount to materialIdCount.

Also, these names are global variables, not constants. So they should follow the conventions for variables not constants.

We could put some deprecation code into Three.Legacy.js or, alternatively, if we really want them to be inconsistently uppercased, initialize them to null or blacklist them.

@tschw wrote:

See utils/build/constifier/dumper.html ...

I guess it might be possible to automate it even further, using this tool only only for hard-wiring the WebGL constants, extracting the THREE constants via node.js. Is it possible to load THREE in this context? I guess you may have some experience?

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

@tschw wrote:

See utils/build/constifier/dumper.html ...
I guess it might be possible to automate it even further, using this tool only only for hard-wiring the WebGL constants, extracting the THREE constants via node.js. Is it possible to load THREE in this context? I guess you may have some experience?

Using Eval the concatenated source code could get you the the THREE object which can be used in the findConstants object.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@gero3

Using Eval the concatenated source code could get you the the THREE object which can be used in the findConstants object.

Would it be a (better) option to just require the temp file? Does Three.js set module.exports when module is provided, that is?

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

It does. But you can't require a string without writing it to the disk somewhere.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@gero3

It does.

Ah cool!

But you can't require a string without writing it to the disk somewhere.

Right - it's only in memory with the JS build... Isn't eval deprecated? Also, extracting the constants every time would slow down the build quite a bit, which might be acceptable since we're talking release builds only. I don't think writing a temp file would dominate.

Maybe constant extraction should just be a separate invocation that performs a debug build as a prerequisite?

@mrdoob
What are your thoughts on the *IdCount considerations above?

Yet another option (that would allow them to remain uppercased) would be to move them onto the classes, e.g. Material.InstanceId. As a matter of personal taste, I'd like it better if this kind of naming would indicate something that does not change, however, I think it should be your call.

@gero3
Copy link
Contributor

gero3 commented May 6, 2016

uglify is very slow compared to the extraction process normally. It shouldn't take a second to get all of the constants.

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

Why don't we just adopt JavaScript symbols which are designed for this?

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol

Constantly like this do not really impact ThreeJS's size at all because they get reduced by gzip. This seems like a special case hard to maintain gzip step that isn't necessary. I would like to see the differences in resulting gzip size before and after this PR. Is it significantly better?

I've done my own experiments before with changing THREE to T and Vector3 to V3 and it made almost no difference in the resulting gzip file because well compression.

I do not understand the point of this PR.

Also this PR changes the static variable naming convention from first uppercase letter to lowercase for a number of constants. I am okay with changing convention but as its own PR.

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

@gero3 wrote:

The package.json in build should be deprecated and replaced with the one in the root. It is no longer useful to have it in there.

I completely agree. :)

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

Ah! What is a smooth way to deprecate it?

Just kill it, it doesn't work well and I doubt many are using it.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@bhouston

Symbols...

...won't make the properties constant. Only the data. Also, they are unsupported by most browsers.

Constantly like this do not really impact ThreeJS's size at all because they get reduced by gzip. This seems like a special case hard to maintain gzip step that isn't necessary.

There is no gzip step.

I would like to see the differences in resulting gzip size before and after this PR. Is it significantly better?

Just see above. It's 2KB gzipped and that equates to a lot of code...
I also see a ~ 10% JS speedup with Firefox.

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

There is no gzip step.

Most webservers serve *.js files (and other text files) as gzipped (or soon brotli) automatically, at least in professional contexts. https://en.wikipedia.org/wiki/HTTP_compression Many webservers, like nginx, and do this transparently and cache the gzipped intermediates automatically: http://nginx.org/en/docs/http/ngx_http_gzip_module.html

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@bhouston
I know. And I measured 2KB difference piping the resulting three.min.js file through gzip.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@bhouston

I've done my own experiments before with changing THREE to T and Vector3 to V3 and it made almost no difference in the resulting gzip file because well compression.

Those occur much more frequently, therefore compress to almost nothing anyway.

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

2K, that is sort of what I saw as well, that is 2% saving, which isn't much. This to me isn't a major issue.

I am not sure that is sufficient to obfuscate the source files like this and duplicate the constants in two places which increases the maintenance complexity of Three.js. I really do not like violating DRY just to save 2K: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself for just a 2K saving.

There are higher priority issues with ThreeJs in my opinion.

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

I slightly oppose this for now because of the DRY violation. But if that was addressed, I wouldn't have much of an opinion on this PR either way, the savings are minimal, but the build step can easily be turned off is needed, so it sort of nets out to even.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

I slightly oppose this for now because of the DRY violation.

Please read the full thread before opposing because there is none. Constant extraction was automated right from the start and I'm currently automating it further. It will be completely transparent.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@bhouston
Also using symbolic constants like we do inhibits optimizations, causes switch..case to deoptimize on Chrome, and makes the renderer slower (this is particularly true for the standardized WebGL constants which are eliminated). It also complicates the architecture because you have to pass gl around, just to access constants on it - so we may even want to generate a debug mode file with those constants to decouple access to them from the live context.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@bhouston

2K, that is sort of what I saw as well, that is 2% saving, which isn't much.

2K gzipped actually equates to a lot of code!

@abelnation
Copy link
Contributor

RE the cd utils/build step before running the build script, I agree that it would be more ideal if the build script could be run from any path. The script can detect it's own path via __dirname and then resolve all other paths relative to that.

see: https://github.com/abelnation/three.js/blob/improve-npm-build/utils/build/build.js#L8

@bhouston
Copy link
Contributor

bhouston commented May 6, 2016

If other tools use the constified version and try to use THREE.XXXXXX what happens? Do they have to run this script on all of their code too? Do the examples work with this new version without changes?

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

@bhouston

If other tools use the constified version and try to use THREE.XXXXXX what happens? Do they have to run this script on all of their code too? Do the examples work with this new version without changes?

THREE.XXXX all exist. So external code will of course keep working. You can use it, if you like to.

It's also safe to use code like

THREE.XXXX = 111;

and the token will not be replaced in this case.

@tschw
Copy link
Contributor Author

tschw commented May 6, 2016

Do the examples work with this new version without changes?

I wouldn't even dare to make this a PR if they didn't :)

@mrdoob
Copy link
Owner

mrdoob commented May 7, 2016

Would it be possible to split this PR in two? I would like to tackle the Object.assign() changes first and the constants later on.

@tschw
Copy link
Contributor Author

tschw commented May 7, 2016

Would it be possible to split this PR in two? I would like to tackle the Object.assign() changes first and the constants later on.

Sure.

Thought at first this one broke, but then I looked at the build and everything was looking fine. Turns out I was in fact just testing canvas / cube believing it was webgl / cube :).

@mrdoob
Copy link
Owner

mrdoob commented May 7, 2016

Thought at first this one broke, but then I looked at the build and everything was looking fine. Turns out I was in fact just testing canvas / cube believing it was webgl / cube :).

Haha!

@tschw
Copy link
Contributor Author

tschw commented May 8, 2016

@mrdoob
The threads grow long real quickly and I guess you can't always read it all. However, I figure (from upvoting above question) that you may not like that *IdCount renamer in this PR. If so, please help me pick one of the many alternatives there are. These two posts list the ones I could think of:

#8824 (comment)
#8824 (comment)

@mrdoob
Copy link
Owner

mrdoob commented May 10, 2016

The threads grow long real quickly and I guess you can't always read it all. However, I figure (from upvoting above question) that you may not like that *IdCount renamer in this PR.

Actually, I was thinking we could make these counters internal. Like...

THREE.Texture = ( function () {
    var id = 0;
    return function () {
        Object.defineProperty( this, 'id', { value: id ++ } );
    };
} )();

This should work fine with Object3D, Material and Texture. However, BufferGeometry currently uses the same counter Geometry uses and that'll create issues inside WebGLRenderer...

Also, I wonder if people relies on THREE.*IdCount for something...

@tschw
Copy link
Contributor Author

tschw commented May 10, 2016

@mrdoob

However, BufferGeometry currently uses the same counter Geometry uses

How about Geometry.id (static), in that case? Or applying this pattern consistently?

Also, I wonder if people relies on THREE.*IdCount for something...

The latter would keep it exposed and allow deprecation of the old name via Three.Legacy, but maybe it's exotic enough to just solve it by documenting it for the next release on the Migration page?

@tschw
Copy link
Contributor Author

tschw commented May 16, 2016

/ping @mrdoob
What about this one?

@mrdoob
Copy link
Owner

mrdoob commented May 17, 2016

I like the idea, but the *IdCount issue has become a bit of a blocker 😕

Rather than having to notify the user that these variables have changed name, I think I would like to remove them (using the approach in #8824 (comment)) and solve the BufferGeometry/Geometry/DirectGeometry collision.

Then the PR/Feature should be ready to go.

@danrossi
Copy link

danrossi commented Nov 30, 2017

Hi I believe this is the best thread to be commenting on. How is this coming along to reduce the filesize ? Is there a way to remove all the shaders not in use at all ?

@donmccurdy
Copy link
Collaborator

@danrossi More recently three.js has moved to ES6 modules and Rollup for its build (#9310). This means that for the particular case of pruning parts of three.js that aren't needed for your particular project, you can use tree-shaking in build systems like Rollup or Webpack. Here is an example of how to set that up: https://stackoverflow.com/questions/41538767/how-do-i-tree-shake-three-js-using-webpack-or-rollup

As far as efforts to reduce the "default" size of threejs, I don't current status of this PR.

@danrossi
Copy link

danrossi commented Dec 6, 2017

Ok not a problem I might have to make another ticket. for my purpose the bundle has to stay global and so not packaged in. Yes I use such imports but it's global.

I have a special build of three which only exports the required files. I can reduce it down to 350kb or there abouts.

However there is alot of things that are still included unnecessarily like all the shaders and their respective uniforms configs.

The shaders need to be modular somehow. I don't need any of them apart for MeshBasicMaterial and create my own with a RawShaderMaterial.

Not sure where to ask about that.

@danrossi
Copy link

danrossi commented Dec 6, 2017

I figured out a hack here to prevent including all the shader code. In the rollup build its trying to transform them I just provide an empty string.

function glsl() {

	return {

		transform( code, id ) {

			if ( /\.glsl$/.test( id ) === false ) return;

			if (!/meshbasic/.test(id)) {
				return {
					code: 'export default " "',
					map: { mappings: '' }
				};
			}

			var transformedCode = 'export default ' + JSON.stringify(
					code
						.replace( /[ \t]*\/\/.*\n/g, '' )
						.replace( /[ \t]*\/\*[\s\S]*?\*\//g, '' )
						.replace( /\n{2,}/g, '\n' )
				) + ';';

			return {
				code: transformedCode,
				map: { mappings: '' }
			};

		}

	};

}

@danrossi
Copy link

danrossi commented Dec 6, 2017

This code has proven to be a problem for an external project I am working on. I think this importing causes more problems than good. It is expecting three installed via npm. I might have to convert them all back to global prefixes. I can't just tell that code to treat three as global. Even though I am bundling that three code beforehand.

I am trying to include the module of that project into a custom three bundle and its trying to include duplicated files and increase the filesize. I think it has even included minified sources from somewhere.

Without that project included and with the shaders now removed I have managed to trim it down to 260kb !

import {
    //MeshBasicMaterial,
    RawShaderMaterial,
    BoxBufferGeometry,
    Mesh,
    Group,
    LinearMipMapLinearFilter,
    LinearFilter,
    DoubleSide
} from 'three';

@danrossi
Copy link

danrossi commented Dec 8, 2017

I am using import constants for a custom shader with morph target requires. So I have to include these back into the bundle. All other shaders can go for my needs. It still setups up a heap of helper properties in the ShaderLib. So extra code not needed.

if (!/morphtarget|begin_vertex|project_vertex/.test(id)) {
				return {
					code: 'export default " "',
					map: { mappings: '' }
				};
			}

@danrossi
Copy link

danrossi commented Dec 8, 2017

Please refer to this discourse in regards to one way tree shaking problems.

The import system is only designed to bundle three.js into an app. You can use the elegant import setup in a 3rd party module build and set three as global in the rollup config.

But when trying to bundle that module back into a custom three bundle you can't. The import code is tree shaking duplicated three module code for no reason and increases the size about 3 times. I think it even includes minified code.

For bundled my custom modules I have to hard code locations of the sources. I am using three github sources not npm. And don't even want it to look for npm three.

Sorry I don't know where else to report this.

https://discourse.threejs.org/t/tree-shaking-three-js/1349/17?u=danrossi

@mrdoob
Copy link
Owner

mrdoob commented Dec 28, 2017

2K, that is sort of what I saw as well, that is 2% saving, which isn't much. This to me isn't a major issue.

👍

@mrdoob mrdoob closed this Dec 28, 2017
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.

7 participants