JavaScript Preprocessor
Introduction
This is a simple JavaScript preprocessor to enable conditional compilation. The syntax of the preprocessor is a subset of the C preprocessor. This means that all JavaScript preprocessor directives are valid C preprocessor directives and can thus be used with the C preprocessor (but not the other way around.) The following directives are supported:
- #define <identifier>
-
Defines an identifier.
- #undef <identifier>
-
Removes a defined identifier, or does nothing it the identifier is not defined.
- #ifdef <identifier>
-
Tests whether an identifier is defined, and parses the code block until either an
#endifor#elsedirective is reached. - #ifndef <identifier>
-
Tests whether an identifier is not defined, and parses the code block until either an
#endifor#elsedirective is reached. - #else
-
A code block executed when
#ifdefor#ifndefreturn false. Code is parsed until an #endif directive is reached. - #endif
-
Ends a code block.
A simple example of some JavaScript code using conditional compilation:
#define DEBUG
#define SAFE_LOOP
function find(object, key) {
#ifdef SAFE_LOOP
if (object.hasOwnProperty(key)) {
return object[key];
}
#else
if (key in object) {
return object[key];
}
#endif
#ifdef DEBUG
console.log('Warning: [find] returning undefined.');
#endif
return undefined;
}Note that the definitions do not necessarily need to be in the file, they can also be passed as arguments by your build tool of choice. This makes it easy to generate multiple versions of your project to―for example― create a debug build, or a specialized version designed to work in restrictive environments.
Running from Ant
Include the following code in your Ant build file, and ensure that the
refid attribute points to a path reference that
includes the Rhino JavaScript
engine. You may also wish to change the src
attribute of the scriptdef element to point to the
location you have installed the JavaScript preprocessor.
<scriptdef name="preprocess" src="js-preprocess.js" language="javascript">
<classpath>
<path refid="js.lib"/>
</classpath>
<attribute name="defines" />
<attribute name="todir" />
<attribute name="file"/>
<attribute name="tofile"/>
<element name="fileset" type="fileset" />
</scriptdef>Once you've done that, you can use the preprocessor to pre-process a set of files. Note that the preprocessor will create new files, so if you set your output directory to your input directory it will effectively strip the preprocessor directives from your source code.
<preprocess todir="target" defines="DEBUG">
<fileset dir="src" includes="**/*.js" />
</preprocess>You may also pre-process a single file using the
file and tofile
attributes.
<preprocess file="input.js" tofile="output.js" defines="DEBUG, SAFE_LOOP" />You can pass multiple defines to the
preprocessor by separating them with comma's.
Running from the command line using Rhino
You can also run the preprocessor from the command line using the Rhino JavaScript engine. The command line version takes the input file as the first argument and uses the remaining arguments as preprocessor definitions.
> rhino js-preprocess.js input.js [definition1 ...]The preprocessor writes to the standard output, so to write it to a file you can redirect the standard output to a file as follows:
> rhino js-preprocess.js input.js DEBUG SAFE_LOOP > output.jsThe example also shows how two definitions are passed to the preprocessor.
Using the preprocessor from JavaScript
The preprocessor exposes a single function called
preprocess: you can simple call this function with
as first argument a string (or an array of strings) containing the source
code you want preprocessed. The second and optional argument is an object
with as keys the predefined definitions you would like the preprocessor to
use. The preprocessor will return to you an array of strings as the
processed output. This output will have all preprocessor directives
removed.
var result = preprocess('...', {
DEBUG: true,
SAFE_LOOP: true
});Frequently Asked Questions
-
Are there any plans to support
#includedirectives? -
Not at the present time. I think JavaScript would be better off with a proper module system, which controls dependencies, file inclusions, and packaging.
-
The JSLint/minification stage in my build process stops working because of the preprocessor directives.
-
The simple solution is to run the preprocessor before running JSLint or minifying your code. Alternatively, you could set up a filter in your build process that strips out all the preprocessor directives.