Optimization adds large amounts of CoffeeScript interpreter to results
mjtko opened this issue · 11 comments
I'm not convinced that after optimization (ie. JavaScript has been generated from the CoffeeScript) that we need to include the CoffeeScript interpreter within the optimized output.
I have a (rudimentary) proof of concept that prevents this from occurring - if you think it's worth it, I'll fork and push my changes.
I wonder, is it just enough to use
exclude: ["cs"]
in the build profile to not include the interpreter in the output. I'll check it when I investigate the other bug you opened, but if you have tried this already, it would be good to know if it worked or not.
Yeah, that was my first thought. :-)
Unfortunately, based on my experience earlier this week (and boy, have I been through some iterations! ;-)) that's not quite enough because when the first module that has a 'cs!...' dependency is referenced, requirejs attempts to find 'cs' which causes cs.js to be requested as it's not included in the optimized output.
Of course, this works, but it does incur an additional request and associated bandwidth - I guess a 0 byte cs.js file would mitigate the increased bandwidth but, IMHO, it's a bit hacky. :)
My POC works by both supplying exclude: ['cs']
as well as getting the write phase of the build to write a "fake" module called cs
' to the optimized file - which basically consists of:
define('cs',function(){return{}};);
I haven't yet investigated if it can be further minimized to this:
define('cs');
This approach is also a bit hacky, but prevents the additional request, which suits my needs for now.
Ok, I've just revisited my initial steps and, further to this, using exclude['cs']
on a module with the cs plugin as distributed causes:
Tracing dependencies for: viewmain
Tracing dependencies for: main
Tracing dependencies for: cs
node.js:134
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Cannot read property '0' of undefined
at Function.parseNode (/root/rcs/tools/requirejs/bin/../build/jslib/parse.js:321:21)
at Function.recurse (/root/rcs/tools/requirejs/bin/../build/jslib/parse.js:105:35)
at Function.recurse (/root/rcs/tools/requirejs/bin/../build/jslib/parse.js:109:26)
[elided Function.recurse stack]
at Function.recurse (/root/rcs/tools/requirejs/bin/../build/jslib/parse.js:109:26)
This is with the same configuration as in issue #2, but with:
{
name: "viewmain",
exclude: ['cs']
},
I guess something in the requirejs static analysis is choking on the embedded CoffeeScript interpreter, because one of my later iterations on the cs plugin, which falls back to requiring coffee-script.js
as a dependency during runtime interpreting, doesn't fail this way.
I think it probably best if I fork and push some of my changes so you can take a closer look. Will try to get on that this evening (UK).
I think I ran across a similar "Cannot read property 0" problem trying to process an UglifyJS file, I pushed a fix for that issue here, so you may want to try that patch first, pulling the latest master code will get the change.
Yup, that alleviates the TypeError: Cannot read property '0' of undefined
problem, thanks.
Next hurdle was:
ReferenceError: CoffeeScript is not defined
at Function.<anonymous> (eval at <anonymous> (/root/rcs/tools/requirejs/bin/../build/jslib/requirePatch.js:155:21))
That was fixed by:
--- cs.js.orig 2011-04-02 20:07:40.000000000 +0100
+++ cs.js 2011-04-02 20:07:50.000000000 +0100
@@ -4213,8 +4213,6 @@
}
define({
- CoffeeScript: CoffeeScript,
-
version: '0.1.0',
load: function (name, parentRequire, load, config) {
As best I can tell, that property was surplus to requirements anyway - the load function refers to the CoffeeScript in scope rather than on this
(ie. the define Object), so I don't believe removing this will cause undue problems.
After this, the pure JS^1 version of the plugin now works as expected when excluded from the optimization process (ie. has to have its dependencies traced).
I'd actually worked around this by moving the bulk of this code out to a cs_pure.js and referencing that using the pluginBuilder
property inside a simplified cs.js (as mentioned above, one that instead uses a require of 'coffee-script' for runtime interpreting).
The additional request problem I describe above still holds, so the 'fake' plugin still needs to be written to the optimized output in order for the weighty cs.js not to get pulled in separately. I'm not sure of the most elegant way of accomplishing this; my current hack is goes something like this:
In outer scope:
var fakeModule = "define({});\n";
Inside the plugin object:
write: function (pluginName, name, write) {
// write the fake cs plugin module once
if (fakeModule) {
write.asModule(pluginName,fakeModule);
fakeModule = null;
}
if (name in buildMap) {
var text = buildMap[name];
write.asModule(pluginName + "!" + name, text);
}
}
I'm pretty sure you can come up with a better way of achieving the same or better effect though! :-)
Note^1 I call this the 'pure' JS version because one of my other iterations for a builder was to use coffee-script as a nodejs required library to perform the compilation.
I'm looking at fixing that "CoffeeScript not defined" error, I believe it is a problem where plugin files that are loaded as regular dependencies get their define call parsed out and it removes some implementation (a quirk of the optimizer). I'm hoping that by fixing that, the exclude thing will work as normal. A pluginBuilder approach certainly works around it, but I want to just distribute one file to get CoffeeScript capabilities, instead of two.
Ok, sounds good - anything that improves the robustness of the optimizer is definitely working forwards in a more productive way than my attempts at working around. :-)
However, my remaining concern is that, even with the exclude working as normal, requirejs will still pull in cs.js
over http even when the modules have been defined within the optimized file.
I suppose the proper solution is not the 'fake' module I mention above, but rather not to pull in plugins unless they're needed to resolve modules that have not yet been defined.
Trying to be clearer, I mean that, at the moment, something like the following will cause cs.js
to be loaded regardless:
define('cs!bar',function() { return {}; });
define('foo',['cs!bar'],function(bar) { return {}; });
I'd expect (hope :-)) this not to go near cs.js
at all, as cs!bar
has already been defined as a lump of plain old JS. Something like the following should still pull in cs.js
though, in order to resolve the cs!quux
dependency:
define('cs!bar',function() { return {}; });
define('foo',['cs!bar'],function(bar) { return {}; });
define('baz',['cs!quux'],function(quux) { return {}; });
I'll try and nail down some proper test cases/examples when I have a few more moments at my disposal!
That makes sense, I'll also check that require.js only loads the plugin if the requested resource is not already available. I can see where it is not doing that right now.
Hi Guys,
I´m running into the same issue. It does not seem to be fixed in the latest demo code. Just adding the "exclude: ['cs']" to the demo code doesn´t work either...
Did one of you solve the problem? Maybe I´m just to stupid to get it... ;)
Cheers,
Thomas
Hi thomas,
I've been living with my hack as outlined above thus far. Let me know if you want some pointers on how I integrated it. :-)
Cheers,
Mark.
I was thinking I needed to make sure the optimizer can properly load a loader plugin even if it is first not loaded as a loader plugin, so that it could be excluded, but still operate properly for the optimization build, but it turns out to resolve relative resource IDs that could be used when referencing the CoffeeScript modules, the plugin needs to be loaded by the loader, since the plugin cannot necessarily parse those IDs (for CoffeeScript dependencies, it is actually very easy, but the loader cannot assume, and must load the plugin to know for sure).
So the right optimization path would be a way to remove the coffeescript implementation from the loader so that it is a tiny file. There are two options:
- Use build pragmas. The optimizer does support build pragmas, so this would be easy to put in, but it is a bit ugly.
- Use a local "has()" implementation. Use a local has() API to bracket the coffeescript code. The optimizer already knows how to convert those to just if(false) kinds of calls, and at least Closure Compiler can remove that block when the code is minified. UglifyJS did not the last time I tried, but there is a newer version I can try.
#1 modifies the source file even before minification, #2 only works in tandem with minification. However I prefer #2 because it feels cleaner than pragmas.
That said, I will probably try to allow for both, but if I get the pragma implementation in quick, then at least that would allow for a small built file fairly quickly. I'll see if I can get in it in the next couple of days.