I'm looking for a way to strip out debug code from functions so I can add test hooks to closures. I've read Google Closure Compiler advanced: remove code blocks at compile time and tested out removing debug code with the following:
/** @define {boolean} */
var DEBUG = true;
if (DEBUG) {
console.log('remove me');
}
Simple optimisation with --define='DEBUG=false'
reduces this to var DEBUG=!1;
. The same applies for this:
/** @const */
var DEBUG = false;
if (DEBUG) {
console.log('remove me');
}
Where I run into trouble is using this convention inside a function:
/** @const */
var DEBUG = false;
function logMe() {
if (DEBUG) {
console.log('remove me');
}
}
This reduces to the following:
var DEBUG=!1;function logMe(){DEBUG&&console.log("remove me")};
I would expect it to reduce further to:
var DEBUG=!1;function logMe(){};
Is there a reason this is not working as expected? I'm really just looking for a clean way to strip debug code and am not ready to take the plunge into advanced optimizations.
Update
Per @John's answer, I implemented my own compiler and have found that the following configuration will remove if (DEBUG) {}
from inside and outside the code for the case of a @define
:
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
//options.setInlineConstantVars(true);
options.setInlineVariables(CompilerOptions.Reach.ALL);
options.setDefineToBooleanLiteral("DEBUG", false);
This works well enough for a single file with the following limitations:
- This requires
var DEBUG
to be defined in each file, which is bad practice. - When combining multiple files, you can only have a single
var DEBUG
or the compiler can't optimize around it. This could be avoided by compiling each file individually and merging them. - Because the value is defined at the beginning of the file, there's no flexibility to receive the value beforehand.
I've toyed with the idea of removing all var DEBUG
definitions from the files and injecting it into the source or extern before execution, but I've run into two issues:
- Defining it in extern appears to do nothing.
- Undefined
DEBUG
in the uncompiled code throws a reference error in the browser.
The ideal option would be to test window.DEBUG
, which does not throw a reference error. Unfortunately, while injecting /** @const */ var window = {}; /** @const */ window.DEBUG = false;
works at the top level, reducing if (window.DEBUG) {}
, the optimization is actually reverted if placed in a function.
Unless another compiler option works the only option that would really make sense is to go with window.DEBUG
and before compilation inject /** @const */ var DEBUG = false;
and to a global replace of /\bwindow.DEBUG\b/
with DEBUG
. Is there a better way?
I'm looking for a way to strip out debug code from functions so I can add test hooks to closures. I've read Google Closure Compiler advanced: remove code blocks at compile time and tested out removing debug code with the following:
/** @define {boolean} */
var DEBUG = true;
if (DEBUG) {
console.log('remove me');
}
Simple optimisation with --define='DEBUG=false'
reduces this to var DEBUG=!1;
. The same applies for this:
/** @const */
var DEBUG = false;
if (DEBUG) {
console.log('remove me');
}
Where I run into trouble is using this convention inside a function:
/** @const */
var DEBUG = false;
function logMe() {
if (DEBUG) {
console.log('remove me');
}
}
This reduces to the following:
var DEBUG=!1;function logMe(){DEBUG&&console.log("remove me")};
I would expect it to reduce further to:
var DEBUG=!1;function logMe(){};
Is there a reason this is not working as expected? I'm really just looking for a clean way to strip debug code and am not ready to take the plunge into advanced optimizations.
Update
Per @John's answer, I implemented my own compiler and have found that the following configuration will remove if (DEBUG) {}
from inside and outside the code for the case of a @define
:
CompilerOptions options = new CompilerOptions();
CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
//options.setInlineConstantVars(true);
options.setInlineVariables(CompilerOptions.Reach.ALL);
options.setDefineToBooleanLiteral("DEBUG", false);
This works well enough for a single file with the following limitations:
- This requires
var DEBUG
to be defined in each file, which is bad practice. - When combining multiple files, you can only have a single
var DEBUG
or the compiler can't optimize around it. This could be avoided by compiling each file individually and merging them. - Because the value is defined at the beginning of the file, there's no flexibility to receive the value beforehand.
I've toyed with the idea of removing all var DEBUG
definitions from the files and injecting it into the source or extern before execution, but I've run into two issues:
- Defining it in extern appears to do nothing.
- Undefined
DEBUG
in the uncompiled code throws a reference error in the browser.
The ideal option would be to test window.DEBUG
, which does not throw a reference error. Unfortunately, while injecting /** @const */ var window = {}; /** @const */ window.DEBUG = false;
works at the top level, reducing if (window.DEBUG) {}
, the optimization is actually reverted if placed in a function.
Unless another compiler option works the only option that would really make sense is to go with window.DEBUG
and before compilation inject /** @const */ var DEBUG = false;
and to a global replace of /\bwindow.DEBUG\b/
with DEBUG
. Is there a better way?
6 Answers
Reset to default 4Use @define annotation:
@define {boolean}
DEBUG = true;
And compile with option
--define="DEBUG=false"
A custom build of the compiler would allow you to do this. You basically want to "inline constant variables":
options.setInlineConstantVars(true);
You could add it here, in applySafeCompilationOptions: http://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/javascript/jscomp/CompilationLevel.java?r=706
Or you could use the Java API and add the option (without modifying the compiler's code). Michael Bolin given an example of how to do this here:
http://blog.bolinfest.com/2009/11/calling-closure-compiler-from-java.html
This is an old answer, but I found a way that's not mentioned here.
(function(){
var DEBUG = true;
if (DEBUG) {
if (something === "wrong") {
console.warn("Stop! Hammer time!");
}
else if (something === "as expected") {
console.log("All good :-)");
}
}
foo();
})();
With ADVANCED_OPTIMIZATIONS this compiles to this:
"wrong" === something ?
console.warn("Stop! Hammer time!") :
"as expected" === something && console.log("All good :-)");
foo();
In our build script we can rewrite the DEBUG line to set it to false
, which would then yield this output.
foo();
The reason this happens is Closure will remove unreachable code. By creating the closure, and defining a local variable, Closure can see that we can't do something like window.DEBUG === true
, so the code is guaranteed to never be run.
Your DEBUG
variable is currently global. GCC will not remove or rename global variables in simple optimization mode, so they'll remain available to any code in other scripts that might possibly want to access them. Try enclosing your code into anonymous function.
The way i solved the problem of "removing debug functions from closure compiled javascript using SIMPLE_OPTIMIZATION" was by combining a similar method as @John proposes as well as using some of @Brian Nichols update. I could only get the compiler to remove the lines by placing this is the global scope of my main js file and doing a custom compile (using multiple .js files this still removed them)
/** @const
* @type {boolean}
*/
var DEBUG = false;
//and used this format for my debug function
DEBUG && myLog('foo');
and then compiling the closure-compiler java with ant to include this option options.setInlineVariables(CompilerOptions.Reach.ALL); under the applySafeCompilationOptions function in the CompilationLevel.java file as @john suggests. This worked for me and didnt break my codebase as ADVANCED did...
Remove var DEBUG = true;
from your code and convert all your conditions that check if (DEBUG)
to if (goog.DEBUG)
. Modify your compiler option to read --define goog.DEBUG=false
. The goog
variable is built into the Closure Library API to provide options and flags for the compiler.
if
blocks at the script level. I'm trying to understand why this approach doesn't work one level down in a function. – Brian Nickel Commented Jul 9, 2012 at 20:39