Preprocessor-Style Comments

Ajax Minifier allows for developers to use C-style preprocessor-like directives to output different code for different builds.

Debug

By default, Ajax Minifier removes “debug” statements from the code. This includes debugger statements, calls to “debug” namespaces, and all code that is between ///#DEBUG and ///#ENDDEBUG comments. To not exclude debug code in AjaxMin output, use the –debug switch.  The “debug” namespaces by default are: Debug, $Debug, WAssert, Msn.Debug, and Web.Debug. Calls to any of those namespaces will be removed when minified unless debug mode is turned off.

The –debug switch can be used to modify that list of “debug” namespaces on the command line, but the sources themselves can also clear or add namespaces. To clear the list so nothing qualifies as a “debug” namespace, add this to your source code (make sure there is no space before the assignment operator, and nothing but whitespace and end-of-line afterwards):

///#DEBUG=

To add a namespace to the “debug” list, specify the name after the assignment operator – be sure to not have any space before or after the assignment operator:

///#DEBUG=My.Debug.Namespace

If there is anything other than the assignment operator immediately after the “DEBUG” string, the comment will be interpreted as a debug block, and all code after it until the next ///#ENDDEBUG will be ignored (if not in debug mode). By the same token, if you intend to create a debug/end-debug block, be sure your ///#DEBUG comment does not have an assignment operator after it.

 

IF-Conditions

There is also a more generic set of branching preprocessor comments that can be controlled via command-line switches. These preprocessor comments act on “defines” which are case-insensitive names using regular JavaScript identifier syntax. The syntax has a number of flavors. The simplest simply checks whether a define is present or not:

///#IF name
// only include this code if name is defined
///#ENDIF

The ///#IF can have an optional ///#ELSE clause as well:

///#IF name
// only include this code if name is defined
///#ELSE
// only include this code if name is not defined
///#ENDIF

The clause of the ///#IF directive can also perform simple checks against the value of the defined name. The syntax is ///#IF name op [value]. The name, obviously, is a case-insensitive preprocessor define name. The allowed operators are == (converting equals), != (converting not-equals), === (non-converting equals), !== (non-converting not-equals). Plus converting <, <=, >, and >=. The value is everything from the operator to the end of the line, with whitespace trimmed from both ends. Strings are not quoted. The == and != operators first do a string comparison between the value of the defined name and the value in the directive. If the values do not match, an attempt is made to convert both values to a double, and equality is checked numerically. The === and !== operators do a string comparison only and do not attempt to convert if there is no match. String-comparison operations for all four operators are case-insensitive. The <, <=, >, and >= operators always perform a numeric conversion and comparison.

For example, this ///#IF directive evaluates to true:

///#DEFINE foo=The quick brown fox
///#IF Foo ==  THE QUICK BROWN FOX
// this code is used
///#ENDIF

And this one also evaluates as true:

///DEFINE ver = 3.5
///#IF ver < 4
// this code is used
///#ENDIF

 

There are also two directives specific to checking whether these preprocessor identifiers are simply defined or not defined: ///#IFDEF (if defined) and ///#IFNDEF (if NOT defined), and both can also have the optional ///#ELSE branch:

///#IFDEF name
// only include this code if name is defined
///#ENDIF

///#IFDEF name
// only include this code if name is defined
///#ELSE
// only include this code if name is not defined
///#ENDIF

///#IFNDEF name
// only include this code if name is not defined
///#ENDIF

///#IFNDEF name
// only include this code if name is not defined
///#ELSE
// only include this code if name is defined
///#ENDIF

These ///#IF, ///#IFDEF and ///#IFNDEF constructs can also be nested to provide more flexibility. For example:

///#IFNDEF name1
    // only include this code if name1 is not defined
    ///#IFDEF name2
    // this code is included only if name1 is not defined but name2 is
    ///#ELSE
    // this code is only included if neither name1 nor name2 are defined
    ///#ENDIF
///#ENDIF

 

Defining Names

To define a name, use the –define switch, which has as its value a comma-separated list of names to define. For example:

ajaxmin.exe input.js –out output.js –define:FOO,BAR,ACK

By default, names are defined with the value of an empty string. If you wish to set a name with a particular value, follow the name with an equal-sign and the value. Remember that if the value contains any spaces, that entire portion of the command line will need to be enclosed in quotes:

ajaxmin.exe input.js –out output.js "-define:foo=the quick brown fox"

Remember that any comma or semicolon in the value are of the –define switch will be interpreted as a name delimiter, terminate the value and start a new name. Try to stay away from values that contain commas or semicolons.

As an alternative to the command-line switch, names can also be defined and undefined within the JavaScript code itself using the ///#DEFINE name and ///#UNDEF name preprocessor comments.

By default, turning on the –debug switch also defines the DEBUG name, and turning it off un-defines it.

Users of the DLL version and the MSBuild task can also take advantage of these defines. The DLL CodeSettings object has a property named PreprocessorDefineList, which can be set to a string consisting of a comma-separated list of names. The Task DLL also has a property, named JsPreprocessorDefines, which is a string consisting of a comma-separated list of names to define:

<AjaxMin JsPreprocessorDefines="Foo,Bar,Ack"
    JsSourceFiles="@(JS)"  JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js"
    CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css"  />

Your build scripts can get very fancy about intelligently setting one or more defined names based on various config settings. You can also just rely on the “Conditional compilation symbols” field in your project’s Build properties box. To add everything in that entry field to your script’s defines, use the $(DefineConstants) replacement in your task.

 

Defining Global References

Sometime your code will need to reference a global value not defined within your module. By default, AjaxMin will report a warning for undefined global references. This is because it’s very common error to forgot to var a variable name in your code, but doing so creates an expando property on the window object – not good for performance, and a possible catastrophic naming collision should you happen to use a name that is actually defined and used by another module.

When you know you are referencing a global value that is defined in another module, that global should be declared to AjaxMin to prevent the error messages from being thrown. The standard way to do this is through the –global switch. But sometimes it’s not easy to be specifying the name of referenced globals on the command-line or in the build task of your project. It’s very convenient to be able to specify those known globals inside the very source file in which they are referenced. This can be done through the ///#GLOBALS preprocessor comment. Simply list the known global variables on the same line as the ///#GLOBALS comment, separated by whitespace:

///#GLOBALS $ Modernizr

This comment would define both the jQuery and Modernizr global objects, and your code would be able to reference them without generating any errors from AjaxMin.

Last edited Sep 9, 2012 at 2:52 PM by ronlo, version 15

Comments

No comments yet.