Exclude debug JavaScript code during minification - javascript

I'm looking into different ways to minify my JavaScript code including the regular JSMin, Packer, and YUI solutions. I'm really interested in the new Google Closure Compiler, as it looks exceptionally powerful.
I noticed that Dean Edwards packer has a feature to exclude lines of code that start with three semicolons. This is handy to exclude debug code. For instance:
;;; console.log("Starting process");
I'm spending some time cleaning up my codebase and would like to add hints like this to easily exclude debug code. In preparation for this, I'd like to figure out if this is the best solution, or if there are other techniques.
Because I haven't chosen how to minify yet, I'd like to clean the code in a way that is compatible with whatever minifier I end up going with. So my questions are these:
Is using the semicolons a standard technique, or are there other ways to do it?
Is Packer the only solution that provides this feature?
Can the other solutions be adapted to work this way as well, or do they have alternative ways of accomplishing this?
I will probably start using Closure Compiler eventually. Is there anything I should do now that would prepare for it?

here's the (ultimate) answer for closure compiler :
/** #const */
var LOG = false;
...
LOG && log('hello world !'); // compiler will remove this line
...
this will even work with SIMPLE_OPTIMIZATIONS and no --define= is necessary !

Here's what I use with Closure Compiler. First, you need to define a DEBUG variable like this:
/** #define {boolean} */
var DEBUG = true;
It's using the JS annotation for closure, which you can read about in the documentation.
Now, whenever you want some debug-only code, just wrap it in an if statement, like so:
if (DEBUG) {
console.log("Running in DEBUG mode");
}
When compiling your code for release, add the following your compilation command: --define='DEBUG=false' -- any code within the debug statement will be completely left out of the compiled file.

A good solution in this case might be js-build-tools which supports 'conditional compilation'.
In short you can use comments such as
// #ifdef debug
var trace = debug.getTracer("easyXDM.Rpc");
trace("constructor");
// #endif
where you define a pragma such as debug.
Then when building it (it has an ant-task)
//this file will not have the debug code
<preprocess infile="work/easyXDM.combined.js" outfile="work/easyXDM.js"/>
//this file will
<preprocess infile="work/easyXDM.combined.js" outfile="work/easyXDM.debug.js" defines="debug"/>

Adding logic to every place in your code where you are logging to the console makes it harder to debug and maintain.
If you are already going to add a build step for your production code, you could always add another file at the top that turns your console methods into noop's.
Something like:
console.log = console.debug = console.info = function(){};
Ideally, you'd just strip out any console methods, but if you are keeping them in anyway but not using them, this is probably the easiest to work with.

If you use the Closure Compiler in Advanced mode, you can do something like:
if (DEBUG) console.log = function() {}
Then the compiler will remove all your console.log calls. Of course you need to --define the variable DEBUG in the command line.
However, this is only for Advanced mode. If you are using Simple mode, you'll need to run a preprocessor on your source file.
Why not consider the Dojo Toolkit? It has built-in comment-based pragma's to include/exclude sections of code based on a build. Plus, it is compatible with the Closure Compiler in Advanced mode (see link below)!
http://dojo-toolkit.33424.n3.nabble.com/file/n2636749/Using_the_Dojo_Toolkit_with_the_Closure_Compiler.pdf?by-user=t

Even though its an old question. I stumbled upon the same issue today and found that it can be achieved using CompilerOptions.
I followed this thread.
We run the compiler, from Java, on our server before sending the code to the client. This worked for us in Simple mode.
private String compressWithClosureCompiler(final String code) {
final Compiler compiler = new Compiler();
final CompilerOptions options = new CompilerOptions();
Logger.getLogger("com.google.javascript.jscomp").setLevel(Level.OFF);
if (compressRemovesLogging) {
options.stripNamePrefixes = ImmutableSet.of("logger");
options.stripNameSuffixes = ImmutableSet.of("debug", "dev", "info", "error",
"warn", "startClock", "stopClock", "dir");
}
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
final JSSourceFile extern = JSSourceFile.fromCode("externs.js", "");
final JSSourceFile input = JSSourceFile.fromCode("input.js", code);
compiler.compile(extern, input, options);
return compiler.toSource();
}
It will remove all the calls to logger.debug, logger.dev...etc.etc

If you're using UglifyJS2, you can use the drop_console argument to remove console.* functions.

I use this in my React apps:
if (process.env.REACT_APP_STAGE === 'PROD')
console.log = function no_console() {};
In other words, console.log will return nothing on prod enviroment.

