让Closure Compiler去除日志函数的使用

12
我有一个日志API想要暴露给一些内部JS代码。我希望能够使用此API进行日志记录,但仅在进行调试构建时才能记录日志。目前,我已经部分地实现了它:它只会在调试构建时记录日志,但是当进行常规构建时,对此API的调用仍然存在于代码中。我希望在我使用 goog.DEBUG = false 进行编译时,闭包编译器能够移除这个基本上是死代码的部分。
日志定义:
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是一个提供给WebView的Java对象,正确地进行了外部扩展,做法如下:

var AndroidLog = {};

/**
 * Log out to the error console
 * 
 * @param {string} message The message to log
 */
AndroidLog.e = function(message) {};

然后,在我的代码中,我可以使用:

com.foo.Log.e("Hello!"); // I want these stripped in production builds
我的问题是:我该如何提供这个API并在代码中使用它,但在未使用goog.DEBUG = true编译时将对此API的任何调用都删除?目前,我的代码库中包含了一堆从未被调用过的Log API调用,导致代码变得臃肿。我想把它们删除。谢谢!

好的,在进一步的研究之后,似乎外部函数不会被内联。http://code.google.com/p/closure-compiler/issues/detail?id=230我仍然希望找到一个解决办法,而不是在每个调用前加上goog.DEBUG &&。 - Sky Kelsey
我自己用Python写了一个小脚本,作为构建过程的一部分,来剥离源代码中所有的调试信息。需要时我可以随时找到它。这很奇怪,因为这应该是一个非常普遍的需求。 - jfriend00
是的,这是第一个代码块中的最后一行。这可能是让我陷入麻烦的原因,对吧? - Sky Kelsey
if (!goog.DEBUG) AndroidLog.e = function() {}; 放在你的代码开头?请注意,如果你导出了 com.foo.Log.e,编译器不会将其删除。 - Stephen Chung
4个回答

1

Closure Compiler提供了四个选项CompilerOptions.java来剥离代码:1)stripTypes,2)stripNameSuffixes,3)stripNamePrefixes和4)stripTypePrefixes。Closure构建工具plovr通过其JSON configuration file options name-suffixes-to-striptype-prefixes-to-strip公开了stripNameSuffixesstripTypePrefixes

在《Closure:权威指南》的442至444页中有这些选项如何工作的优秀示例。以下行提供了常见用例:

options.stripTypePrefixes = ImmutableSet.of(“goog.debug”, “goog.asserts”);
options.stripNameSuffixes = ImmutableSet.of(“logger”, “logger_”); 

为了理解这些选项的细微差别并避免潜在的陷阱,我强烈建议阅读《闭包:权威指南》中的完整示例。

谢谢!我应该提一下,我正在使用Closure Ant任务,因此没有直接传递编译器选项的能力。我正在考虑子类化Ant任务以便传入自己的编译器选项,但是想先问一下是否有实现这个的方法。我也会看看plovr。 - Sky Kelsey
@SkyKelsey:上述的编译器选项用于剥离代码是 Closure Compiler Java 源码中的内部选项,即 CompilerOptions.java。但是,plovr 使得使用 stripNameSuffixes 和 stripTypePrefixes 轻而易举,无需编写 Java 代码。我目前正在为所有 Closure 工具编写一套 Ant 任务,以提供更多灵活性。希望它们能在 2012 年 4 月底之前准备好发布。 - Christopher Peisert

0

好的,事实证明如果我停止导出com.foo.Log()及其方法,这很容易做到。如果我确实想在某些特定情况下记录日志,但仍然要剥离内部代码中的日志调用,我只需声明两个类:

// 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);

0

0

不要像jfriend00建议的那样运行自己的脚本,而是看看编译器的定义api(这也是goog.DEBUG来自的地方),你默认拥有DEBUG、COMPILED,但在那里你可以自己编写。


1
这样做是缺失了重点。重点不仅仅是关闭调试输出(这很容易实现)。而是在部署前移除所有的调试代码,以便它们不会占用额外的空间、执行或甚至被展示给全世界。 - jfriend00
lennel,感谢您的建议。正如jfriend00所提到的那样,这只是让我传递数据的简单方法。我已经在使用通过goog.DEBUG参数传递的数据,并且它可以完美地剥离函数体。但是,这个空的函数体从未被内联到我的代码中以删除函数用法。我认为这是因为CC不能假定外部函数不会在运行时被修改? - Sky Kelsey
抱歉,我度假去了。如果您在编译时设置一个定义变量为false,编译器将在高级模式下省略该代码。 - lennel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接