47

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:

  1. Is using the semicolons a standard technique, or are there other ways to do it?

  2. Is Packer the only solution that provides this feature?

  3. Can the other solutions be adapted to work this way as well, or do they have alternative ways of accomplishing this?

  4. I will probably start using Closure Compiler eventually. Is there anything I should do now that would prepare for it?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tauren
  • 26,795
  • 42
  • 131
  • 167

12 Answers12

65

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 !

Jordan Arsenault
  • 7,100
  • 8
  • 53
  • 96
kares
  • 7,076
  • 1
  • 28
  • 38
  • 3
    This answer should be at the top. A bonus is that in advanced mode the `log` function would be stripped out as dead code. – Matthew Mar 13 '12 at 14:46
  • This sounds realy usefull. But testing it on the online closure-compiler page and local does not have the effect. – hellectronic Aug 21 '12 at 14:41
  • @hellectronic Actually it does work, you just need to put all of your code inside a closure, as otherwise the compiler thinks it's a global variable, and will not strip it out. This is probably best anyways so that you don't globalize any "use strict" statements – ansiart Dec 10 '12 at 20:37
  • Can someone explain this answer a little more? i'm still new to closure compiler and don't really get whats going on in this code. – tim peterson Sep 12 '13 at 01:31
  • `LOG` is annotated as a constant (non changing value) - closure assumes it never changes thus when a `LOG && ...` statements is reached it can safely 'remove' the whole statement since it has no side effects - it will always end up as "false" – kares Sep 12 '13 at 17:01
  • Hi @Kares, do you mean `console.log('...')` rather than `log('...')`? `log()` gives an error. Also, how does this work with multiple files, given as @anisart says, that it needs to be in a closure? You have to put your code above on each page? – tim peterson Sep 14 '13 at 15:24
  • 3
    A big downside to this method, unlike using the traditional `console.log()`, is that when `LOG=true`the line number reported in the dev tools for `log()` is where this function was defined and not where it was invoked. So all messages are reported as being on the same line and not where they occurred. This is pretty suboptimal given that the line number of the message is often key for debugging. – tim peterson Sep 18 '13 at 11:01
  • @timpeterson this is only a concept - if you do not like it down vote it :) ... I personally do not care about line numbers (on the other side you can `var log = console.log` or even better you can make sure it's always executed in the context of `console` if needed) and it was written around the time when leaving bare `console.log` statements in your code was not very safe and portable. – kares Sep 19 '13 at 05:55
  • 2
    For anyone confused about how to make this work, here's an easy way: 1) plonk `/** @const */ var LOG = false;` at the top, after the Closure settings, before your code. 2) find/change `console.log` to `LOG&&console.log` in your code. All console.log calls will be removed by the compiler. However, for me this only works wiht "ADVANCED_OPTIMIZATIONS" mode – user56reinstatemonica8 Aug 20 '14 at 11:23
25

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.

Fortes
  • 1,436
  • 2
  • 12
  • 14
6

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"/>
Sean Kinsey
  • 37,689
  • 7
  • 52
  • 71
6

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.

Kevin Beal
  • 10,500
  • 12
  • 66
  • 92
2

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

Druska
  • 4,752
  • 2
  • 30
  • 34
2

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

Stephen Chung
  • 14,497
  • 1
  • 35
  • 48
2

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.

pmiranda
  • 7,602
  • 14
  • 72
  • 155
2

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

j0k
  • 22,600
  • 28
  • 79
  • 90
1

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");
William
  • 166
  • 1
  • 4
0

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.

Marcel Korpel
  • 21,536
  • 6
  • 60
  • 80
0

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.

Nagev
  • 10,835
  • 4
  • 58
  • 69
0

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.

sll
  • 61,540
  • 22
  • 104
  • 156