I am with #marcel-korpel. Isn't perfect but works. Replace the debug instructions before minification. The regular expression works in many places. Watch out unenclosed lines.
/console\.[^;]*/gm
Works on:
;;; console.log("Starting process");
console.log("Starting process");
console.dir("Starting process");;;;;
console.log("Starting "+(1+2)+" processes"); iamok('good');
console.log('Message ' +
'with new line'
);
console.group("a");
console.groupEnd();
swtich(input){
case 1 : alert('ok'); break;
default: console.warn("Fatal error"); break;
}
Don't works:
console.log("instruction without semicolon")
console.log("semicolon in ; string");

I haven't looked into minification so far, but this behaviour could be accomplished using a simple regular expression:
s/;;;.*//g
This replaces everything in a line after (and including) three semicolons with nothing, so it's discarded before minifying. You can run sed (or a similar tool) before running your minification tool, like this:
sed 's/;;;.*//g' < infile.js > outfile.js
BTW, if you're wondering whether the packed version or the minified version will be 'better', read this comparison of JavaScript compression methods.

I've used following self-made stuf:
// Uncomment to enable debug messages
// var debug = true;
function ShowDebugMessage(message) {
if (debug) {
alert(message);
}
}
So when you've declared variable debug which is set to true - all ShowDebugMessage() calls would call alert() as well. So just use it in a code and forget about in place conditions like ifdef or manual commenting of the debug output lines.

I was searching for a built-in option to do this. I have not found that yet, but my favorite answer also does not require any changes to existing source code. Here's an example with basic usage.
Assume HTML file test.html with:
<html>
<script src="hallo.js"></script>
</html>
And hallo.js with:
sayhi();
function sayhi()
{
console.log("hallo, world!");
}
We'll use a separate file, say noconsole.js, having this from the linked answer:
console.log = console.debug = console.info = function(){};
Then we can compile it as follows, bearing in mind that order matters, noconsole.js must be placed first in the arguments:
google-closure-compiler --js noconsole.js hallo.js --js_output_file hallo.out.js
If you cat hallo.out.js you'd see:
console.log=console.debug=console.info=function(){};sayhi();function sayhi(){console.log("hallo, world!")};
And if I test with mv hallo.out.js hallo.js and reload the page, I can see that the console is now empty.
Hope this clarifies it. Note that I have not yet tested this in the ideal mode of compiling all the source code with ADVANCED optimizations, but I'd expect it to also work.

Related

Unit tests in JavaScript with mocked objects

So I'm working with an enterprise tool where we have javascript scripts embedded throughout. These scripts have access to certain built-in objects.
Unfortunately, the tool doesn't give any good way to unit test these scripts. So my thinking was to maintain the scripts in a repo, mock the built-in objects, and then set up unit tests that run on my system.
I'm pretty ignorant to how JavaScript works in terms of building, class loading, etc. but I've been just trying things and seeing what works. I started by trying out Mocha by making it a node project (even though it's just a directory full of scripts, not a real node project). The default test works, but when I try and test functions from my code, I get compiler errors.
Here's what a sample script from my project looks like. I'm hoping to test the functions, not the entire script:
var thing = builtInObject.foo();
doStuff(thing);
doMoreStuff(thing);
function doStuff(thing) {
// Code
}
function doMoreStuff(thing) {
// More Code
}
Here's what a test file looks like:
var assert = require('assert');
var sampleScript = require('../scripts/sampleScript.js');
describe('SampleScript', function() {
describe('#doStuff()', function() {
it('should do stuff', function() {
assert.equal(-1, sampleScript.doStuff("input"));
});
});
});
Problem happens when I import ("require") the script. I get compilation errors, because it doesn't builtInObject. Is there any way I can "inject" those built in objects with mocks? So I define variables and functions that those objects contain, and the compiler knows what they are?
I'm open to alternative frameworks or ideas. Sorry for my ignorance, I'm not really a javascript guy. And I know this is a bit hacky, but it seems like the best option since I'm not getting out of the enterprise tool.
So if I get it right you want to do the unit tests for the frontened file in the Node.js environment.
There are some complications.
First, in terms of Node.js each file has it's own scope so the variables defined inside of the file won't be accessible even if you required the file. So you need to export the vars to use them.
module.exports.doStuff = doStuff; //in the end of sample script
Second, you you start using things like require/module.exports on the frontend they'll be undefined so you'll get an error.
The easiest way to run your code would be. Inside the sample script:
var isNode = typeof module !== 'undefined' && module.exports;
if (isNode) {
//So we are exporting only when we are running in Node env.
//After this doStuff and doMoreStuff will be avail. in the test
module.exports.doStuff = doStuff;
module.exports.doMoreStuff = doMoreStuff;
}
What for the builtInObject. The easies way to mock it would be inside the test before the require do the following:
global.builtInObject = {
foo: function () { return 'thing'; }
};
The test just passed for me. See the sources.
Global variables are not good anyway. But in this case seems you cannot avoid using them.
Or you can avoid using Node.js by configuring something like Karma. It physically launches browser and runs the tests in it. :)

