最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Removing debug code from inside a function using Closure Compiler simple optimisations - Stack Overflow

programmeradmin6浏览0评论

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:

  1. This requires var DEBUG to be defined in each file, which is bad practice.
  2. 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.
  3. 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:

  1. This requires var DEBUG to be defined in each file, which is bad practice.
  2. 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.
  3. 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?

Share Improve this question edited May 23, 2017 at 10:29 CommunityBot 11 silver badge asked Jul 9, 2012 at 20:01 Brian NickelBrian Nickel 27.6k5 gold badges82 silver badges111 bronze badges 3
  • Closure Compiler's simple optimizations won't strip out unused code, just minify it. It's doing the minification correctly - the compiler doesn't know that this variable is an unchanging flag. You'll need to use the advanced optimizations if you want it to strip out unused code. – alecananian Commented Jul 9, 2012 at 20:31
  • Hi @AlecAnanian, per the first two examples, simple optimizations will remove always-false 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
  • Oh, I see - it looks like you have to use the specific goog.DEBUG flag for the simple optimization to take it into account. – alecananian Commented Jul 9, 2012 at 20:41
Add a comment  | 

6 Answers 6

Reset to default 4

Use @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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论