closure-builder
Version:
Simple Closure, Soy and JavaScript Build system
438 lines (402 loc) • 12.1 kB
JavaScript
/**
* @fileoverview Closure Builder - Build compilers
*
* @license Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author mbordihn@google.com (Markus Bordihn)
*/
var browserify = require('browserify');
var cleanCss = require('clean-css');
var fs = require('fs-extra');
var log = require('loglevel');
var marked = require('marked');
var path = require('path');
var validator = require('validator');
var buildTools = require('./build_tools.js');
var fileTools = require('./tools/file.js');
var pathTools = require('./tools/path.js');
var remoteTools = require('./tools/remote.js');
var textTools = require('./tools/text.js');
var closureCompiler = require('./compilers/closure-compiler/compiler.js');
var closureTemplatesCompiler = require(
'./compilers/closure-templates/compiler.js');
/**
* Build Compilers.
* @constructor
* @struct
* @final
*/
var BuildCompilers = function() {};
/**
* Avalible memory to avoid "Out of memory" issues.
* @type {number}
*/
BuildCompilers.SAFE_MEMORY = buildTools.getSafeMemory() || 512;
/**
* Running in test mode ?
* @type {boolean}
*/
BuildCompilers.TEST_MODE = typeof global.it === 'function';
/**
* Copy file from src to dest.
* @param {!string} src
* @param {!string} dest
* @param {function=} opt_callback
*/
BuildCompilers.copyFile = function(src, dest, opt_callback) {
if (!fileTools.access(src)) {
var message = 'No access to resource ' + src;
log.error(message);
if (opt_callback) {
opt_callback(message, false);
}
return;
}
var destFile = path.join(dest, pathTools.getFileBase(src));
if (pathTools.isFile(dest)) {
destFile = dest;
}
var fileEvent = function(error) {
if (error) {
var message = 'Resource ' + src + ' failed to copy to ' + destFile;
log.error(message);
if (opt_callback) {
opt_callback(message, false, destFile);
}
} else {
if (opt_callback) {
opt_callback(false, false, destFile);
}
}
};
fs.copy(src, destFile, fileEvent.bind(this));
};
/**
* Copy file from remote to dest.
* @param {!string} src
* @param {!string} dest
* @param {function=} opt_callback
*/
BuildCompilers.copyRemoteFile = function(src, dest, opt_callback) {
var destFile = pathTools.getUrlFile(src);
var completeEvent = function(response) {
if (!opt_callback) {
return;
}
if (response.statusCode !== 200) {
opt_callback('Remote Resource' + src + 'failed to download with' +
'http status: ' + response.statusCode, false, destFile);
} else {
opt_callback(false, false, destFile);
}
};
var errorEvent = function(error) {
if (!opt_callback) {
return;
}
if (error && error.code == 'ENOTFOUND') {
var warnMessage = 'Resource at ' + error.hostname +
' is not reachable!\n' +
'Please make sure you are online and that the name is correct!\n' +
'(This message could be ignored if you are working offline!)';
opt_callback(false, warnMessage, destFile);
} else {
opt_callback('Remote resource ' + src + ' failed to copy to ' +
destFile + ':' + error, false, destFile);
}
};
remoteTools.getFile(src, dest, destFile, completeEvent, errorEvent);
};
/**
* Copy files from srcs to dest.
* @param {!string} srcs
* @param {!string} dest
* @param {function=} opt_callback
*/
BuildCompilers.copyFiles = function(srcs, dest, opt_callback) {
if (pathTools.isFile(dest)) {
fileTools.mkdir(path.dirname(dest));
} else {
fileTools.mkdir(dest);
}
var errors_ = [];
var warnings_ = [];
var files_ = [];
var numFiles_ = srcs.length;
var numDone_ = 0;
var callback = function(errors, warnings, files) {
if (errors) {
errors_.push(errors);
}
if (warnings) {
warnings_.push(warnings);
}
if (files) {
files_.push(files);
}
numDone_ += 1;
if (numFiles_ <= numDone_) {
if (opt_callback) {
opt_callback(errors_, warnings_, files_);
}
}
}.bind(this);
for (var i = numFiles_ - 1; i >= 0; i--) {
if (validator.isURL(srcs[i])) {
BuildCompilers.copyRemoteFile(srcs[i], dest, callback);
} else {
BuildCompilers.copyFile(srcs[i], dest, callback);
}
}
};
/**
* @param {Array} files
* @param {string=} out
* @param {object=} opt_options Additional options for the compiler.
* opt_options.config = BuildConfig
* opt_options.options = Additional compiler options
* @param {function=} opt_callback
*/
BuildCompilers.compileSoyTemplates = function(files, out,
opt_options, opt_callback) {
var options = (opt_options && opt_options.options) ?
opt_options.options : {};
var config = (opt_options && opt_options.config) ?
opt_options.config : false;
var message = 'Compiling ' + files.length + ' soy files to ' + out;
if (typeof options.shouldProvideRequireSoyNamespaces === 'undefined') {
options.shouldProvideRequireSoyNamespaces = true;
}
if (config) {
config.setMessage(message);
if (config.i18n) {
options.i18n = config.i18n;
}
if (config.requireSoyi18n) {
options.i18n = true;
options.use_i18n = config.requireSoyi18n;
}
} else {
log.debug(message);
log.trace(files);
}
fileTools.mkdir(out);
var compilerEvent = function(errors, warnings, files) {
if (!errors) {
var success_message = 'Compiled ' + files.length + ' soy files to ' +
textTools.getTruncateText(out);
if (config) {
config.setMessage(success_message);
}
}
if (opt_callback) {
opt_callback(errors, warnings, files);
}
}.bind(this);
closureTemplatesCompiler.compile(files, options, out, compilerEvent);
};
/**
* @param {Array} files
* @param {string=} output
* @param {function=} opt_callback
* @param {BuildConfig=} opt_config
*/
BuildCompilers.compileCssFiles = function(files, out, opt_callback,
opt_config) {
var compilerEvent = function(errors, minified) {
if (errors) {
var errorsMessage = 'Failed for ' + out + ':' + errors;
BuildCompilers.errorCssCompiler(errorsMessage);
if (opt_config) {
opt_config.setMessage(errorsMessage);
}
if (opt_callback) {
opt_callback(errors, false);
}
} else if (minified) {
fileTools.saveContent(out, minified.styles, opt_callback, opt_config);
}
}.bind(this);
new cleanCss().minify(files, compilerEvent);
};
/**
* @param {Array} files
* @param {string=} output
* @param {function=} opt_callback
* @param {BuildConfig=} opt_config
*/
BuildCompilers.compileNodeFiles = function(files, out, opt_callback,
opt_config) {
var nodeCompiler = browserify();
nodeCompiler.add(files);
fileTools.mkfile(out);
var bufferEvent = function(errors) {
if (errors) {
var error_message = 'Was not able to write file ' + out + ':' + errors;
this.errorNodeCompiler(error_message);
if (opt_callback) {
opt_callback(errors, false);
}
} else {
if (opt_config) {
opt_config.setMessage('Saving output to ' + out);
}
}
}.bind(this);
var streamEvent = fs.createWriteStream(out);
streamEvent.on('error', function(error) {
var error_message = 'Was not able to write file ' + out + ':' + error;
this.errorNodeCompiler(error_message);
if (opt_callback) {
opt_callback(error, false);
}
}.bind(this));
streamEvent.on('finish', function() {
if (opt_callback) {
opt_callback(false, false, out);
}
});
nodeCompiler.bundle(bufferEvent).pipe(streamEvent);
};
/**
* @param {Array} file
* @param {string=} output
* @param {function=} opt_callback
* @param {BuildConfig=} opt_config
*/
BuildCompilers.convertMarkdownFile = function(file, out, opt_callback,
opt_config) {
var markdown = fs.readFileSync(file, 'utf8');
var content = marked(markdown);
var destFile = path.join(out,
pathTools.getPathFile(file).replace('.md', '.html'));
fileTools.saveContent(destFile, content, opt_callback, opt_config);
};
/**
* @param {Array} files
* @param {string=} output
* @param {function=} opt_callback
* @param {BuildConfig=} opt_config
*/
BuildCompilers.convertMarkdownFiles = function(files, out,
opt_callback, opt_config) {
var foundError = false;
var outFiles = [];
var errorEvent = function(error, warning, file) {
if (error) {
foundError = error;
} else if (file) {
outFiles.push(file);
}
}.bind(this);
for (var i in files) {
BuildCompilers.convertMarkdownFile(files[i], out, errorEvent, opt_config);
if (foundError) {
break;
}
}
if (opt_callback) {
opt_callback(foundError, false, outFiles, '');
}
};
/**
* @param {Array} files
* @param {string=} output
* @param {object=} opt_options Additional options for the compiler.
* opt_options.config = BuildConfig
* opt_options.options = Additional compiler options
* @param {function=} opt_callback
*/
BuildCompilers.compileJsFiles = function(files, out,
opt_options, opt_callback) {
var options = (opt_options && opt_options.options) ? opt_options.options : {};
var config = (opt_options && opt_options.config) ? opt_options.config : false;
log.debug('Compiling', files.length, 'files to', out, '...');
log.trace(files);
var useRemoteService = false;
if (config) {
if (config.entryPoint) {
options.dependency_mode = 'STRICT';
options.entry_point = config.entryPoint;
}
if (config.remoteService) {
useRemoteService = true;
delete options.entry_point;
delete options.dependency_mode;
}
if (config.requireECMAScript6) {
options.language_in = 'ECMASCRIPT6';
options.language_out = 'ES5_STRICT';
}
if (config.requireClosureExport) {
options.generate_exports = true;
}
if (config.requireSoyLibrary) {
options.use_closure_templates = true;
}
if (config.requireClosureLibrary) {
options.use_closure_library = true;
}
if (config.compress) {
options.compilation_level = 'ADVANCED_OPTIMIZATIONS';
}
if (config.externs) {
options.externs = config.externs;
}
if (!config.warn) {
options.no_warnings = true;
}
if (config.outSourceMap) {
options.create_source_map = config.outSourceMap;
}
if (config.jscompOff !== undefined && config.jscompOff.length > 0) {
options.jscomp_off = config.jscompOff;
}
if (config.jscompWarning !== undefined && config.jscompWarning.length > 0) {
options.jscomp_warning = config.jscompWarning;
}
if (config.jscompError !== undefined && config.jscompError.length > 0) {
options.jscomp_error = config.jscompError;
}
}
var compilerEvent = function(errors, warnings, target_file, content) {
if (errors) {
if (opt_callback) {
opt_callback(errors, warnings);
}
} else if (content) {
fileTools.saveContent(out, content, opt_callback, config, warnings);
} else {
if (opt_callback) {
opt_callback(errors, warnings);
}
}
}.bind(this);
closureCompiler.compile(files, options, null, compilerEvent,
useRemoteService);
};
/**
* @param {string} msg
*/
BuildCompilers.errorCssCompiler = function(msg) {
log.error('[Css Compiler Error]', msg);
};
/**
* @param {string} msg
*/
BuildCompilers.errorNodeCompiler = function(msg) {
log.error('[Node Compiler Error]', msg);
};
module.exports = BuildCompilers;