somewhat odd javascript code works in all major browsers, but fails with phantomjs/qunit

I work on an app with a javascript/html front-end and a back-end REST service. I mostly work on the back end service, but I'm attempting to add javascript unit tests to the build. I had someone help me with the javascript testing framework setup, using phantomjs, qunit, and jstestrunner, all referenced from Maven.
I wrote a trivial unit test for a module (we'll call it "data.daily.js") that begins like this:
Data.Daily = new Function();
Data.Daily.prototype = {
Just to be clear, this code runs every day in production, and appears to work fine in all major browsers (FF, IE, and Chrome).
The test looks like this:
requirejs.config({ shim: { 'data.daily': ['config'] } });
require(['data.daily'], function() {
'use strict';
module('data.daily');
test('data.daily.test.initialize', function() {
var dataDaily = new Data.Daily();
dataDaily.initialize(Config.AJAX_DAILY_DATA_BASE_URL, Config.MOCKDATA_AJAX_DAILY_DATA_BASE_URL);
deepEqual(dataDaily.getData(), {}, "object is \"" + JSON.stringify(dataDaily.getData()) + "\", but it should be empty object");
});
});
When I run this test, it fails like this:
ReferenceError: Can't find variable: Data, source: http://localhost:9080/data.daily.js:5
[data.daily] data.daily.test.initialize: failed: 1 passed: 0
Died on test #1 at http://localhost:9080/js/qunit.js:425
at http://localhost:9080/js/data.daily.test.js:17
at http://localhost:9080/js/require.js:1682
at http://localhost:9080/js/require.js:983
at http://localhost:9080/js/require.js:1194
at http://localhost:9080/js/require.js:129
at http://localhost:9080/js/require.js:1237
at each (http://localhost:9080/js/require.js:58)
at http://localhost:9080/js/require.js:1238
at http://localhost:9080/js/require.js:1043
at http://localhost:9080/js/require.js:1224
at http://localhost:9080/js/require.js:882
at callGetModule (http://localhost:9080/js/require.js:1249)
at http://localhost:9080/js/require.js:1578
at http://localhost:9080/js/require.js:1703: Can't find variable: Data, source: ReferenceError: Can't find variable: Data
The only way I can find to get this test working is to change "data.daily.js" in this way, adding a line before the existing lines:
var Data = {};
Data.Daily = new Function();
Data.Daily.prototype = {
Now I have to say that this looks logical to me, but the fact remains that the existing code works fine in all the major browsers. This code only started failing when referenced from the test.
Note that I also tried changing the test script instead, adding the "var Data = {}" line before the "var dataDaily = new Data.Daily()" line, but that had no effect.
So, can anyone explain what is going on here? Why does the original code work if it fails in the test. Is there something funky about how "require.js" works that makes this happen? Why didn't the test work by adding the line in the test, instead of the CUT (code under test)?
Ok, I've managed to resolve this.
The assignment is actually present in the existing production code, I just didn't think to look in ".html" files for it before. When I didn't find it in ".js" files, I thought something else was going on.
The reason it didn't work to put the line in the test script instead was because I was putting the line in the wrong place. The error actually occurs at configuration time, not when the test itself is executed, so the assignment had to be before the "requirejs.config()" call. Now the test works, without having to modify the CUT.

Make Closure Compiler strip log function usages

I have a logging API I want to expose to some internal JS code. I want to be able to use this API to log, but only when I am making a debug build. Right now, I have it partially working. It only logs on debug builds, but the calls to this API are still in the code when there is a regular build. I would like the closure-compiler to remove this essentially dead code when I compiler with goog.DEBUG = false.
Log definition:
goog.provide('com.foo.android.Log');
com.foo.Log.e = function(message){
goog.DEBUG && AndroidLog.e(message);
}
goog.export(com.foo.Log, "e", com.foo.Log.e);
AndroidLog is a Java object provided to the webview this will run in, and properly externed like this:
var AndroidLog = {};
/**
* Log out to the error console
*
* #param {string} message The message to log
*/
AndroidLog.e = function(message) {};
Then, in my code, I can use:
com.foo.Log.e("Hello!"); // I want these stripped in production builds
My question is this: How can I provide this API, use this API all over my code, but then have any calls to this API removed when not compiled with goog.DEBUG = true? Right now, my code base is getting bloated with a bunch of calls to the Log API that are never called. I want the removed.
Thanks!
The Closure Compiler provides four options in CompilerOptions.java to strip code: 1) stripTypes, 2) stripNameSuffixes, 3) stripNamePrefixes and 4) stripTypePrefixes. The Closure build tool plovr, exposes stripNameSuffixes and stripTypePrefixes through its JSON configuration file options name-suffixes-to-strip and type-prefixes-to-strip.
There are excellent examples of how these options work in Closure: The Definitive Guide on pages 442 to 444. The following lines are provided as common use cases:
options.stripTypePrefixes = ImmutableSet.of(“goog.debug”, “goog.asserts”);
options.stripNameSuffixes = ImmutableSet.of(“logger”, “logger_”);
To understand the nuances of these options and avoid potential pitfalls, I highly recommend reading the complete examples in Closure: The Definitive Guide.
Instead of running your own script as jfriend00 suggested I would look at the define api of the compiler (which is where goog.DEBUG comes from as well), you have DEBUG, COMPILED by default, but there you can roll your own.
OK, it turns out this is easy to do if I stop exporting com.foo.Log() and its methods. If I really want to be able to log in some specific cases, but still strip out the log calls in my internal code, I can just declare two classes for this:
// This will get inlined and stripped, since its not exported.
goog.provide('com.foo.android.Log');
com.foo.Log.e = function(message){
goog.DEBUG && AndroidLog.e(message);
}
// Don't export.
// This be available to use after closure compiler runs, since it's exported.
goog.provide('com.foo.android.production.Log');
goog.exportSymbol("ProductionLog", com.foo.android.production.Log);
com.foo.android.production.Log.log = function(message){
goog.DEBUG && AndroidLog.e(message);
}
// Export.
goog.exportProperty(com.foo.android.production.Log, "log", com.foo.android.production.Log.log);
I have modified a compiler and packaged it as an npm package.
You can get it here: https://github.com/thanpolas/superstartup-closure-compiler#readme
It will strip all logging messages during compilation

JSLint in my unit tests

I code according to JSLint standards (excluding a couple of options) and I thought it might be a good idea to work this into my in-browser unit tests so that I don't accidentally commit anything which doesn't pass it. I'm using QUnit, but the same can probably be applied to any in-browser testing framework.
I tried this code first of all:
test("code passes JSLint", function () {
var i, options;
options = {
browser : true,
plusplus : true,
unparam : true,
maxlen : 120,
indent : 4
};
// in QUnit `ok` is the equivalent of `assertTrue`
ok(JSLINT(this.code, options));
// Help me out a bit if it fails
for (i = 0; i < JSLINT.errors.length; i++) {
console.log(JSLINT.errors[i].line + ': ' + JSLINT.errors[i].reason);
}
});
Edit: Forgot to mention, I declared in the setup that this.code = myFunction.toString();.
Which works great in Chrome, so I committed and continued merrily coding. When I tried it in FF, I found that FF seems to strip all the white-space out of functions when it converts them into strings, so it fails.
I'm coding and testing locally, so using AJAX to download the JS file isn't really an option. Can anyone think of a better option, or is this just a complete waste of time? (Ignoring the benefits or drawbacks of JSLint as a whole please... that's for a different discussion)
What is your development environment? Jslint can be integrated into common IDEs like Eclipse and (I'm pretty sure) Visual Studio. I think that would be a better option then putting it into your unit tests even if it worked perfectly in the unit tests.
Otherwise to stick with the unit test approach maybe you could put a conditional in to only run the Jslint test if in Chrome - the things Jslint checks for don't need to be tested in multiple browsers.
Oddly, I'm seeing the opposite behavior (fails in chrome[v17.0.x], passes in FF[v10.0.1]). Either way, another solution is to tell JSLINT to not worry about the white spaces with white:true
Your code in coffeescript, setting JSLINT to ignore whitespace:
<script src="http://coffeescript.org/extras/coffee-script.js"></script>
<script src="https://github.com/douglascrockford/JSLint/raw/master/jslint.js"></script>
<script src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>
<script>
function myCode(){window.console.log("I have no whitespace");}
</script>
<script type="text/coffeescript">
test "Code is LINT'able", ()->
options =
browser:true
white:true #don't worry about whitespace
sloppy:true #allow missing "use strict"
ok JSLINT(myCode.toString(), options)
JSLINT.errors.forEach (error)->
console.log "#{error.line}: #{error.reason}"
</script>

Javascript array : why is this not valid?

I have the following working code :
var routes = [];
Eclipse validator for javascript prints the following warning :
Type mismatch: cannot convert from any[] to any
What is wrong with my empty array ?
Edit : the warning disappeared later. Apparently Eclipse was wrong and the question needs to be closed. Sorry about that.
Your JavaScript is valid, the problem is with JSDT plugin for Eclipse. In the latest version they introduced a type verification which is problematic in many situations - not only for empty arrays (like in your case). Another typical case may look like this: a = b || c;
The plugin will complain when b and c are of different types (which is absolutely valid code for JavaScript). There is several bugs already reported to JSDT developers about this problem, but the issues are not fixed yet.
Unfortunately currently it is not possible to switch off the type verification using JSDT configuration screen in Eclipse. I switched it off directly from the JSDT source code. To do this, please follow these steps:
Download the JSDT source code from Eclipse WebTools Project
Open the org.eclipse.wst.jsdt.debug.core project with Eclipse. Make sure you have Eclipse SDK installed. It might also be required to adjust some dependencies in the plugin.xml file.
The type verification is located in computeSeverity method of ProblemReporter class.
To switch off type verification replace the line: case IProblem.TypeMismatch: return ProblemSeverities.Warning; with case IProblem.TypeMismatch: return ProblemSeverities.Ignore;
Build the project and close Eclipse.
In Eclipse folder find the file named org.eclipse.wst.jsdt.core<version>.jar - make a safe copy of it, then open the jar file and replace the ProblemReporter.class file with the one you compiled in the step 5 (the file is in bin folder of your project).
Start Eclipse and clean your JavaScript project. All type checking will be ignored by JSDT.
Important! Make sure you've downloaded the same version of JSDT that you are using in Eclipse. Eventually instead of replacing a single file you can replace the entire plugin.
If you don't want to download and compile the plugin by yourself you can try with my fixed version. I've placed it on my FunctionSack webpage. I am using Eclipse 3.7 (Indigo) with JSDT 1.3.0, so make sure you have similar configuration if you want to use my file.
The eclipse's web tools platform plugin (wtp) includes a JavaScript validator that is somewhat allergic to the object literal "{}" and array literal "[]" notations, it also shows up some other annoying 'problems' such as 'missing semicolon' etc.
I have found the best solution for me and for my nerves is to disable the wtp's-embedded JavaScript validation completely and use a third-party plug-in.
Surprisingly it's not that easy to disable JavaScript validator. Every eclipse versions requires a different approach, so try the following guide:
In Eclipse prior to version 3.6 it was possible to disable javascript
validation via
'Window->Preferences->JavaScript->Validator->Errors/Warnings->[ ]
Enable JavaScript Semantic validation" - but this doesn't seem to
work in 3.7 Indigo see the eclipse bug
In 3.7 Indigo try Project -> Properties -> Builders - > [ ] JavaScript Validator
If doesn't help, try Project -> Properties -> JavaScript -> Include Path -> Source -> Excluded -> Edit -> Exclusion Patterns -> Edit -> */
If nothing above helps, open .project file and delete/comment out "<nature>org.eclipse.wst.jsdt.core.jsNature</nature>" line
After disabling the wtp validator you can try using a third party tool such as jsLint/jsHint
As what I observed in my testing so far, the problem occurs when you define a local variable in a function which body following a return keyword. This scenario can be shown in the following example (assuming the code is in a top level JavaScript file, means not inside any module/function so the first a is defined in global scope):
var a=[]; //Global variable assignment, no warnings
function f1(){ //global function
var a=[]; //level 1 local variable, no warnings
return a;
}
function f2(){ //local functions and member functions
var f = function(){
var a=[]; // no warinings
return a;
};
this.f = function(){
var a=[]; //no warnings
return a;
};
return f; //returning a defined funciton is OK
}
function f3(){ //returning a function
return function(){
var a=[]; //warning: Type mismatch: cannot convert from any[] to any
return a;
};
}
So the workaround is simple: change f3 to
function f3(){ //returning a defined function
var f = function(){
var a=[]; //warning is gone!
return a;
};
return f;
}
It's valid Javascript (assuming you're not writing it in some wierd context like the middle of an expression :P) so either the "Eclipse validator for javascript" is broken, or you're not using the "Eclipse validator for javascript" after all.
This is completely valid JS and sounds like Eclipse may be using the incorrect syntax plugin or something is ... well ... wrong.
You can confirm this by trying...
var routes = [];
routes.push({ url: '/' });
console.log(routes.length);

Categories