Reputation: 1733
First off: I am aware of this SO question and also the externs extractor here.
I'm working on my first game project using ThreeJS. Since I've built it in a modular manner, I intended to use Google's Closure Compiler to pack my results in the end. A little test showed that compiling (using Advanced Optimizations) would also minify methods and properties of constructed objects, for example THREE.Geometry
or THREE.Color
.
Checking the generated externs file, this is what I find on THREE.Color
at line 78 (ThreeJS r60):
"Color": function () {},
Obviously it lacks methods and properties, so the Closure Compiler wouldn'nt know it's not supposed to rename these.
I had another solution which would be to define exposures in a separate file and compile it along with the sources. It works fine, but it's a lot of manual work to define each used method and property this way. Also this is just a hackish workaround. The Closure Compiler simply resorts to not renaming any occurrences of these names on objects at all.
For example, all of these have the same effect:
/** @expose */
THREE.Geometry.vertices;
/** @expose */
THREE.vertices;
/** @expose */
Object.vertices;
window.vertices = function() {
console.log("foo");
};
window.vertices
will not be renamed. Consequentially this is not an ideal solution.
The actual question is: Is the Closure Compiler even capable of this? Or should I rather bake such dependencies into my compilation and have it rename all methods and properties I use?
Upvotes: 2
Views: 1339
Reputation: 98
If I have understood your question correctly, you are trying to compile your awesomeGame.js - which is dependent on threejs.js - but your references to threejs.js are being broken by closure's renaming process.
I agree that automated extern scripts probably have a lot of development ahead of them, and may not be a robust solution.
My project had a very similar problem. I can offer two possible solutions. Each has trade-offs. I'm not personally a threejs user, I will only illustrate general conventions here.
Per this discussion, after a library has executed and placed its root object in the global context, you can find it by starting at window
- and the window object is never renamed. Furthermore, bracket syntax is never renamed. Therefore, your compiled code can find THREE
at window['THREE']
and THREE.Geometry
at window['THREE']['Geometry']
, etc. Also, jQuery would be found at window['jQuery'], and underscore at window['_'].
I suggest that any time you need to access an external object or function from compiled code, say threejs.js, state some global defines at the top of the file, like so:
// awesomeGame.js:
// global defines:
var three_vertex = window['THREE']['Geometry']['vertex'],
three_rectangle = window['THREE']['Geometry']['rectangle'],
three_crossproduct = window['THREE']['Math']['crossproduct'],
jquery_ajax = window['jQuery']['ajax'];
// game assets:
var myVertex = new three_vertex(10,10),
myRectangle = new three_rectangle(10,10,10,10);
// do stuff:
console.log(myVertex['x'] + ' ' + myVertex['y']);
console.log(three_crossproduct(myVertex, myVertex));
myRectangle['paint']('black');
If you paste the above to the Compiler Service on pretty/advanced mode, you yield the following.
var a = window.THREE.Geometry.rectangle,
b = window.THREE.Math.crossproduct,
c = new window.THREE.Geometry.vertex(10, 10),
d = new a(10, 10, 10, 10);
console.log(c.x + " " + c.y);
console.log(b(c, c));
d.paint("black");
Positives: The full threejs.js reference is only printed once, in the global define. From then on, closure prints a container 'a' or 'b' instead. So you get most of the compression benefit, unlike the extern approach you've discussed.
Negatives:
Methods and properties from threejs.js objects -must- be consistently named in bracket syntax. note that the ['x'] and ['y'] properties, as well as the ['paint'] method, would have been renamed if they had not been bracketed. Set Closure to --warning_level=VERBOSE
, watch for JSC_INEXISTENT_PROPERTY
errors, and bracket them. If you forget anything, the compiler will remind you. Error Reference.
If your linter is upset about all the brackets, persuade it with --sub
, as in "tolerate subscripting". All jsLint errors/options.
Another approach: set yourself up a build script that concatenates all your source files, in order of dependency, into a grand temp file. If you're unix-y, your script might look like this:
#!/bin/bash
# build_and_run.sh
# get updates to google closure: http://code.google.com/p/closure-compiler/downloads/list
#
# local jslint:
# sudo apt-get install nodejs npm
# sudo npm jslint -g
rm temp.js
rm final.js
cat threejs.js \
awesomeGame_moduleOne.js \
awesomeGame_moduleTwo.js \
> temp.js
# docs: http://www.jslint.com/lint.html
jslint temp.js \
--maxerr=50 --sloppy --white --sub --plusplus \
--nomen --bitwise --browser \
--predef unescape \
--predef Uint8Array \
--predef Blob
java -jar compiler-20130823.jar \
--compilation_level ADVANCED_OPTIMIZATIONS \
--formatting pretty_print \
--language_in=ECMASCRIPT5 \
--js temp.js \
--js_output_file final.js
#maybe
nodejs final.js
# or
# https://developers.google.com/chrome/web-store/docs/get_started_simple
chromium --load-and-launch-app=./
# rinse, repeat
Positives: No brackets, no defines, no extrens, no middleware. Google closure sees all. Maximum compression of the final product. Full linting of the shipping project as well (scanning the original, divided source files may be confusing to the linter). Ship a single, integrated file.
Negatives: Immersion in the upstream source code. This may or may not be agreeable to you. Linting the upstream source will especially tempt you to impose code style upstream. Resist, resist. On the other hand, knowing the middleware you rely on has advantages.
Debugging the master.js file is also involved, as line numbers will be reported for final.js and you will need to identify the source file as you read. Use pretty mode, gentler compile settings, @preserve
comments, and plenty of console.log
and you will get a handle on it.
Finally, depending on the priorities and code conventions at threejs, compilation in Closure may not be supported or might introduce subtle changes in behavior. If that is the case, rule this option out. jQuery compatibility with closure is actively being discussed, for instance.
Anyway, I hope this answers your question. Reply if I'm off-the-mark. There are plenty 'include/modularization' frameworks out there, too. Maybe someone can fill in on that subject.
Upvotes: 2