JScript + Closure Compier

- JScript + Closure Compiler（Windows上）で、ファルダ内のJavascriptソースコードを結合、コンパイルする。
- JREさえインストールされていれば利用可能。
- 文字コードはUTF-8（BOMなし）LFを想定。
- 依存関係はgoog.require()とgoog.provide()で指定。
  - 出力ファイルにgoog.require()とgoog.provie()は含まれない。
    - コンパイルの後処理で削除する。
- Pythonが利用可能なら、素直にそっちを使うべき。
  - 組み込みのAntタスクはサポートしていない実行オプションがあったり、前処理、後処理を差し挟むのが面倒なので、ちょっと微妙。

```build.js
var COMPILER         = 'closure-compiler.jar';
var DEST_DEVELOPMENT = '../../dest.js';
var DEST_TEST        = '../qunit/test.js';
var DEST_PRODUCTION  = '../../dest.min.js';
var FOLDER_SOURCES   = '../../sources';
var FOLDER_TESTS     = '../../tests/qunit';
var FOLDER_EXTERNS   = 'externs';
var ENCODING         = 'utf-8';
var TEMPLATE         = '"(function(window) {%output%})(window);"';

// Gets file names in a folder recursively.
var getFileNames = function(fileName) {
	var fileSystem = new ActiveXObject('Scripting.FileSystemObject');
	if (fileSystem.FileExists(fileName)) {
			// If the file is a file (not a directory).
		return [fileName];
	}
	if (!fileSystem.FolderExists(fileName)) {
		return [];
	}

	var filenames = [];

	// Searches a current folder.
	var folder = fileSystem.GetFolder(fileName);
	for (var e = new Enumerator(folder.Files); !e.atEnd(); e.moveNext()) {
		filenames.push(e.item().Path);
	}

	// Seaches child folders.
	for (var e = new Enumerator(folder.SubFolders); !e.atEnd(); e.moveNext()) {
		filenames = filenames.concat(getFileNames(e.item()));
	}
	return filenames;
};

// Reads a file with UTF-8.
var readFile = function(fileName) {
	try {
		var stream = new ActiveXObject('ADODB.Stream');
		stream.Type = 2;
		stream.charset = ENCODING;
		stream.Open();
		stream.LoadFromFile(fileName);
		return stream.ReadText(-1);
	} finally {
		stream.Close();
	}
};

// Writes a text to a file with UTF-8 except a BOM.
var writeFile = function(fileName, text) {
	try {
		// Writes a text to a stream with UTF-8.
		var stream = new ActiveXObject('ADODB.Stream');
		stream.Type = 2;
		stream.Charset = ENCODING;
		stream.Open();
		stream.WriteText(text);

		// Changes a stream type.
		stream.Position = 0;
		stream.Type = 1;

		// Skips a BOM and keeps a text.
		stream.Position = 3;
		var bytes = stream.Read();

		// Resets a stream and writes a text to.
		stream.Position = 0;
		stream.SetEOS();
		stream.Write(bytes);

		// Saves a stream.
		stream.SaveToFile(fileName, 2);
	} finally {
		stream.Close();
	}
};

try {
	// Runs Closure Compiler.
	var runClosureCompiler = function(options) {
		var command = 'java';
		for (var key in options) {
			switch (key) {
				case '--externs':
				case '--formatting':
					for (var i = 0; i < options[key].length; i++) {
						command += ' ' + key + ' ' + options[key][i];
					}
					break;
				case '--js':
					command += ' ' + key;
					for (var i = 0; i < options[key].length; i++) {
						command += ' ' + options[key][i];
					}
					break;
				default:
					command += ' ' + key + ' ' + options[key];
					break;
			}
		}

		var shell = new ActiveXObject('WScript.Shell');
		var exec = shell.Exec(command);

		while (exec.Status === 0) {
			WScript.Sleep(10);
		}

		var errorMessage = '';
		while (!exec.StdErr.AtEndofStream) {
			errorMessage += exec.StdErr.ReadLine() + '\n';
		}
		if (errorMessage !== '') {
			throw {command: command, errorMessage: errorMessage};
		}
	};

	// Generates nx.js.
	runClosureCompiler({
		'-jar'		                    : COMPILER,
		'--charset'                     : ENCODING,
		'--compilation_level'           : 'WHITESPACE_ONLY',
		'--manage_closure_dependencies' : 'true',
		'--js_output_file'              : DEST_DEVELOPMENT,
		'--js'                          : getFileNames(FOLDER_SOURCES),
		'--output_wrapper'              : TEMPLATE,
		'--formatting'                  : [
			'pretty_print',
			'print_input_delimiter'
		]
	});

	// Removes Google directives from nx.js.
	var content = readFile(DEST_DEVELOPMENT)
		.replace(/goog\.require\(.*\);\s?/g, '')
		.replace(/goog\.provide\(.*\);\s?/g, '');
	writeFile(DEST_DEVELOPMENT, content);

	// Generates nx.min.js.
	runClosureCompiler({
		'-jar'		          : COMPILER,
		'--charset'           : ENCODING,
		'--compilation_level' : 'ADVANCED_OPTIMIZATIONS',
		'--js_output_file'    : DEST_PRODUCTION,
		'--js'                : [
			DEST_DEVELOPMENT
		],
		'--externs'           : getFileNames(FOLDER_EXTERNS),
		'--output_wrapper'    : '"(function() {%output%})();"'
	});

	// Generates test.js.
	if (FOLDER_TESTS.length >= 1) {
		runClosureCompiler({
			'-jar'                : COMPILER,
			'--charset'           : ENCODING,
			'--compilation_level' : 'WHITESPACE_ONLY',
			'--js_output_file'    : DEST_TEST,
			'--js'                : getFileNames(FOLDER_TESTS),
			'--formatting'        : [
				'pretty_print',
				'print_input_delimiter'
			]
		});
	}
}  catch (e) {
	WScript.Echo(e.command);
	WScript.Echo(e.errorMessage);
}
```